123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- package restful
- // Copyright 2013 Ernest Micklei. All rights reserved.
- // Use of this source code is governed by a license
- // that can be found in the LICENSE file.
- import (
- "errors"
- "fmt"
- "net/http"
- "sort"
- "strings"
- )
- // RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
- // as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
- // RouterJSR311 implements the Router interface.
- // Concept of locators is not implemented.
- type RouterJSR311 struct{}
- // SelectRoute is part of the Router interface and returns the best match
- // for the WebService and its Route for the given Request.
- func (r RouterJSR311) SelectRoute(
- webServices []*WebService,
- httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
- // Identify the root resource class (WebService)
- dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
- if err != nil {
- return nil, nil, NewError(http.StatusNotFound, "")
- }
- // Obtain the set of candidate methods (Routes)
- routes := r.selectRoutes(dispatcher, finalMatch)
- if len(routes) == 0 {
- return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
- }
- // Identify the method (Route) that will handle the request
- route, ok := r.detectRoute(routes, httpRequest)
- return dispatcher, route, ok
- }
- // ExtractParameters is used to obtain the path parameters from the route using the same matching
- // engine as the JSR 311 router.
- func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
- webServiceExpr := webService.pathExpr
- webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
- pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
- routeExpr := route.pathExpr
- routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
- routeParams := r.extractParams(routeExpr, routeMatches)
- for key, value := range routeParams {
- pathParameters[key] = value
- }
- return pathParameters
- }
- func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
- params := map[string]string{}
- for i := 1; i < len(matches); i++ {
- if len(pathExpr.VarNames) >= i {
- params[pathExpr.VarNames[i-1]] = matches[i]
- }
- }
- return params
- }
- // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
- func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
- candidates := make([]*Route, 0, 8)
- for i, each := range routes {
- ok := true
- for _, fn := range each.If {
- if !fn(httpRequest) {
- ok = false
- break
- }
- }
- if ok {
- candidates = append(candidates, &routes[i])
- }
- }
- if len(candidates) == 0 {
- if trace {
- traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
- }
- return nil, NewError(http.StatusNotFound, "404: Not Found")
- }
- // http method
- previous := candidates
- candidates = candidates[:0]
- for _, each := range previous {
- if httpRequest.Method == each.Method {
- candidates = append(candidates, each)
- }
- }
- if len(candidates) == 0 {
- if trace {
- traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
- }
- allowed := []string{}
- allowedLoop:
- for _, candidate := range previous {
- for _, method := range allowed {
- if method == candidate.Method {
- continue allowedLoop
- }
- }
- allowed = append(allowed, candidate.Method)
- }
- header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
- return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
- }
- // content-type
- contentType := httpRequest.Header.Get(HEADER_ContentType)
- previous = candidates
- candidates = candidates[:0]
- for _, each := range previous {
- if each.matchesContentType(contentType) {
- candidates = append(candidates, each)
- }
- }
- if len(candidates) == 0 {
- if trace {
- traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
- }
- if httpRequest.ContentLength > 0 {
- return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
- }
- }
- // accept
- previous = candidates
- candidates = candidates[:0]
- accept := httpRequest.Header.Get(HEADER_Accept)
- if len(accept) == 0 {
- accept = "*/*"
- }
- for _, each := range previous {
- if each.matchesAccept(accept) {
- candidates = append(candidates, each)
- }
- }
- if len(candidates) == 0 {
- if trace {
- traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
- }
- available := []string{}
- for _, candidate := range previous {
- available = append(available, candidate.Produces...)
- }
- // if POST,PUT,PATCH without body
- method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length")
- if (method == http.MethodPost ||
- method == http.MethodPut ||
- method == http.MethodPatch) && length == "" {
- return nil, NewError(
- http.StatusUnsupportedMediaType,
- fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")),
- )
- }
- return nil, NewError(
- http.StatusNotAcceptable,
- fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
- )
- }
- // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
- return candidates[0], nil
- }
- // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
- // n/m > n/* > */*
- func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
- // TODO
- return &routes[0]
- }
- // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
- func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
- filtered := &sortableRouteCandidates{}
- for _, each := range dispatcher.Routes() {
- pathExpr := each.pathExpr
- matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
- if matches != nil {
- lastMatch := matches[len(matches)-1]
- if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
- filtered.candidates = append(filtered.candidates,
- routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
- }
- }
- }
- if len(filtered.candidates) == 0 {
- if trace {
- traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
- }
- return []Route{}
- }
- sort.Sort(sort.Reverse(filtered))
- // select other routes from candidates whoes expression matches rmatch
- matchingRoutes := []Route{filtered.candidates[0].route}
- for c := 1; c < len(filtered.candidates); c++ {
- each := filtered.candidates[c]
- if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
- matchingRoutes = append(matchingRoutes, each.route)
- }
- }
- return matchingRoutes
- }
- // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
- func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
- filtered := &sortableDispatcherCandidates{}
- for _, each := range dispatchers {
- matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
- if matches != nil {
- filtered.candidates = append(filtered.candidates,
- dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
- }
- }
- if len(filtered.candidates) == 0 {
- if trace {
- traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
- }
- return nil, "", errors.New("not found")
- }
- sort.Sort(sort.Reverse(filtered))
- return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
- }
- // Types and functions to support the sorting of Routes
- type routeCandidate struct {
- route Route
- matchesCount int // the number of capturing groups
- literalCount int // the number of literal characters (means those not resulting from template variable substitution)
- nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
- }
- func (r routeCandidate) expressionToMatch() string {
- return r.route.pathExpr.Source
- }
- func (r routeCandidate) String() string {
- return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
- }
- type sortableRouteCandidates struct {
- candidates []routeCandidate
- }
- func (rcs *sortableRouteCandidates) Len() int {
- return len(rcs.candidates)
- }
- func (rcs *sortableRouteCandidates) Swap(i, j int) {
- rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
- }
- func (rcs *sortableRouteCandidates) Less(i, j int) bool {
- ci := rcs.candidates[i]
- cj := rcs.candidates[j]
- // primary key
- if ci.literalCount < cj.literalCount {
- return true
- }
- if ci.literalCount > cj.literalCount {
- return false
- }
- // secundary key
- if ci.matchesCount < cj.matchesCount {
- return true
- }
- if ci.matchesCount > cj.matchesCount {
- return false
- }
- // tertiary key
- if ci.nonDefaultCount < cj.nonDefaultCount {
- return true
- }
- if ci.nonDefaultCount > cj.nonDefaultCount {
- return false
- }
- // quaternary key ("source" is interpreted as Path)
- return ci.route.Path < cj.route.Path
- }
- // Types and functions to support the sorting of Dispatchers
- type dispatcherCandidate struct {
- dispatcher *WebService
- finalMatch string
- matchesCount int // the number of capturing groups
- literalCount int // the number of literal characters (means those not resulting from template variable substitution)
- nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
- }
- type sortableDispatcherCandidates struct {
- candidates []dispatcherCandidate
- }
- func (dc *sortableDispatcherCandidates) Len() int {
- return len(dc.candidates)
- }
- func (dc *sortableDispatcherCandidates) Swap(i, j int) {
- dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
- }
- func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
- ci := dc.candidates[i]
- cj := dc.candidates[j]
- // primary key
- if ci.matchesCount < cj.matchesCount {
- return true
- }
- if ci.matchesCount > cj.matchesCount {
- return false
- }
- // secundary key
- if ci.literalCount < cj.literalCount {
- return true
- }
- if ci.literalCount > cj.literalCount {
- return false
- }
- // tertiary key
- return ci.nonDefaultCount < cj.nonDefaultCount
- }
|