Skip to content

Commit

Permalink
router: add GlobalOPTIONS handler
Browse files Browse the repository at this point in the history
Closes #200, #214, #260
Updates #156
  • Loading branch information
julienschmidt committed Sep 29, 2019
1 parent aff381b commit b390476
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ Alternatively, one can also use `params := r.Context().Value(httprouter.ParamsKe

Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.

## Automatic OPTIONS responses and CORS

One might wish to modify automatic responses to OPTIONS requests, e.g. to support [CORS preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/preflight_request) or to set other headers.
This can be achieved using the [`Router.GlobalOPTIONS`](https://godoc.org/github.com/julienschmidt/httprouter#Router.GlobalOPTIONS) handler:

```go
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Access-Control-Request-Method") != "" {
// Set CORS headers
header := w.Header()
header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
header.Set("Access-Control-Allow-Origin", "*")
}

// Adjust status code to 204
w.WriteHeader(http.StatusNoContent)
})
```

## Where can I find Middleware *X*?

This package just provides a very efficient request router with a few extra features. The router is just a [`http.Handler`](https://golang.org/pkg/net/http/#Handler), you can chain any http.Handler compatible middleware before the router, for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](https://justinas.org/writing-http-middleware-in-go/), it's very easy!
Expand Down
9 changes: 9 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ type Router struct {
// Custom OPTIONS handlers take priority over automatic replies.
HandleOPTIONS bool

// An optional http.Handler that is called on automatic OPTIONS requests.
// The handler is only called if HandleOPTIONS is true and no OPTIONS
// handler for the specific path was set.
// The "Allowed" header is set before calling the handler.
GlobalOPTIONS http.Handler

// Cached value of global (*) allowed methods
globalAllowed string

Expand Down Expand Up @@ -417,6 +423,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Handle OPTIONS requests
if allow := r.allowed(path, http.MethodOptions); len(allow) > 0 {
w.Header().Set("Allow", allow)
if r.GlobalOPTIONS != nil {
r.GlobalOPTIONS.ServeHTTP(w, req)
}
return
}
} else {
Expand Down
12 changes: 9 additions & 3 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,18 @@ func TestRouterOPTIONS(t *testing.T) {
// add another method
router.GET("/path", handlerFunc)

// set a global OPTIONS handler
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Adjust status code to 204
w.WriteHeader(http.StatusNoContent)
})

// test again
// * (server)
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
Expand All @@ -289,7 +295,7 @@ func TestRouterOPTIONS(t *testing.T) {
r, _ = http.NewRequest(http.MethodOptions, "/path", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
Expand All @@ -306,7 +312,7 @@ func TestRouterOPTIONS(t *testing.T) {
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
Expand Down

0 comments on commit b390476

Please sign in to comment.