Skip to content

Commit b390476

Browse files
committed
router: add GlobalOPTIONS handler
Closes #200, #214, #260 Updates #156
1 parent aff381b commit b390476

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,25 @@ Alternatively, one can also use `params := r.Context().Value(httprouter.ParamsKe
146146

147147
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.
148148

149+
## Automatic OPTIONS responses and CORS
150+
151+
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.
152+
This can be achieved using the [`Router.GlobalOPTIONS`](https://godoc.org/github.com/julienschmidt/httprouter#Router.GlobalOPTIONS) handler:
153+
154+
```go
155+
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
156+
if r.Header.Get("Access-Control-Request-Method") != "" {
157+
// Set CORS headers
158+
header := w.Header()
159+
header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
160+
header.Set("Access-Control-Allow-Origin", "*")
161+
}
162+
163+
// Adjust status code to 204
164+
w.WriteHeader(http.StatusNoContent)
165+
})
166+
```
167+
149168
## Where can I find Middleware *X*?
150169

151170
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!

router.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ type Router struct {
156156
// Custom OPTIONS handlers take priority over automatic replies.
157157
HandleOPTIONS bool
158158

159+
// An optional http.Handler that is called on automatic OPTIONS requests.
160+
// The handler is only called if HandleOPTIONS is true and no OPTIONS
161+
// handler for the specific path was set.
162+
// The "Allowed" header is set before calling the handler.
163+
GlobalOPTIONS http.Handler
164+
159165
// Cached value of global (*) allowed methods
160166
globalAllowed string
161167

@@ -417,6 +423,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
417423
// Handle OPTIONS requests
418424
if allow := r.allowed(path, http.MethodOptions); len(allow) > 0 {
419425
w.Header().Set("Allow", allow)
426+
if r.GlobalOPTIONS != nil {
427+
r.GlobalOPTIONS.ServeHTTP(w, req)
428+
}
420429
return
421430
}
422431
} else {

router_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,18 @@ func TestRouterOPTIONS(t *testing.T) {
274274
// add another method
275275
router.GET("/path", handlerFunc)
276276

277+
// set a global OPTIONS handler
278+
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
279+
// Adjust status code to 204
280+
w.WriteHeader(http.StatusNoContent)
281+
})
282+
277283
// test again
278284
// * (server)
279285
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
280286
w = httptest.NewRecorder()
281287
router.ServeHTTP(w, r)
282-
if !(w.Code == http.StatusOK) {
288+
if !(w.Code == http.StatusNoContent) {
283289
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
284290
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
285291
t.Error("unexpected Allow header value: " + allow)
@@ -289,7 +295,7 @@ func TestRouterOPTIONS(t *testing.T) {
289295
r, _ = http.NewRequest(http.MethodOptions, "/path", nil)
290296
w = httptest.NewRecorder()
291297
router.ServeHTTP(w, r)
292-
if !(w.Code == http.StatusOK) {
298+
if !(w.Code == http.StatusNoContent) {
293299
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
294300
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
295301
t.Error("unexpected Allow header value: " + allow)
@@ -306,7 +312,7 @@ func TestRouterOPTIONS(t *testing.T) {
306312
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
307313
w = httptest.NewRecorder()
308314
router.ServeHTTP(w, r)
309-
if !(w.Code == http.StatusOK) {
315+
if !(w.Code == http.StatusNoContent) {
310316
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
311317
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
312318
t.Error("unexpected Allow header value: " + allow)

0 commit comments

Comments
 (0)