diff --git a/pkg/broker/filter/filter_handler.go b/pkg/broker/filter/filter_handler.go index 72012efcad2..215c0d3f498 100644 --- a/pkg/broker/filter/filter_handler.go +++ b/pkg/broker/filter/filter_handler.go @@ -105,6 +105,7 @@ func (h *Handler) Start(ctx context.Context) error { // 5. send event to trigger's subscriber // 6. write the response func (h *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Allow", "POST") if request.Method != http.MethodPost { writer.WriteHeader(http.StatusMethodNotAllowed) diff --git a/pkg/broker/ingress/ingress_handler.go b/pkg/broker/ingress/ingress_handler.go index 6468e888027..047b9c5c389 100644 --- a/pkg/broker/ingress/ingress_handler.go +++ b/pkg/broker/ingress/ingress_handler.go @@ -101,7 +101,14 @@ func (h *Handler) Start(ctx context.Context) error { } func (h *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Allow", "POST, OPTIONS") // validate request method + if request.Method == http.MethodOptions { + writer.Header().Set("WebHook-Allowed-Origin", "*") // Accept from any Origin: + writer.Header().Set("WebHook-Allowed-Rate", "*") // Unlimited requests/minute + writer.WriteHeader(http.StatusOK) + return + } if request.Method != http.MethodPost { h.Logger.Warn("unexpected request method", zap.String("method", request.Method)) writer.WriteHeader(http.StatusMethodNotAllowed) diff --git a/pkg/broker/ingress/ingress_handler_test.go b/pkg/broker/ingress/ingress_handler_test.go index ab7e0a464ae..3de13349f46 100644 --- a/pkg/broker/ingress/ingress_handler_test.go +++ b/pkg/broker/ingress/ingress_handler_test.go @@ -104,20 +104,29 @@ func TestHandler_ServeHTTP(t *testing.T) { defaulter: broker.TTLDefaulter(logger, 100), }, { - name: "invalid method OPTIONS", - method: nethttp.MethodOptions, - uri: "/ns/name", - body: getValidEvent(), - statusCode: nethttp.StatusMethodNotAllowed, + name: "valid method OPTIONS", + method: nethttp.MethodOptions, + uri: "/ns/name", + body: strings.NewReader(""), + expectedHeaders: nethttp.Header{ + "Allow": []string{"PUT, OPTIONS"}, + "WebHook-Allowed-Origin": []string{"*"}, + "WebHook-Allowed-Rate": []string{"*"}, + "Content-Length": []string{"0"}, + }, + statusCode: nethttp.StatusOK, handler: handler(), reporter: &mockReporter{}, defaulter: broker.TTLDefaulter(logger, 100), }, { - name: "valid (happy path)", - method: nethttp.MethodPost, - uri: "/ns/name", - body: getValidEvent(), + name: "valid (happy path POST)", + method: nethttp.MethodPost, + uri: "/ns/name", + body: getValidEvent(), + expectedHeaders: nethttp.Header{ + "Allow": []string{"PUT, OPTIONS"}, + }, statusCode: senderResponseStatusCode, handler: handler(), reporter: &mockReporter{StatusCode: senderResponseStatusCode, EventDispatchTimeReported: true}, diff --git a/pkg/channel/message_receiver.go b/pkg/channel/message_receiver.go index 897580250bd..7be4aa273fc 100644 --- a/pkg/channel/message_receiver.go +++ b/pkg/channel/message_receiver.go @@ -136,6 +136,13 @@ func (r *MessageReceiver) Start(ctx context.Context) error { } func (r *MessageReceiver) ServeHTTP(response nethttp.ResponseWriter, request *nethttp.Request) { + response.Header().Set("Allow", "POST, OPTIONS") + if request.Method == nethttp.MethodOptions { + response.Header().Set("WebHook-Allowed-Origin", "*") // Accept from any Origin: + response.Header().Set("WebHook-Allowed-Rate", "*") // Unlimited requests/minute + response.WriteHeader(nethttp.StatusOK) + return + } if request.Method != nethttp.MethodPost { response.WriteHeader(nethttp.StatusMethodNotAllowed) return diff --git a/pkg/channel/message_receiver_test.go b/pkg/channel/message_receiver_test.go index 58a586626cd..2c9529001cb 100644 --- a/pkg/channel/message_receiver_test.go +++ b/pkg/channel/message_receiver_test.go @@ -53,6 +53,7 @@ func TestMessageReceiver_ServeHTTP(t *testing.T) { additionalHeaders nethttp.Header expected int receiverFunc UnbufferedMessageReceiverFunc + responseValidator func(r httptest.ResponseRecorder) error }{ "non '/' path": { path: "/something", @@ -121,6 +122,22 @@ func TestMessageReceiver_ServeHTTP(t *testing.T) { }, expected: nethttp.StatusAccepted, }, + "OPTIONS okay": { + method: nethttp.MethodOptions, + host: "test-name.test-namespace.svc." + network.GetClusterDomainName(), + expected: nethttp.StatusOK, + responseValidator: func(res httptest.ResponseRecorder) error { + expectedHeaders := nethttp.Header{ + "Allow": []string{"POST, OPTIONS"}, + "Webhook-Allowed-Origin": []string{"*"}, + "Webhook-Allowed-Rate": []string{"*"}, + } + if diff := cmp.Diff(expectedHeaders, res.Header()); diff != "" { + return fmt.Errorf("test receiver func -- bad OPTION headers (-want, +got): %s", diff) + } + return nil + }, + }, } reporter := NewStatsReporter("testcontainer", "testpod") for n, tc := range testCases { @@ -172,6 +189,11 @@ func TestMessageReceiver_ServeHTTP(t *testing.T) { if res.Code != tc.expected { t.Fatalf("Unexpected status code. Expected %v. Actual %v", tc.expected, res.Code) } + if tc.responseValidator != nil { + if err := tc.responseValidator(res); err != nil { + t.Errorf("Incorrect response: %v", err) + } + } }) } } diff --git a/test/conformance/helpers/broker_data_plane_test_helper.go b/test/conformance/helpers/broker_data_plane_test_helper.go index a88d73a8c95..178e6b26874 100644 --- a/test/conformance/helpers/broker_data_plane_test_helper.go +++ b/test/conformance/helpers/broker_data_plane_test_helper.go @@ -84,7 +84,7 @@ func BrokerDataPlaneNamespaceSetupOption(ctx context.Context, namespace string) //Supports structured or Binary mode //Respond with 2xx on good CE //Respond with 400 on bad CE -//Reject non-POST requests to publish URI +//Reject non-POST, non-OPTIONS requests to publish URI (beyond spec?!) func BrokerIngressDataPlaneTestHelper( ctx context.Context, t *testing.T, @@ -108,6 +108,22 @@ func BrokerIngressDataPlaneTestHelper( resources.WithSubscriberServiceRefForTrigger(loggerName), ) + st.Run("Ingress supports OPTIONS", func(t *testing.T) { + // TODO(evana): check "Allow" header and other options. + // This may be simpler if the setup requires a local HTTP proxy which can reach into the cluster. + method := sender.WithMethod("OPTIONS") + responseSink := "http://" + client.GetServiceHost(loggerName) + responseForward := sender.WithResponseSink(responseSink) + senderName := "options-test-sender" + client.SendRequestToAddressable(ctx, senderName, broker.Name, testlib.BrokerTypeMeta, map[string]string{}, "", method, responseForward) + client.WaitForResourceReadyOrFail(senderName, &podMeta) + eventTracker.AssertAtLeast(1, recordevents.MatchEvent(cetest.AllOf( + cetest.AnyOf( + sender.MatchStatusCode(200), sender.MatchStatusCode(405)), + cetest.HasSource("https://knative.dev/eventing/test/event-sender"), + ))) + }) + client.WaitForResourceReadyOrFail(trigger.Name, testlib.TriggerTypeMeta) st.Run("Ingress Supports CE0.3", func(t *testing.T) { eventID := "CE0.3"