Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add default OPTIONS route.

Unless this feature is disabled, a default OPTIONS route is added for
every exiting route.  Also, add a 405 Method Not Available for all the
non existing methods for a given route.

This way, if you define a GET and DELETE for a route, but no POST, PUT
and PATCH, a 405 will be returned to the client if it tries to reach one
of this endpoint.
  • Loading branch information...
commit d17b20b372268e66abb164854b7320a45dc0a526 1 parent b494d7a
@franckcuny authored
Showing with 73 additions and 0 deletions.
  1. +73 −0 router.go
View
73 router.go
@@ -43,7 +43,9 @@ import (
"errors"
"fmt"
"github.com/ant0ine/go-urlrouter/trie"
+ "net/http"
"net/url"
+ "strings"
)
// TODO
@@ -66,6 +68,10 @@ type Router struct {
// list of Routes, the order matters, if multiple Routes match, the first defined will be used.
Routes []Route
disableTrieCompression bool
+ // By default, support for the method OPTIONS is added to all the routes
+ // and add a 405 Method Not Allowed response if the method is not existing
+ // for the path. By setting this option to false, the routes are not added.
+ disableDefaultOptions bool
index map[*Route]int
trie *trie.Trie
routesIndex map[string]bool
@@ -120,6 +126,10 @@ func (self *Router) Start() error {
}
}
+ if self.disableDefaultOptions == false {
+ self.AddNotAllowed()
+ }
+
if self.disableTrieCompression == false {
self.trie.Compress()
}
@@ -130,6 +140,69 @@ func (self *Router) Start() error {
return nil
}
+func (self *Router) addAllowedRoutes(path string, allowed []string) {
+ optionsFunc := func(w http.ResponseWriter, req *http.Request, params map[string]string) {
+ w.Header().Set("Allow", strings.Join(allowed, ", "))
+ w.WriteHeader(204)
+ r := []byte{}
+ w.Write(r)
+ }
+ route := Route{
+ Path: path,
+ HttpMethod: "OPTIONS",
+ Dest: optionsFunc,
+ }
+ self.trie.AddRoute(route.Path, route.HttpMethod, &route)
+
+}
+
+func (self *Router) addDisallowedRoutes(path string, allowed []string, unallowed []string) {
+ unallowedFunc := func(w http.ResponseWriter, req *http.Request, params map[string]string) {
+ w.Header().Set("Allow", strings.Join(allowed, ", "))
+ w.Header().Set("Content-Type", "text/plain")
+ w.WriteHeader(405)
+ r := []byte{}
+ w.Write(r)
+ }
+ for _, m := range unallowed {
+ route := Route{
+ Path: path,
+ HttpMethod: m,
+ Dest: unallowedFunc,
+ }
+ self.trie.AddRoute(route.Path, route.HttpMethod, &route)
+ }
+}
+
+func (self *Router) addOptions(path string, methods map[string]bool) {
+ allowed := []string{}
+ unallowed := []string{}
+
+ for _, dm := range trie.HttpDefaultMethods {
+ if methods[dm] == false {
+ unallowed = append(unallowed, dm)
+ } else {
+ allowed = append(allowed, dm)
+ }
+ }
+ self.addAllowedRoutes(path, allowed)
+ self.addDisallowedRoutes(path, allowed, unallowed)
+}
+
+func (self *Router) AddNotAllowed() {
+ for path, methods := range self.trie.AllRoutes() {
+ // I would like to also add HEAD here, but this means a few changes:
+ // - functions should not write the response, but returns the status, headers and body
+ // - the main handler write the response
+ // - the HEAD function will call the function associated with GET and drop the body,
+ // and write only the HEADERS + status (as per the RFC).
+ // Since this is a bigger change, I'm not going to apply that patch for now.
+ if methods["OPTIONS"] == false {
+ self.addOptions(path, methods)
+ }
+ }
+}
+
// Return the first matching Route and the corresponding parameters for a given URL object.
func (self *Router) FindRouteFromURL(urlObj *url.URL, method string) (*Route, map[string]string) {
Please sign in to comment.
Something went wrong with that request. Please try again.