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.
14 // CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
15 type CurlyRouter struct{}
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) {
23 requestTokens := tokenizePath(httpRequest.URL.Path)
25 detectedService := c.detectWebService(requestTokens, webServices)
26 if detectedService == nil {
28 traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
30 return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
32 candidateRoutes := c.selectRoutes(detectedService, requestTokens)
33 if len(candidateRoutes) == 0 {
35 traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
37 return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
39 selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
40 if selectedRoute == nil {
41 return detectedService, nil, err
43 return detectedService, selectedRoute, nil
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)
52 candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
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) (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], "*}") {
69 for i, routeToken := range routeTokens {
70 if i == len(requestTokens) {
71 // reached end of request path
74 requestToken := requestTokens[i]
75 if strings.HasPrefix(routeToken, "{") {
77 if colon := strings.Index(routeToken, ":"); colon != -1 {
79 matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
87 } else { // no { prefix
88 if requestToken != routeToken {
94 return true, paramCount, staticCount
97 // regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
98 // format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
99 func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
100 regPart := routeToken[colon+1 : len(routeToken)-1]
103 traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
107 matched, err := regexp.MatchString(regPart, requestToken)
108 return (matched && err == nil), false
111 var jsr311Router = RouterJSR311{}
113 // detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
114 // headers of the Request. See also RouterJSR311 in jsr311.go
115 func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
116 // tracing is done inside detectRoute
117 return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
120 // detectWebService returns the best matching webService given the list of path tokens.
121 // see also computeWebserviceScore
122 func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
125 for _, each := range webServices {
126 matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
127 if matches && (eachScore > score) {
135 // computeWebserviceScore returns whether tokens match and
136 // the weighted score of the longest matching consecutive tokens from the beginning.
137 func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
138 if len(tokens) > len(requestTokens) {
142 for i := 0; i < len(tokens); i++ {
143 each := requestTokens[i]
145 if len(each) == 0 && len(other) == 0 {
149 if len(other) > 0 && strings.HasPrefix(other, "{") {
160 score += (len(tokens) - i) * 10 //fuzzy