diff --git a/gateway/handlers/forwarding_proxy.go b/gateway/handlers/forwarding_proxy.go index 63db766d..cc6e4b2e 100644 --- a/gateway/handlers/forwarding_proxy.go +++ b/gateway/handlers/forwarding_proxy.go @@ -29,12 +29,17 @@ type BaseURLResolver interface { Resolve(r *http.Request) string } +// RequestURLPathTransformer Transform the incoming URL path for upstream requests +type URLPathTransformer interface { + Transform(r *http.Request) string +} + // MakeForwardingProxyHandler create a handler which forwards HTTP requests -func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, notifiers []HTTPNotifier, baseURLResolver BaseURLResolver) http.HandlerFunc { +func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, notifiers []HTTPNotifier, baseURLResolver BaseURLResolver, urlPathTransformer URLPathTransformer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { baseURL := baseURLResolver.Resolve(r) - requestURL := r.URL.Path + requestURL := urlPathTransformer.Transform(r) start := time.Now() @@ -51,23 +56,7 @@ func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, notifiers [ } func buildUpstreamRequest(r *http.Request, baseURL string, requestURL string) *http.Request { - url := baseURL - - if requestURL != "" { - // When forwarding to a function, since the `/function/xyz` portion - // of a path like `/function/xyz/rest/of/path` is only used or needed - // by the Gateway, we want to trim it down to `/rest/of/path` for the - // upstream request. In the following regex, in the case of a match - // the requestURL will be at `0`, the function name at `1` and the - // rest of the path (the part we are interested in) at `2`. - matcher := functionMatcher.Copy() - parts := matcher.FindStringSubmatch(requestURL) - if 3 == len(parts) { - url += parts[2] - } else { - url += requestURL - } - } + url := baseURL + requestURL if len(r.URL.RawQuery) > 0 { url = fmt.Sprintf("%s?%s", url, r.URL.RawQuery) @@ -211,3 +200,46 @@ func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string { return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort) } + +// TransparentURLPathTransformer passes the requested URL path through untouched. +type TransparentURLPathTransformer struct { +} + +// Transform returns the URL path unchanged. +func (f TransparentURLPathTransformer) Transform(r *http.Request) string { + return r.URL.Path +} + +// PathTruncatingURLPathTransformer always truncated the path to "/". +type PathTruncatingURLPathTransformer struct { +} + +// Transform always return a path of "/". +func (f PathTruncatingURLPathTransformer) Transform(r *http.Request) string { + return "/" +} + +// FunctionPrefixTrimmingURLPathTransformer removes the "/function/servicename/" prefix from the URL path. +type FunctionPrefixTrimmingURLPathTransformer struct { +} + +// Transform removes the "/function/servicename/" prefix from the URL path. +func (f FunctionPrefixTrimmingURLPathTransformer) Transform(r *http.Request) string { + ret := r.URL.Path + + if ret != "" { + // When forwarding to a function, since the `/function/xyz` portion + // of a path like `/function/xyz/rest/of/path` is only used or needed + // by the Gateway, we want to trim it down to `/rest/of/path` for the + // upstream request. In the following regex, in the case of a match + // the r.URL.Path will be at `0`, the function name at `1` and the + // rest of the path (the part we are interested in) at `2`. + matcher := functionMatcher.Copy() + parts := matcher.FindStringSubmatch(ret) + if 3 == len(parts) { + ret = parts[2] + } + } + + return ret +} diff --git a/gateway/handlers/forwarding_proxy_test.go b/gateway/handlers/forwarding_proxy_test.go index e973a5c8..a16d72f5 100644 --- a/gateway/handlers/forwarding_proxy_test.go +++ b/gateway/handlers/forwarding_proxy_test.go @@ -164,7 +164,10 @@ func Test_buildUpstreamRequest_Body_Method_Query_Path(t *testing.T) { t.Fail() } - upstream := buildUpstreamRequest(request, "http://xyz:8080", request.URL.Path) + transformer := FunctionPrefixTrimmingURLPathTransformer{} + transformedPath := transformer.Transform(request) + + upstream := buildUpstreamRequest(request, "http://xyz:8080", transformedPath) if request.Method != upstream.Method { t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method) diff --git a/gateway/handlers/queueproxy.go b/gateway/handlers/queueproxy.go index e61cc61f..c6dc839c 100644 --- a/gateway/handlers/queueproxy.go +++ b/gateway/handlers/queueproxy.go @@ -15,7 +15,7 @@ import ( ) // MakeQueuedProxy accepts work onto a queue -func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueRequests queue.CanQueueRequests) http.HandlerFunc { +func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueRequests queue.CanQueueRequests, pathTransformer URLPathTransformer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -49,6 +49,7 @@ func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueReque Body: body, Method: r.Method, QueryString: r.URL.RawQuery, + Path: pathTransformer.Transform(r), Header: r.Header, Host: r.Host, CallbackURL: callbackURL, diff --git a/gateway/queue/types.go b/gateway/queue/types.go index f955480f..2e625be6 100644 --- a/gateway/queue/types.go +++ b/gateway/queue/types.go @@ -12,6 +12,7 @@ type Request struct { Host string Body []byte Method string + Path string QueryString string Function string CallbackURL *url.URL `json:"CallbackUrl"` diff --git a/gateway/server.go b/gateway/server.go index 40c066a5..cf7b0d4f 100644 --- a/gateway/server.go +++ b/gateway/server.go @@ -74,14 +74,23 @@ func main() { functionURLResolver = urlResolver } - faasHandlers.Proxy = handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver) + urlTransformer := handlers.PathTruncatingURLPathTransformer{} + var functionURLTransformer handlers.URLPathTransformer - faasHandlers.RoutelessProxy = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) - faasHandlers.ListFunctions = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) - faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) - faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) - faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) - queryFunction := handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) + if config.PassURLPathsToFunctions { + functionURLTransformer = handlers.FunctionPrefixTrimmingURLPathTransformer{} + } else { + functionURLTransformer = urlTransformer + } + + faasHandlers.Proxy = handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver, functionURLTransformer) + + faasHandlers.RoutelessProxy = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) + faasHandlers.ListFunctions = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) + faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) + faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) + faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) + queryFunction := handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) alertHandler := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL) faasHandlers.Alert = handlers.MakeAlertHandler(alertHandler) @@ -93,7 +102,7 @@ func main() { log.Fatalln(queueErr) } - faasHandlers.QueuedProxy = handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, true, natsQueue)) + faasHandlers.QueuedProxy = handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, true, natsQueue, functionURLTransformer)) faasHandlers.AsyncReport = handlers.MakeAsyncReport(metricsOptions) } @@ -101,7 +110,7 @@ func main() { faasHandlers.ListFunctions = metrics.AddMetricsHandler(faasHandlers.ListFunctions, prometheusQuery) faasHandlers.Proxy = handlers.MakeCallIDMiddleware(faasHandlers.Proxy) - faasHandlers.ScaleFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver) + faasHandlers.ScaleFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer) if credentials != nil { faasHandlers.UpdateFunction = @@ -135,10 +144,10 @@ func main() { // r.StrictSlash(false) // This didn't work, so register routes twice. r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", functionProxy) r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", functionProxy) - r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/{params:.*}", faasHandlers.Proxy) + r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/{params:.*}", functionProxy) r.HandleFunc("/system/info", handlers.MakeInfoHandler(handlers.MakeForwardingProxyHandler( - reverseProxy, forwardingNotifiers, urlResolver))).Methods(http.MethodGet) + reverseProxy, forwardingNotifiers, urlResolver, urlTransformer))).Methods(http.MethodGet) r.HandleFunc("/system/alert", faasHandlers.Alert) @@ -172,7 +181,7 @@ func main() { metricsHandler := metrics.PrometheusHandler() r.Handle("/metrics", metricsHandler) - r.HandleFunc("/healthz", handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)).Methods(http.MethodGet) + r.HandleFunc("/healthz", handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, urlTransformer)).Methods(http.MethodGet) r.Handle("/", http.RedirectHandler("/ui/", http.StatusMovedPermanently)).Methods(http.MethodGet) diff --git a/gateway/types/readconfig.go b/gateway/types/readconfig.go index 5f0986e9..5146efb1 100644 --- a/gateway/types/readconfig.go +++ b/gateway/types/readconfig.go @@ -105,6 +105,8 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig { cfg.DirectFunctions = parseBoolValue(hasEnv.Getenv("direct_functions")) cfg.DirectFunctionsSuffix = hasEnv.Getenv("direct_functions_suffix") + cfg.PassURLPathsToFunctions = parseBoolValue(hasEnv.Getenv("pass_url_path_to_functions")) + cfg.UseBasicAuth = parseBoolValue(hasEnv.Getenv("basic_auth")) secretPath := hasEnv.Getenv("secret_mount_path") @@ -150,6 +152,10 @@ type GatewayConfig struct { // If set this will be used to resolve functions directly DirectFunctionsSuffix string + // If set to true, the requested path will be passed along to the function, minus the "/function/xyz" + // prefix, else the path will be truncated to "/" regardless of what the client sends. + PassURLPathsToFunctions bool + // If set, reads secrets from file-system for enabling basic auth. UseBasicAuth bool