From afe8a959ca1e4b1b062f1d3cce0fdf577f885506 Mon Sep 17 00:00:00 2001 From: Matej Vasek Date: Wed, 8 Jun 2022 13:30:37 +0200 Subject: [PATCH] Plain JSON body in func invoke with HTTP (#1039) Do not use form encoding. Signed-off-by: Matej Vasek --- client_test.go | 45 +++++++++++++++++++++++++++++---------------- invoke.go | 11 ++++++++++- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/client_test.go b/client_test.go index 3356773e8..7e1ba3840 100644 --- a/client_test.go +++ b/client_test.go @@ -8,12 +8,14 @@ import ( "context" "errors" "fmt" + "io" "io/ioutil" "net" "net/http" "os" "path/filepath" "reflect" + "sync/atomic" "testing" "time" @@ -1042,7 +1044,7 @@ func TestClient_Invoke_HTTP(t *testing.T) { defer Using(t, root)() // Flag indicating the Function was invoked - var invoked bool + var invoked int32 // The message to send to the Function // Individual fields can be overridden, by default all fields are populeted @@ -1053,21 +1055,31 @@ func TestClient_Invoke_HTTP(t *testing.T) { // invoker POSTed the invocation message. handler := http.NewServeMux() handler.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { - invoked = true - if err := req.ParseForm(); err != nil { - t.Fatal(err) - } + atomic.StoreInt32(&invoked, 1) + // Verify that we POST to HTTP endpoints by default if req.Method != "POST" { - t.Fatalf("expected 'POST' request, got '%v'", req.Method) + t.Errorf("expected 'POST' request, got %q", req.Method) + return } - // Verify the values came through via a spot-check of the unique ID - if req.Form.Get("ID") != message.ID { - t.Fatalf("expected message ID '%v', got '%v'", message.ID, req.Form.Get("ID")) - } - _, err := res.Write([]byte("hello world")) + + data, err := io.ReadAll(req.Body) if err != nil { - t.Fatal(err) + t.Errorf("cannot read request body: %v", err) + return + } + dataAsStr := string(data) + + // Verify the body is correct + if dataAsStr != message.Data { + t.Errorf("expected message data %q, got %q", message.Data, dataAsStr) + return + } + + _, err = res.Write([]byte("hello world")) + if err != nil { + t.Error(err) + return } }) @@ -1082,7 +1094,9 @@ func TestClient_Invoke_HTTP(t *testing.T) { fmt.Fprintf(os.Stderr, "error serving: %v", err) } }() - defer s.Close() + t.Cleanup(func() { + _ = s.Close() + }) // Create a client with a mock runner which will report the port at which the // interloping Function is listening. @@ -1106,8 +1120,7 @@ func TestClient_Invoke_HTTP(t *testing.T) { if err != nil { t.Fatal(err) } - defer job.Stop() - + t.Cleanup(job.Stop) // Invoke the Function, which will use the mock Runner h, r, err := client.Invoke(context.Background(), f.Root, "", message) if err != nil { @@ -1125,7 +1138,7 @@ func TestClient_Invoke_HTTP(t *testing.T) { } // Fail if the Function was never invoked. - if !invoked { + if atomic.LoadInt32(&invoked) == 0 { t.Fatal("Function was not invoked") } diff --git a/invoke.go b/invoke.go index bd3aa748f..d5c2447e1 100644 --- a/invoke.go +++ b/invoke.go @@ -1,6 +1,7 @@ package function import ( + "bytes" "context" "errors" "fmt" @@ -193,10 +194,18 @@ func sendPost(ctx context.Context, route string, m InvokeMessage, t http.RoundTr fmt.Printf(" %v: %v\n", k, v[0]) // NOTE len==1 value slices assumed } } - resp, err := client.PostForm(route, values) + + req, err := http.NewRequestWithContext(ctx, "POST", route, bytes.NewBufferString(m.Data)) + if err != nil { + return nil, "", fmt.Errorf("failure to create request: %w", err) + } + req.Header.Add("Content-Type", m.ContentType) + + resp, err := client.Do(req) if err != nil { return nil, "", err } + defer resp.Body.Close() if resp.StatusCode > 299 { return nil, "", fmt.Errorf("failure invoking '%v' (HTTP %v)", route, resp.StatusCode)