...

Source file src/github.com/emicklei/go-restful/v3/jsr311.go

Documentation: github.com/emicklei/go-restful/v3

     1  package restful
     2  
     3  // Copyright 2013 Ernest Micklei. All rights reserved.
     4  // Use of this source code is governed by a license
     5  // that can be found in the LICENSE file.
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  // RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
    16  // as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
    17  // RouterJSR311 implements the Router interface.
    18  // Concept of locators is not implemented.
    19  type RouterJSR311 struct{}
    20  
    21  // SelectRoute is part of the Router interface and returns the best match
    22  // for the WebService and its Route for the given Request.
    23  func (r RouterJSR311) SelectRoute(
    24  	webServices []*WebService,
    25  	httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
    26  
    27  	// Identify the root resource class (WebService)
    28  	dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
    29  	if err != nil {
    30  		return nil, nil, NewError(http.StatusNotFound, "")
    31  	}
    32  	// Obtain the set of candidate methods (Routes)
    33  	routes := r.selectRoutes(dispatcher, finalMatch)
    34  	if len(routes) == 0 {
    35  		return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
    36  	}
    37  
    38  	// Identify the method (Route) that will handle the request
    39  	route, ok := r.detectRoute(routes, httpRequest)
    40  	return dispatcher, route, ok
    41  }
    42  
    43  // ExtractParameters is used to obtain the path parameters from the route using the same matching
    44  // engine as the JSR 311 router.
    45  func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
    46  	webServiceExpr := webService.pathExpr
    47  	webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
    48  	pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
    49  	routeExpr := route.pathExpr
    50  	routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
    51  	routeParams := r.extractParams(routeExpr, routeMatches)
    52  	for key, value := range routeParams {
    53  		pathParameters[key] = value
    54  	}
    55  	return pathParameters
    56  }
    57  
    58  func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
    59  	params := map[string]string{}
    60  	for i := 1; i < len(matches); i++ {
    61  		if len(pathExpr.VarNames) >= i {
    62  			params[pathExpr.VarNames[i-1]] = matches[i]
    63  		}
    64  	}
    65  	return params
    66  }
    67  
    68  // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
    69  func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
    70  	candidates := make([]*Route, 0, 8)
    71  	for i, each := range routes {
    72  		ok := true
    73  		for _, fn := range each.If {
    74  			if !fn(httpRequest) {
    75  				ok = false
    76  				break
    77  			}
    78  		}
    79  		if ok {
    80  			candidates = append(candidates, &routes[i])
    81  		}
    82  	}
    83  	if len(candidates) == 0 {
    84  		if trace {
    85  			traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
    86  		}
    87  		return nil, NewError(http.StatusNotFound, "404: Not Found")
    88  	}
    89  
    90  	// http method
    91  	previous := candidates
    92  	candidates = candidates[:0]
    93  	for _, each := range previous {
    94  		if httpRequest.Method == each.Method {
    95  			candidates = append(candidates, each)
    96  		}
    97  	}
    98  	if len(candidates) == 0 {
    99  		if trace {
   100  			traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
   101  		}
   102  		allowed := []string{}
   103  	allowedLoop:
   104  		for _, candidate := range previous {
   105  			for _, method := range allowed {
   106  				if method == candidate.Method {
   107  					continue allowedLoop
   108  				}
   109  			}
   110  			allowed = append(allowed, candidate.Method)
   111  		}
   112  		header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
   113  		return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
   114  	}
   115  
   116  	// content-type
   117  	contentType := httpRequest.Header.Get(HEADER_ContentType)
   118  	previous = candidates
   119  	candidates = candidates[:0]
   120  	for _, each := range previous {
   121  		if each.matchesContentType(contentType) {
   122  			candidates = append(candidates, each)
   123  		}
   124  	}
   125  	if len(candidates) == 0 {
   126  		if trace {
   127  			traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
   128  		}
   129  		if httpRequest.ContentLength > 0 {
   130  			return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
   131  		}
   132  	}
   133  
   134  	// accept
   135  	previous = candidates
   136  	candidates = candidates[:0]
   137  	accept := httpRequest.Header.Get(HEADER_Accept)
   138  	if len(accept) == 0 {
   139  		accept = "*/*"
   140  	}
   141  	for _, each := range previous {
   142  		if each.matchesAccept(accept) {
   143  			candidates = append(candidates, each)
   144  		}
   145  	}
   146  	if len(candidates) == 0 {
   147  		if trace {
   148  			traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
   149  		}
   150  		available := []string{}
   151  		for _, candidate := range previous {
   152  			available = append(available, candidate.Produces...)
   153  		}
   154  		// if POST,PUT,PATCH without body
   155  		method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length")
   156  		if (method == http.MethodPost ||
   157  			method == http.MethodPut ||
   158  			method == http.MethodPatch) && (length == "" || length == "0") {
   159  			return nil, NewError(
   160  				http.StatusUnsupportedMediaType,
   161  				fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")),
   162  			)
   163  		}
   164  		return nil, NewError(
   165  			http.StatusNotAcceptable,
   166  			fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
   167  		)
   168  	}
   169  	// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
   170  	return candidates[0], nil
   171  }
   172  
   173  // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
   174  // n/m > n/* > */*
   175  func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
   176  	// TODO
   177  	return &routes[0]
   178  }
   179  
   180  // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2  (step 2)
   181  func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
   182  	filtered := &sortableRouteCandidates{}
   183  	for _, each := range dispatcher.Routes() {
   184  		pathExpr := each.pathExpr
   185  		matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
   186  		if matches != nil {
   187  			lastMatch := matches[len(matches)-1]
   188  			if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
   189  				filtered.candidates = append(filtered.candidates,
   190  					routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
   191  			}
   192  		}
   193  	}
   194  	if len(filtered.candidates) == 0 {
   195  		if trace {
   196  			traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
   197  		}
   198  		return []Route{}
   199  	}
   200  	sort.Sort(sort.Reverse(filtered))
   201  
   202  	// select other routes from candidates whoes expression matches rmatch
   203  	matchingRoutes := []Route{filtered.candidates[0].route}
   204  	for c := 1; c < len(filtered.candidates); c++ {
   205  		each := filtered.candidates[c]
   206  		if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
   207  			matchingRoutes = append(matchingRoutes, each.route)
   208  		}
   209  	}
   210  	return matchingRoutes
   211  }
   212  
   213  // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
   214  func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
   215  	filtered := &sortableDispatcherCandidates{}
   216  	for _, each := range dispatchers {
   217  		matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
   218  		if matches != nil {
   219  			filtered.candidates = append(filtered.candidates,
   220  				dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
   221  		}
   222  	}
   223  	if len(filtered.candidates) == 0 {
   224  		if trace {
   225  			traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
   226  		}
   227  		return nil, "", errors.New("not found")
   228  	}
   229  	sort.Sort(sort.Reverse(filtered))
   230  	return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
   231  }
   232  
   233  // Types and functions to support the sorting of Routes
   234  
   235  type routeCandidate struct {
   236  	route           Route
   237  	matchesCount    int // the number of capturing groups
   238  	literalCount    int // the number of literal characters (means those not resulting from template variable substitution)
   239  	nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^  /]+?)’)
   240  }
   241  
   242  func (r routeCandidate) expressionToMatch() string {
   243  	return r.route.pathExpr.Source
   244  }
   245  
   246  func (r routeCandidate) String() string {
   247  	return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
   248  }
   249  
   250  type sortableRouteCandidates struct {
   251  	candidates []routeCandidate
   252  }
   253  
   254  func (rcs *sortableRouteCandidates) Len() int {
   255  	return len(rcs.candidates)
   256  }
   257  func (rcs *sortableRouteCandidates) Swap(i, j int) {
   258  	rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
   259  }
   260  func (rcs *sortableRouteCandidates) Less(i, j int) bool {
   261  	ci := rcs.candidates[i]
   262  	cj := rcs.candidates[j]
   263  	// primary key
   264  	if ci.literalCount < cj.literalCount {
   265  		return true
   266  	}
   267  	if ci.literalCount > cj.literalCount {
   268  		return false
   269  	}
   270  	// secundary key
   271  	if ci.matchesCount < cj.matchesCount {
   272  		return true
   273  	}
   274  	if ci.matchesCount > cj.matchesCount {
   275  		return false
   276  	}
   277  	// tertiary key
   278  	if ci.nonDefaultCount < cj.nonDefaultCount {
   279  		return true
   280  	}
   281  	if ci.nonDefaultCount > cj.nonDefaultCount {
   282  		return false
   283  	}
   284  	// quaternary key ("source" is interpreted as Path)
   285  	return ci.route.Path < cj.route.Path
   286  }
   287  
   288  // Types and functions to support the sorting of Dispatchers
   289  
   290  type dispatcherCandidate struct {
   291  	dispatcher      *WebService
   292  	finalMatch      string
   293  	matchesCount    int // the number of capturing groups
   294  	literalCount    int // the number of literal characters (means those not resulting from template variable substitution)
   295  	nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^  /]+?)’)
   296  }
   297  type sortableDispatcherCandidates struct {
   298  	candidates []dispatcherCandidate
   299  }
   300  
   301  func (dc *sortableDispatcherCandidates) Len() int {
   302  	return len(dc.candidates)
   303  }
   304  func (dc *sortableDispatcherCandidates) Swap(i, j int) {
   305  	dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
   306  }
   307  func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
   308  	ci := dc.candidates[i]
   309  	cj := dc.candidates[j]
   310  	// primary key
   311  	if ci.matchesCount < cj.matchesCount {
   312  		return true
   313  	}
   314  	if ci.matchesCount > cj.matchesCount {
   315  		return false
   316  	}
   317  	// secundary key
   318  	if ci.literalCount < cj.literalCount {
   319  		return true
   320  	}
   321  	if ci.literalCount > cj.literalCount {
   322  		return false
   323  	}
   324  	// tertiary key
   325  	return ci.nonDefaultCount < cj.nonDefaultCount
   326  }
   327  

View as plain text