gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/router.go (about)

     1  package ign
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"github.com/codegangsta/negroni"
     7  	"github.com/gorilla/mux"
     8  	"gitlab.com/ignitionrobotics/web/ign-go/monitoring"
     9  	"net/http"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  // NewRouter just creates a new Gorilla/mux router
    16  func NewRouter() *mux.Router {
    17  	// We need to set StrictSlash to "false" (default) to avoid getting
    18  	// routes redirected automatically.
    19  	return mux.NewRouter().StrictSlash(false)
    20  }
    21  
    22  // RouterConfigurer is used to configure a mux.Router with declared routes
    23  // and middlewares. It also adds support for default global OPTIONS handler
    24  // based on the route declarations.
    25  type RouterConfigurer struct {
    26  	// Embedded type mux.Router
    27  	// See https://golang.org/doc/effective_go.html#embedding
    28  	*mux.Router
    29  	// An optional list of middlewares to be injected between the common
    30  	// middlewares and the final route handler.
    31  	customHandlers []negroni.Handler
    32  
    33  	corsMap map[string]int
    34  	// sortedREs keeps a sorted list of registered routes in corsMap.
    35  	// It allows us to iterate the corsMap in 'order'.
    36  	sortedREs []string
    37  	// declared Routes
    38  	routes *Routes
    39  	// private field to keep a reference to JWT middlewares
    40  	authOptMiddleware negroni.HandlerFunc
    41  	authReqMiddleware negroni.HandlerFunc
    42  	// monitoring provides a middleware to keep track of server request metrics.
    43  	monitoring monitoring.Provider
    44  }
    45  
    46  // NewRouterConfigurer creates a new RouterConfigurer, used to
    47  // configure a mux.Router with routes declarations.
    48  func NewRouterConfigurer(r *mux.Router, monitoring monitoring.Provider) *RouterConfigurer {
    49  	rc := &RouterConfigurer{
    50  		Router:     r,
    51  		corsMap:    make(map[string]int, 0),
    52  		monitoring: monitoring,
    53  	}
    54  	return rc
    55  }
    56  
    57  // SetCustomHandlers - allows to set a list of optional middlewares
    58  // that will be injected between the common middlewares and the final route handler.
    59  func (rc *RouterConfigurer) SetCustomHandlers(handlers ...negroni.Handler) *RouterConfigurer {
    60  	rc.customHandlers = handlers
    61  	return rc
    62  }
    63  
    64  // SetAuthHandlers - set the JWT handlers to be used by the router for secure and unsecure
    65  // routes.
    66  func (rc *RouterConfigurer) SetAuthHandlers(optionalJWT, requiredJWT negroni.HandlerFunc) *RouterConfigurer {
    67  	rc.authOptMiddleware = optionalJWT
    68  	rc.authReqMiddleware = requiredJWT
    69  	return rc
    70  }
    71  
    72  // ConfigureRouter - given an array of Route declarations,
    73  // this method confifures the router to handle them.
    74  // This is the main method to invoke with a RouterConfigurer.
    75  //
    76  // It supports an optional pathPrefix used to differentiate API versions (eg. "/2.0/").
    77  func (rc *RouterConfigurer) ConfigureRouter(pathPrefix string, routes Routes) *RouterConfigurer {
    78  	// Store the route declarations in the router.
    79  	rc.routes = &routes
    80  
    81  	for routeIndex, route := range routes {
    82  
    83  		// Process unsecure routes
    84  		for _, method := range route.Methods {
    85  			for _, formatHandler := range method.Handlers {
    86  				rc.registerRouteHandler(routeIndex, method.Type, false, formatHandler)
    87  				rc.registerRouteInOptionsHandler(pathPrefix, routeIndex, formatHandler)
    88  			}
    89  		}
    90  
    91  		// Process secure routes
    92  		for _, method := range route.SecureMethods {
    93  			for _, formatHandler := range method.Handlers {
    94  				rc.registerRouteHandler(routeIndex, method.Type, true, formatHandler)
    95  				rc.registerRouteInOptionsHandler(pathPrefix, routeIndex, formatHandler)
    96  			}
    97  		}
    98  	}
    99  
   100  	// Sorting corsMap is needed to correctly resolve OPTION requests
   101  	// that need to match a regex.
   102  	rc.sortedREs = getSortedREs(rc.corsMap)
   103  
   104  	return rc
   105  }
   106  
   107  /////////////////////////////////////////////////
   108  
   109  // Internal method that registers the route (with its format)
   110  // into the router's corsMap, for later use by the OPTIONS handler.
   111  func (rc *RouterConfigurer) registerRouteInOptionsHandler(pathPrefix string,
   112  	routeIndex int, formatHandler FormatHandler) {
   113  	route := (*rc.routes)[routeIndex]
   114  	// Setup a helper regex for "{_text_}" URL parameters.
   115  	re := regexp.MustCompile("{[^}]+?}")
   116  	namedVarRE := regexp.MustCompile("{[^}]+:[^{]+}")
   117  	// Register the route in the corsMap. Used by the global OPTIONS handler
   118  	uriPath := route.URI + formatHandler.Extension
   119  	prefixedURIPath := strings.Replace(pathPrefix+uriPath, "//", "/", -1)
   120  	// Store route information for the global OPTIONS handler
   121  	newStr := strings.Replace(prefixedURIPath, ".", "\\.", -1)
   122  	reString := namedVarRE.ReplaceAllString(newStr, ".+")
   123  	reString = re.ReplaceAllString(reString, "[^/]+")
   124  	rc.corsMap[reString] = routeIndex
   125  
   126  	rc.
   127  		Methods("OPTIONS").
   128  		Path(uriPath).
   129  		Name(route.Name + formatHandler.Extension).
   130  		Handler(http.HandlerFunc(rc.globalOptionsHandler))
   131  }
   132  
   133  // Helper function that registers the given route handler AND
   134  // automatically creates and registers an HTTP OPTIONS method handler on the route.
   135  //
   136  // formatHandler is the given most route handler.
   137  func (rc *RouterConfigurer) registerRouteHandler(routeIndex int, methodType string,
   138  	secure bool, formatHandler FormatHandler) {
   139  
   140  	handler := formatHandler.Handler
   141  
   142  	// TODO move to top chain middlewares
   143  
   144  	// Configure auth middleware
   145  	var authMiddleware negroni.HandlerFunc
   146  	if !secure {
   147  		authMiddleware = rc.authOptMiddleware
   148  	} else {
   149  		authMiddleware = rc.authReqMiddleware
   150  	}
   151  
   152  	routeName := (*rc.routes)[routeIndex].Name
   153  
   154  	recovery := negroni.NewRecovery()
   155  	// PrintStack is set to false to avoid sending stacktrace to client.
   156  	recovery.PrintStack = false
   157  
   158  	// Configure middleware chain
   159  	middlewares := negroni.New()
   160  	// Add monitoring middleware if monitoring provider is present.
   161  	// It must be the first middleware in the chain
   162  	if rc.monitoring != nil {
   163  		middlewares = middlewares.With(rc.monitoring.Middleware())
   164  	}
   165  	// Add default middlewares
   166  	middlewares = middlewares.With(
   167  		recovery,
   168  		negroni.HandlerFunc(requireDBMiddleware),
   169  		negroni.HandlerFunc(addCORSheadersMiddleware),
   170  		authMiddleware,
   171  		negroni.HandlerFunc(newGaEventTracking(routeName)),
   172  	)
   173  	// Inject custom handlers just before the final handler
   174  	middlewares = middlewares.With(rc.customHandlers...)
   175  	middlewares.Use(negroni.Wrap(http.Handler(handler)))
   176  
   177  	// Last, wrap everything with a Logger middleware
   178  	handler = logger(middlewares, routeName)
   179  
   180  	uriPath := (*rc.routes)[routeIndex].URI + formatHandler.Extension
   181  
   182  	// Create the route handler.
   183  	rc.
   184  		Methods(methodType).
   185  		Path(uriPath).
   186  		Name(routeName + formatHandler.Extension).
   187  		Handler(handler)
   188  }
   189  
   190  // globalOptionsHandler is an OPTIONS method handler that will return
   191  // documentation of a route based on its Route definition.
   192  func (rc *RouterConfigurer) globalOptionsHandler(w http.ResponseWriter, r *http.Request) {
   193  	index := 0
   194  	ok := false
   195  	// Find the matching URL
   196  	for _, key := range rc.sortedREs {
   197  		// Make sure the regular expression matches the complete URL path
   198  		if regexp.MustCompile(key).FindString(r.URL.Path) == r.URL.Path {
   199  			ok = true
   200  			index = rc.corsMap[key]
   201  			break
   202  		}
   203  	}
   204  	route := (*rc.routes)[index]
   205  	if ok {
   206  		if output, e := json.Marshal(route); e != nil {
   207  			err := NewErrorMessageWithBase(ErrorMarshalJSON, e)
   208  			reportJSONError(w, r, *err)
   209  		} else {
   210  			// Find all the route supported HTTP Methods
   211  			allow := make([]string, 0)
   212  			for _, m := range route.Methods {
   213  				allow = append(allow, m.Type)
   214  			}
   215  			for _, m := range route.SecureMethods {
   216  				allow = append(allow, m.Type)
   217  			}
   218  			w.Header().Set("Allow", strings.Join(allow[:], ","))
   219  			w.Header().Set("Content-Type", "application/json")
   220  			addCORSheaders(w)
   221  			fmt.Fprintln(w, string(output))
   222  		}
   223  		return
   224  	}
   225  
   226  	// Return error if a URL did not match
   227  	err := ErrorMessage(ErrorNameNotFound)
   228  	reportJSONError(w, r, err)
   229  }
   230  
   231  /////////////////////////////////////////////////
   232  // sortRE is an internal []string wrapper type used to sort by
   233  // the number of "[^/]+" string occurrences found in a regex (ie. count).
   234  // If the same count is found then the larger string will take precedence.
   235  type sortRE []string
   236  
   237  func (s sortRE) Len() int {
   238  	return len(s)
   239  }
   240  
   241  func (s sortRE) Swap(i, j int) {
   242  	s[i], s[j] = s[j], s[i]
   243  }
   244  
   245  func (s sortRE) Less(i, j int) bool {
   246  	ci := strings.Count(s[i], "[^/]+")
   247  	cj := strings.Count(s[j], "[^/]+")
   248  	if ci == cj {
   249  		return len(s[i]) > len(s[j])
   250  	}
   251  	return ci < cj
   252  }
   253  
   254  func getSortedREs(m map[string]int) []string {
   255  	var keys []string
   256  	for k := range m {
   257  		keys = append(keys, k)
   258  	}
   259  	sort.Sort(sortRE(keys))
   260  	return keys
   261  }