...

Source file src/github.com/emicklei/go-restful/v3/curly.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  	"net/http"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  )
    13  
    14  // CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
    15  type CurlyRouter struct{}
    16  
    17  // SelectRoute is part of the Router interface and returns the best match
    18  // for the WebService and its Route for the given Request.
    19  func (c CurlyRouter) SelectRoute(
    20  	webServices []*WebService,
    21  	httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
    22  
    23  	requestTokens := tokenizePath(httpRequest.URL.Path)
    24  
    25  	detectedService := c.detectWebService(requestTokens, webServices)
    26  	if detectedService == nil {
    27  		if trace {
    28  			traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
    29  		}
    30  		return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
    31  	}
    32  	candidateRoutes := c.selectRoutes(detectedService, requestTokens)
    33  	if len(candidateRoutes) == 0 {
    34  		if trace {
    35  			traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
    36  		}
    37  		return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
    38  	}
    39  	selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
    40  	if selectedRoute == nil {
    41  		return detectedService, nil, err
    42  	}
    43  	return detectedService, selectedRoute, nil
    44  }
    45  
    46  // selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
    47  func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
    48  	candidates := make(sortableCurlyRoutes, 0, 8)
    49  	for _, each := range ws.routes {
    50  		matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
    51  		if matches {
    52  			candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
    53  		}
    54  	}
    55  	sort.Sort(candidates)
    56  	return candidates
    57  }
    58  
    59  // matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
    60  func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string, routeHasCustomVerb bool) (matches bool, paramCount int, staticCount int) {
    61  	if len(routeTokens) < len(requestTokens) {
    62  		// proceed in matching only if last routeToken is wildcard
    63  		count := len(routeTokens)
    64  		if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
    65  			return false, 0, 0
    66  		}
    67  		// proceed
    68  	}
    69  	for i, routeToken := range routeTokens {
    70  		if i == len(requestTokens) {
    71  			// reached end of request path
    72  			return false, 0, 0
    73  		}
    74  		requestToken := requestTokens[i]
    75  		if routeHasCustomVerb && hasCustomVerb(routeToken){
    76  			if !isMatchCustomVerb(routeToken, requestToken) {
    77  				return false, 0, 0
    78  			}
    79  			staticCount++
    80  			requestToken = removeCustomVerb(requestToken)
    81  			routeToken = removeCustomVerb(routeToken)
    82  		}
    83  
    84  		if strings.HasPrefix(routeToken, "{") {
    85  			paramCount++
    86  			if colon := strings.Index(routeToken, ":"); colon != -1 {
    87  				// match by regex
    88  				matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
    89  				if !matchesToken {
    90  					return false, 0, 0
    91  				}
    92  				if matchesRemainder {
    93  					break
    94  				}
    95  			}
    96  		} else { // no { prefix
    97  			if requestToken != routeToken {
    98  				return false, 0, 0
    99  			}
   100  			staticCount++
   101  		}
   102  	}
   103  	return true, paramCount, staticCount
   104  }
   105  
   106  // regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
   107  // format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
   108  func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
   109  	regPart := routeToken[colon+1 : len(routeToken)-1]
   110  	if regPart == "*" {
   111  		if trace {
   112  			traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
   113  		}
   114  		return true, true
   115  	}
   116  	matched, err := regexp.MatchString(regPart, requestToken)
   117  	return (matched && err == nil), false
   118  }
   119  
   120  var jsr311Router = RouterJSR311{}
   121  
   122  // detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
   123  // headers of the Request. See also RouterJSR311 in jsr311.go
   124  func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
   125  	// tracing is done inside detectRoute
   126  	return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
   127  }
   128  
   129  // detectWebService returns the best matching webService given the list of path tokens.
   130  // see also computeWebserviceScore
   131  func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
   132  	var best *WebService
   133  	score := -1
   134  	for _, each := range webServices {
   135  		matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
   136  		if matches && (eachScore > score) {
   137  			best = each
   138  			score = eachScore
   139  		}
   140  	}
   141  	return best
   142  }
   143  
   144  // computeWebserviceScore returns whether tokens match and
   145  // the weighted score of the longest matching consecutive tokens from the beginning.
   146  func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
   147  	if len(tokens) > len(requestTokens) {
   148  		return false, 0
   149  	}
   150  	score := 0
   151  	for i := 0; i < len(tokens); i++ {
   152  		each := requestTokens[i]
   153  		other := tokens[i]
   154  		if len(each) == 0 && len(other) == 0 {
   155  			score++
   156  			continue
   157  		}
   158  		if len(other) > 0 && strings.HasPrefix(other, "{") {
   159  			// no empty match
   160  			if len(each) == 0 {
   161  				return false, score
   162  			}
   163  			score += 1
   164  		} else {
   165  			// not a parameter
   166  			if each != other {
   167  				return false, score
   168  			}
   169  			score += (len(tokens) - i) * 10 //fuzzy
   170  		}
   171  	}
   172  	return true, score
   173  }
   174  

View as plain text