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