Skip to content

Commit

Permalink
Option to specify preflight response status code (#121)
Browse files Browse the repository at this point in the history
* feat: Add ability to specify response status code for pre-flight requests
* test: Cover OptionsSuccessStatus options in unit tests
* docs: Update README file to include OptionsSuccessStatus setting option
  • Loading branch information
Neurostep committed Nov 18, 2021
1 parent 64821dd commit 27c7ce4
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -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.
Expand Down
22 changes: 18 additions & 4 deletions cors.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -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")
Expand Down
50 changes: 50 additions & 0 deletions cors_test.go
Expand Up @@ -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)
})
}

0 comments on commit 27c7ce4

Please sign in to comment.