diff --git a/README.md b/README.md index ecc83b2..8f8d81a 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ handler = c.Handler(handler) * **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. * **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. * **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`. +* **OptionsSuccessStatus** `int`: Provides a status code to use for successful OPTIONS requests. Default value is `http.StatusNoContent` (`204`). * **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues. See [API documentation](http://godoc.org/github.com/rs/cors) for more info. diff --git a/cors.go b/cors.go index 41d5f7c..2ce24e3 100644 --- a/cors.go +++ b/cors.go @@ -65,6 +65,9 @@ type Options struct { // OptionsPassthrough instructs preflight to let other potential next handlers to // process the OPTIONS method. Turn this on if your application handles OPTIONS. OptionsPassthrough bool + // Provides a status code to use for successful OPTIONS requests. + // Default value is http.StatusNoContent (204). + OptionsSuccessStatus int // Debugging flag adds additional output to debug server side CORS issues Debug bool } @@ -97,8 +100,10 @@ type Cors struct { allowedOriginsAll bool // Set to true when allowed headers contains a "*" allowedHeadersAll bool - allowCredentials bool - optionPassthrough bool + // Status code to use for successful OPTIONS requests + optionsSuccessStatus int + allowCredentials bool + optionPassthrough bool } // New creates a new Cors handler with the provided options. @@ -171,6 +176,13 @@ func New(options Options) *Cors { c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) } + // Options Success Status Code + if options.OptionsSuccessStatus == 0 { + c.optionsSuccessStatus = http.StatusNoContent + } else { + c.optionsSuccessStatus = options.OptionsSuccessStatus + } + return c } @@ -211,7 +223,7 @@ func (c *Cors) Handler(h http.Handler) http.Handler { if c.optionPassthrough { h.ServeHTTP(w, r) } else { - w.WriteHeader(http.StatusNoContent) + w.WriteHeader(c.optionsSuccessStatus) } } else { c.logf("Handler: Actual request") @@ -226,6 +238,8 @@ func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { c.logf("HandlerFunc: Preflight request") c.handlePreflight(w, r) + + w.WriteHeader(c.optionsSuccessStatus) } else { c.logf("HandlerFunc: Actual request") c.handleActualRequest(w, r) @@ -244,7 +258,7 @@ func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Handl if c.optionPassthrough { next(w, r) } else { - w.WriteHeader(http.StatusNoContent) + w.WriteHeader(c.optionsSuccessStatus) } } else { c.logf("ServeHTTP: Actual request") diff --git a/cors_test.go b/cors_test.go index 7f9898e..b4d6d5a 100644 --- a/cors_test.go +++ b/cors_test.go @@ -558,3 +558,53 @@ func TestIsMethodAllowedReturnsTrueWithOptions(t *testing.T) { t.Error("IsMethodAllowed should return true when c.allowedMethods is nil.") } } + +func TestOptionsSuccessStatusCodeDefault(t *testing.T) { + s := New(Options{ + // Intentionally left blank. + }) + + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Access-Control-Request-Method", "GET") + + t.Run("Handler", func(t *testing.T) { + res := httptest.NewRecorder() + s.Handler(testHandler).ServeHTTP(res, req) + assertResponse(t, res, http.StatusNoContent) + }) + t.Run("HandlerFunc", func(t *testing.T) { + res := httptest.NewRecorder() + s.HandlerFunc(res, req) + assertResponse(t, res, http.StatusNoContent) + }) + t.Run("Negroni", func(t *testing.T) { + res := httptest.NewRecorder() + s.ServeHTTP(res, req, testHandler) + assertResponse(t, res, http.StatusNoContent) + }) +} + +func TestOptionsSuccessStatusCodeOverride(t *testing.T) { + s := New(Options{ + OptionsSuccessStatus: http.StatusOK, + }) + + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Access-Control-Request-Method", "GET") + + t.Run("Handler", func(t *testing.T) { + res := httptest.NewRecorder() + s.Handler(testHandler).ServeHTTP(res, req) + assertResponse(t, res, http.StatusOK) + }) + t.Run("HandlerFunc", func(t *testing.T) { + res := httptest.NewRecorder() + s.HandlerFunc(res, req) + assertResponse(t, res, http.StatusOK) + }) + t.Run("Negroni", func(t *testing.T) { + res := httptest.NewRecorder() + s.ServeHTTP(res, req, testHandler) + assertResponse(t, res, http.StatusOK) + }) +}