commit
97b5c8d43f
4 changed files with 171 additions and 0 deletions
@ -0,0 +1 @@ |
|||
apicast-sidecar-proxy |
|||
@ -0,0 +1,5 @@ |
|||
FROM scratch |
|||
COPY apicast-sidecar-proxy /apicast-sidecar-proxy |
|||
EXPOSE 9090 9091 |
|||
ENTRYPOINT [ "/apicast-sidecar-proxy" ] |
|||
|
|||
@ -0,0 +1,41 @@ |
|||
# A sidecar for Apicast, enabling proxy support |
|||
|
|||
## Why this project ? |
|||
|
|||
[Apicast](https://github.com/3scale/apicast) is an API gateway that fetches his |
|||
configuration and asks for authorization on the 3scale services (hosted in their |
|||
cloud). The gateway, however, is deployed on-premise in the customer network. |
|||
|
|||
More and more customers enforce the use of an HTTP proxy for outgoing connections. |
|||
Sometimes, an authentication is required to connect to the proxy. |
|||
|
|||
Currently, apicast does not fully support HTTP proxies. Hence this project provides |
|||
a sidecar container for Apicast that enables HTTP proxies support. |
|||
|
|||
## Deployment |
|||
|
|||
TODO |
|||
|
|||
## Development |
|||
|
|||
### Build |
|||
``` |
|||
GOOS=linux GOARCH=amd64 go build -o apicast-sidecar-proxy src/itix.fr/forward/main.go |
|||
``` |
|||
|
|||
### Package |
|||
|
|||
``` |
|||
VERSION=1.0 |
|||
git tag TODO |
|||
docker build -t apicast-sidecar-proxy:$VERSION . |
|||
``` |
|||
|
|||
### Pushing your image to DockerHub (Optional) |
|||
|
|||
``` |
|||
docker login https://index.docker.io/v1/ |
|||
docker images apicast-sidecar-proxy:$VERSION --format '{{ .ID }}' |
|||
docker tag $(docker images apicast-sidecar-proxy:$VERSION --format '{{ .ID }}') index.docker.io/<your-username>/apicast-sidecar-proxy:$VERSION |
|||
docker push index.docker.io/<your-username>/apicast-sidecar-proxy:$VERSION |
|||
``` |
|||
@ -0,0 +1,124 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"net/url" |
|||
"os" |
|||
"strconv" |
|||
) |
|||
|
|||
// The MyResponseWriter is a wrapper around the standard http.ResponseWriter
|
|||
// We need it to retrieve the http status code of an http response
|
|||
type MyResponseWriter struct { |
|||
Underlying http.ResponseWriter |
|||
Status int |
|||
} |
|||
|
|||
func (mrw *MyResponseWriter) Header() http.Header { |
|||
return mrw.Underlying.Header() |
|||
} |
|||
|
|||
func (mrw *MyResponseWriter) Write(b []byte) (int, error) { |
|||
return mrw.Underlying.Write(b) |
|||
} |
|||
|
|||
func (mrw *MyResponseWriter) WriteHeader(s int) { |
|||
mrw.Status = s |
|||
mrw.Underlying.WriteHeader(s) |
|||
} |
|||
|
|||
func SetupReverseProxy(local_port int, target string) { |
|||
// Parse the target URL and perform some sanity checks
|
|||
url, err := url.Parse(target) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
// Initialize a Reverse Proxy object with a custom director
|
|||
proxy := httputil.NewSingleHostReverseProxy(url) |
|||
underlying_director := proxy.Director |
|||
proxy.Director = func(req *http.Request) { |
|||
// Let the underlying director do the mandatory job
|
|||
underlying_director(req) |
|||
|
|||
// Custom Handling
|
|||
// ---------------
|
|||
//
|
|||
// Filter out the "Host" header sent by the client
|
|||
// otherwise the target server won't be able to find the
|
|||
// matching virtual host. The correct host header will be
|
|||
// added automatically by the net/http package.
|
|||
req.Host = "" |
|||
} |
|||
handler := http.NewServeMux() |
|||
handler.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { |
|||
// Log the incoming request (including headers)
|
|||
fmt.Printf("%v %v HTTP/1.1\n", req.Method, req.URL) |
|||
req.Header.Write(os.Stdout) |
|||
fmt.Println() |
|||
|
|||
// Wrap the standard response writer with our own
|
|||
// implementation because we need the status code of the
|
|||
// response and that field is not exported by default
|
|||
mrw := &MyResponseWriter{Underlying: rw} |
|||
|
|||
// Let the reverse proxy handle the request
|
|||
proxy.ServeHTTP(mrw, req) |
|||
|
|||
// Log the response
|
|||
fmt.Printf("%v %v\n", mrw.Status, http.StatusText(mrw.Status)) |
|||
mrw.Header().Write(os.Stdout) |
|||
fmt.Println() |
|||
}) |
|||
|
|||
fmt.Printf("Listening on port %v for incoming requests...\n", local_port) |
|||
err = http.ListenAndServe(fmt.Sprintf(":%v", local_port), handler) |
|||
if (err != nil) { |
|||
fmt.Println("ERROR: %s", err) |
|||
} |
|||
} |
|||
|
|||
func main() { |
|||
portal_endpoint := os.Getenv("THREESCALE_PORTAL_ENDPOINT") |
|||
backend_endpoint := os.Getenv("BACKEND_ENDPOINT_OVERRIDE") |
|||
|
|||
portal_port_opt := os.Getenv("PORTAL_LISTEN_PORT") |
|||
if (portal_port_opt == "") { |
|||
portal_port_opt = "9090" |
|||
fmt.Println("WARNING: No PORTAL_LISTEN_PORT environment variable found, defaulting to '9090'...") |
|||
} |
|||
|
|||
backend_port_opt := os.Getenv("BACKEND_LISTEN_PORT") |
|||
if (backend_port_opt == "") { |
|||
backend_port_opt = "9091" |
|||
fmt.Println("WARNING: No BACKEND_LISTEN_PORT environment variable found, defaulting to '9091'...") |
|||
} |
|||
|
|||
error := false |
|||
if portal_endpoint == "" { |
|||
fmt.Println("ERROR: No THREESCALE_PORTAL_ENDPOINT environment variable found !") |
|||
error = true |
|||
} |
|||
if backend_endpoint == "" { |
|||
fmt.Println("ERROR: No BACKEND_ENDPOINT_OVERRIDE environment variable found !") |
|||
error = true |
|||
} |
|||
portal_port, err := strconv.Atoi(portal_port_opt) |
|||
if (err != nil) { |
|||
fmt.Printf("ERROR: Cannot parse the PORTAL_LISTEN_PORT environment variable (%s): %s\n", portal_port_opt, err) |
|||
error = true |
|||
} |
|||
backend_port, err := strconv.Atoi(backend_port_opt) |
|||
if (err != nil) { |
|||
fmt.Printf("ERROR: Cannot parse the BACKEND_LISTEN_PORT environment variable (%s): %s\n", backend_port_opt, err) |
|||
error = true |
|||
} |
|||
if error { |
|||
os.Exit(1) |
|||
} |
|||
|
|||
go SetupReverseProxy(backend_port, backend_endpoint) |
|||
SetupReverseProxy(portal_port, portal_endpoint) |
|||
} |
|||
Loading…
Reference in new issue