From a7187527ab3278128c1b2a8fe9856d49ecddf25d Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Wed, 21 Sep 2022 09:06:33 -0700 Subject: [PATCH] Helper function to create event from HTTP Request or Response (#799) * Helper function to create event from HTTP Request or Response * Add missing use of encoding options and remove redundant function call Signed-off-by: Adam Ross * Update v2/protocol/http/utility.go Co-authored-by: Scott Nichols * Update v2/protocol/http/utility.go Co-authored-by: Scott Nichols * Update v2/protocol/http/utility.go Co-authored-by: Scott Nichols * Update v2/protocol/http/utility.go Co-authored-by: Scott Nichols * capitalize HTTP and WIP clean-up Signed-off-by: Adam Ross Signed-off-by: Adam Ross Co-authored-by: Scott Nichols --- README.md | 13 +++++ docs/index.md | 13 +++++ v2/alias.go | 4 ++ v2/protocol/http/utility.go | 26 +++++++++ v2/protocol/http/utility_test.go | 91 ++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 v2/protocol/http/utility.go create mode 100644 v2/protocol/http/utility_test.go diff --git a/README.md b/README.md index 0be83aaee..f57940893 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,19 @@ func main() { } ``` +## Create a CloudEvent from an HTTP Request + +```go +func handler(w http.ResponseWriter, r *http.Request) { + event, err := cloudevents.NewCloudEventFromHTTPRequest(r) + if err != nil { + log.Print("failed to parse CloudEvent from request: %v", err) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + } + w.Write([]byte(*event.String())) +} +``` + ## Serialize/Deserialize a CloudEvent To marshal a CloudEvent into JSON: diff --git a/docs/index.md b/docs/index.md index 3b821970d..082d00b1b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -81,6 +81,19 @@ func main() { } ``` +## Create a CloudEvent from an HTTP Request + +```go +func handler(w http.ResponseWriter, r *http.Request) { + event, err := cloudevents.NewCloudEventFromHTTPRequest(r) + if err != nil { + log.Print("failed to parse CloudEvent from request: %v", err) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + } + w.Write([]byte(*event.String())) +} +``` + ## Serialize/Deserialize a CloudEvent To marshal a CloudEvent into JSON: diff --git a/v2/alias.go b/v2/alias.go index e7ed3a357..a30980a4b 100644 --- a/v2/alias.go +++ b/v2/alias.go @@ -134,6 +134,10 @@ var ( ToMessage = binding.ToMessage + // Event Creation + NewEventFromHTTPRequest = http.NewEventFromHTTPRequest + NewEventFromHTTPResponse = http.NewEventFromHTTPResponse + // HTTP Messages WriteHTTPRequest = http.WriteRequest diff --git a/v2/protocol/http/utility.go b/v2/protocol/http/utility.go new file mode 100644 index 000000000..d46a33461 --- /dev/null +++ b/v2/protocol/http/utility.go @@ -0,0 +1,26 @@ +/* + Copyright 2022 The CloudEvents Authors + SPDX-License-Identifier: Apache-2.0 +*/ + +package http + +import ( + "context" + nethttp "net/http" + + "github.com/cloudevents/sdk-go/v2/binding" + "github.com/cloudevents/sdk-go/v2/event" +) + +// NewEventFromHTTPRequest returns an Event. +func NewEventFromHTTPRequest(req *nethttp.Request) (*event.Event, error) { + msg := NewMessageFromHttpRequest(req) + return binding.ToEvent(context.Background(), msg) +} + +// NewEventFromHTTPResponse returns an Event. +func NewEventFromHTTPResponse(resp *nethttp.Response) (*event.Event, error) { + msg := NewMessageFromHttpResponse(resp) + return binding.ToEvent(context.Background(), msg) +} diff --git a/v2/protocol/http/utility_test.go b/v2/protocol/http/utility_test.go new file mode 100644 index 000000000..992751cb4 --- /dev/null +++ b/v2/protocol/http/utility_test.go @@ -0,0 +1,91 @@ +/* + Copyright 2022 The CloudEvents Authors + SPDX-License-Identifier: Apache-2.0 +*/ + +package http + +import ( + "bytes" + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cloudevents/sdk-go/v2/binding" + "github.com/cloudevents/sdk-go/v2/event" + "github.com/cloudevents/sdk-go/v2/test" +) + +func TestNewEventFromHttpRequest(t *testing.T) { + tests := []struct { + name string + encoding binding.Encoding + }{{ + name: "Structured encoding", + encoding: binding.EncodingStructured, + }, { + name: "Binary encoding", + encoding: binding.EncodingBinary, + }} + + for _, tt := range tests { + test.EachEvent(t, test.Events(), func(t *testing.T, eventIn event.Event) { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + if tt.encoding == binding.EncodingStructured { + ctx = binding.WithForceStructured(ctx) + } else if tt.encoding == binding.EncodingBinary { + ctx = binding.WithForceBinary(ctx) + } + + req := httptest.NewRequest("POST", "http://localhost", nil) + require.NoError(t, WriteRequest(ctx, (*binding.EventMessage)(&eventIn), req)) + + got, err := NewEventFromHTTPRequest(req) + require.NoError(t, err) + test.AssertEvent(t, *got, test.IsValid()) + }) + }) + } +} + +func TestNewEventFromHttpResponse(t *testing.T) { + tests := []struct { + name string + resp *http.Response + }{{ + name: "Structured encoding", + resp: &http.Response{ + Header: http.Header{ + "Content-Type": {event.ApplicationCloudEventsJSON}, + }, + Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"data":"foo","datacontenttype":"application/json","id":"id","source":"source","specversion":"1.0","type":"type"}`))), + ContentLength: 113, + }, + }, { + name: "Binary encoding", + resp: &http.Response{ + Header: func() http.Header { + h := http.Header{} + h.Set("ce-specversion", "1.0") + h.Set("ce-source", "unittest") + h.Set("ce-type", "unittest") + h.Set("ce-id", "unittest") + h.Set("Content-Type", "application/json") + return h + }(), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewEventFromHTTPResponse(tt.resp) + require.NoError(t, err) + test.AssertEvent(t, *got, test.IsValid()) + }) + } +}