diff --git a/gateway/handlers/functionshandler.go b/gateway/handlers/functionshandler.go index d493630ae137428091eb8b1c8312fc9133baea96..8ad50e3d2d40096e1c85b23cfde3229fab14b7fe 100644 --- a/gateway/handlers/functionshandler.go +++ b/gateway/handlers/functionshandler.go @@ -79,9 +79,8 @@ func MakeFunctionReader(metricsOptions metrics.MetricOptions, c *client.Client) // MakeNewFunctionHandler creates a new function (service) inside the swarm network. func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - body, _ := ioutil.ReadAll(r.Body) defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) request := requests.CreateFunctionRequest{} err := json.Unmarshal(body, &request) @@ -91,7 +90,10 @@ func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Clie } fmt.Println(request) - w.WriteHeader(http.StatusNotImplemented) + + // TODO: review why this was here... debugging? + // w.WriteHeader(http.StatusNotImplemented) + options := types.ServiceCreateOptions{} spec := makeSpec(&request) diff --git a/gateway/handlers/proxy.go b/gateway/handlers/proxy.go index 033b51b8188cbed2b7a6fe8df7fe546987b45fef..1e47ffbd42b2cd460aded1687fdc87b51751f70a 100644 --- a/gateway/handlers/proxy.go +++ b/gateway/handlers/proxy.go @@ -103,6 +103,14 @@ func lookupSwarmService(serviceName string, c *client.Client) (bool, error) { return len(services) > 0, err } +func copyHeaders(destination *http.Header, source *http.Header) { + for k, vv := range *source { + vvClone := make([]string, len(vv)) + copy(vvClone, vv) + (*destination)[k] = vvClone + } +} + func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.MetricOptions, service string, requestBody []byte, logger *logrus.Logger, proxyClient *http.Client) { stamp := strconv.FormatInt(time.Now().Unix(), 10) @@ -123,14 +131,12 @@ func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.Metri } contentType := r.Header.Get("Content-Type") - if len(contentType) == 0 { - contentType = "text/plain" - } - fmt.Printf("[%s] Forwarding request [%s] to: %s\n", stamp, contentType, url) request, err := http.NewRequest("POST", url, bytes.NewReader(requestBody)) - request.Header.Add("Content-Type", contentType) + + copyHeaders(&request.Header, &r.Header) + defer request.Body.Close() response, err := proxyClient.Do(request) @@ -152,6 +158,9 @@ func invokeService(w http.ResponseWriter, r *http.Request, metrics metrics.Metri return } + clientHeader := w.Header() + copyHeaders(&clientHeader, &response.Header) + // Match header for strict services w.Header().Set("Content-Type", r.Header.Get("Content-Type")) diff --git a/gateway/tests/integration/README.md b/gateway/tests/integration/README.md new file mode 100644 index 0000000000000000000000000000000000000000..899df19db40d5bdde3e106caa9ae25c38b8cbda6 --- /dev/null +++ b/gateway/tests/integration/README.md @@ -0,0 +1,4 @@ +# Integration testing + +These tests should be run against the sample stack included in the repository root. + diff --git a/gateway/tests/integration/routes_test.go b/gateway/tests/integration/routes_test.go index 8d3f99368fd62cde9af52500ccb6a23ea79257c1..f63ab4610b54896988b6eab4572d6290363bf151 100644 --- a/gateway/tests/integration/routes_test.go +++ b/gateway/tests/integration/routes_test.go @@ -12,10 +12,11 @@ import ( // Before running these tests do a Docker stack deploy. func fireRequest(url string, method string, reqBody string) (string, int, error) { - return fireRequestWithHeader(url, method, reqBody, "") + headers := make(map[string]string) + return fireRequestWithHeaders(url, method, reqBody, headers) } -func fireRequestWithHeader(url string, method string, reqBody string, xheader string) (string, int, error) { +func fireRequestWithHeaders(url string, method string, reqBody string, headers map[string]string) (string, int, error) { httpClient := http.Client{ Timeout: time.Second * 2, // Maximum of 2 secs } @@ -26,9 +27,10 @@ func fireRequestWithHeader(url string, method string, reqBody string, xheader st } req.Header.Set("User-Agent", "go-integration") - if len(xheader) != 0 { - req.Header.Set("X-Function", xheader) + for kk, vv := range headers { + req.Header.Set(kk, vv) } + res, getErr := httpClient.Do(req) if getErr != nil { log.Fatal(getErr) @@ -46,14 +48,33 @@ func TestGet_Rejected(t *testing.T) { var reqBody string _, code, err := fireRequest("http://localhost:8080/function/func_echoit", http.MethodGet, reqBody) if code != http.StatusInternalServerError { - t.Log("Failed") + t.Logf("Failed got: %d", code) } if err != nil { t.Log(err) t.Fail() } +} + +func TestEchoIt_Post_Route_Handler_ForwardsClientHeaders(t *testing.T) { + reqBody := "test message" + headers := make(map[string]string, 0) + headers["X-Api-Key"] = "123" + body, code, err := fireRequestWithHeaders("http://localhost:8080/function/func_echoit", http.MethodPost, reqBody, headers) + + if err != nil { + t.Log(err) + t.Fail() + } + if code != http.StatusOK { + t.Log("Failed") + } + if body != reqBody { + t.Log("Expected body returned") + t.Fail() + } } func TestEchoIt_Post_Route_Handler(t *testing.T) { @@ -73,9 +94,12 @@ func TestEchoIt_Post_Route_Handler(t *testing.T) { } } -func TestEchoIt_Post_Header_Handler(t *testing.T) { +func TestEchoIt_Post_X_Header_Routing_Handler(t *testing.T) { reqBody := "test message" - body, code, err := fireRequestWithHeader("http://localhost:8080/", http.MethodPost, reqBody, "func_echoit") + headers := make(map[string]string, 0) + headers["X-Function"] = "func_echoit" + + body, code, err := fireRequestWithHeaders("http://localhost:8080/", http.MethodPost, reqBody, headers) if err != nil { t.Log(err) diff --git a/watchdog/main.go b/watchdog/main.go index 5a5f8b7ea007431af2893cb22130ef2fd34e6454..02c6ee205df5c82f2ec4919d8cf1365535ec84d7 100644 --- a/watchdog/main.go +++ b/watchdog/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "fmt" "io/ioutil" "log" "net/http" @@ -31,9 +32,19 @@ func buildFunctionInput(config *WatchdogConfig, r *http.Request) ([]byte, error) return res, err } +func debugHeaders(source *http.Header, direction string) { + for k, vv := range *source { + fmt.Printf("[%s] %s=%s\n", direction, k, vv) + } +} + func pipeRequest(config *WatchdogConfig, w http.ResponseWriter, r *http.Request) { parts := strings.Split(config.faasProcess, " ") + if config.debugHeaders { + debugHeaders(&r.Header, "in") + } + targetCmd := exec.Command(parts[0], parts[1:]...) writer, _ := targetCmd.StdinPipe() @@ -51,6 +62,7 @@ func pipeRequest(config *WatchdogConfig, w http.ResponseWriter, r *http.Request) return } + // Write to pipe in separate go-routine to prevent blocking go func() { defer wg.Done() writer.Write(res) @@ -77,12 +89,18 @@ func pipeRequest(config *WatchdogConfig, w http.ResponseWriter, r *http.Request) os.Stdout.Write(out) } - // Match header for strict services - if r.Header.Get("Content-Type") == "application/json" { + clientContentType := r.Header.Get("Content-Type") + if len(clientContentType) > 0 { w.Header().Set("Content-Type", "application/json") } + w.WriteHeader(200) w.Write(out) + + if config.debugHeaders { + header := w.Header() + debugHeaders(&header, "out") + } } func makeRequestHandler(config *WatchdogConfig) func(http.ResponseWriter, *http.Request) { diff --git a/watchdog/readconfig.go b/watchdog/readconfig.go index fd5a877737776356f559cce8c0d8d9310431004d..7c8910db57c5a1eb5b4004eab49a2bb7e1e476dd 100644 --- a/watchdog/readconfig.go +++ b/watchdog/readconfig.go @@ -58,13 +58,15 @@ func (ReadConfig) Read(hasEnv HasEnv) WatchdogConfig { cfg.writeDebug = parseBoolValue(hasEnv.Getenv("write_debug")) cfg.marshallRequest = parseBoolValue(hasEnv.Getenv("marshall_request")) + cfg.debugHeaders = parseBoolValue(hasEnv.Getenv("debug_headers")) return cfg } // WatchdogConfig for the process. type WatchdogConfig struct { - readTimeout time.Duration + readTimeout time.Duration + writeTimeout time.Duration // faasProcess is the process to exec faasProcess string @@ -73,4 +75,7 @@ type WatchdogConfig struct { writeDebug bool marshallRequest bool + + // prints out all incoming and out-going HTTP headers + debugHeaders bool }