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 // RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
15 // as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
16 // RouterJSR311 implements the Router interface.
17 // Concept of locators is not implemented.
18 type RouterJSR311 struct{}
20 // SelectRoute is part of the Router interface and returns the best match
21 // for the WebService and its Route for the given Request.
22 func (r RouterJSR311) SelectRoute(
23 webServices []*WebService,
24 httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
26 // Identify the root resource class (WebService)
27 dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
29 return nil, nil, NewError(http.StatusNotFound, "")
31 // Obtain the set of candidate methods (Routes)
32 routes := r.selectRoutes(dispatcher, finalMatch)
34 return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
37 // Identify the method (Route) that will handle the request
38 route, ok := r.detectRoute(routes, httpRequest)
39 return dispatcher, route, ok
42 // ExtractParameters is used to obtain the path parameters from the route using the same matching
43 // engine as the JSR 311 router.
44 func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
45 webServiceExpr := webService.pathExpr
46 webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
47 pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
48 routeExpr := route.pathExpr
49 routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
50 routeParams := r.extractParams(routeExpr, routeMatches)
51 for key, value := range routeParams {
52 pathParameters[key] = value
57 func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
58 params := map[string]string{}
59 for i := 1; i < len(matches); i++ {
60 if len(pathExpr.VarNames) >= i {
61 params[pathExpr.VarNames[i-1]] = matches[i]
67 // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
68 func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
69 candidates := make([]*Route, 0, 8)
70 for i, each := range routes {
72 for _, fn := range each.If {
79 candidates = append(candidates, &routes[i])
82 if len(candidates) == 0 {
84 traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
86 return nil, NewError(http.StatusNotFound, "404: Not Found")
90 previous := candidates
91 candidates = candidates[:0]
92 for _, each := range previous {
93 if httpRequest.Method == each.Method {
94 candidates = append(candidates, each)
97 if len(candidates) == 0 {
99 traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
101 return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
105 contentType := httpRequest.Header.Get(HEADER_ContentType)
106 previous = candidates
107 candidates = candidates[:0]
108 for _, each := range previous {
109 if each.matchesContentType(contentType) {
110 candidates = append(candidates, each)
113 if len(candidates) == 0 {
115 traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
117 if httpRequest.ContentLength > 0 {
118 return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
123 previous = candidates
124 candidates = candidates[:0]
125 accept := httpRequest.Header.Get(HEADER_Accept)
126 if len(accept) == 0 {
129 for _, each := range previous {
130 if each.matchesAccept(accept) {
131 candidates = append(candidates, each)
134 if len(candidates) == 0 {
136 traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
138 return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable")
140 // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
141 return candidates[0], nil
144 // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
146 func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
151 // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
152 func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
153 filtered := &sortableRouteCandidates{}
154 for _, each := range dispatcher.Routes() {
155 pathExpr := each.pathExpr
156 matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
158 lastMatch := matches[len(matches)-1]
159 if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
160 filtered.candidates = append(filtered.candidates,
161 routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
165 if len(filtered.candidates) == 0 {
167 traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
171 sort.Sort(sort.Reverse(filtered))
173 // select other routes from candidates whoes expression matches rmatch
174 matchingRoutes := []Route{filtered.candidates[0].route}
175 for c := 1; c < len(filtered.candidates); c++ {
176 each := filtered.candidates[c]
177 if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
178 matchingRoutes = append(matchingRoutes, each.route)
181 return matchingRoutes
184 // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
185 func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
186 filtered := &sortableDispatcherCandidates{}
187 for _, each := range dispatchers {
188 matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
190 filtered.candidates = append(filtered.candidates,
191 dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
194 if len(filtered.candidates) == 0 {
196 traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
198 return nil, "", errors.New("not found")
200 sort.Sort(sort.Reverse(filtered))
201 return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
204 // Types and functions to support the sorting of Routes
206 type routeCandidate struct {
208 matchesCount int // the number of capturing groups
209 literalCount int // the number of literal characters (means those not resulting from template variable substitution)
210 nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
213 func (r routeCandidate) expressionToMatch() string {
214 return r.route.pathExpr.Source
217 func (r routeCandidate) String() string {
218 return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
221 type sortableRouteCandidates struct {
222 candidates []routeCandidate
225 func (rcs *sortableRouteCandidates) Len() int {
226 return len(rcs.candidates)
228 func (rcs *sortableRouteCandidates) Swap(i, j int) {
229 rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
231 func (rcs *sortableRouteCandidates) Less(i, j int) bool {
232 ci := rcs.candidates[i]
233 cj := rcs.candidates[j]
235 if ci.literalCount < cj.literalCount {
238 if ci.literalCount > cj.literalCount {
242 if ci.matchesCount < cj.matchesCount {
245 if ci.matchesCount > cj.matchesCount {
249 if ci.nonDefaultCount < cj.nonDefaultCount {
252 if ci.nonDefaultCount > cj.nonDefaultCount {
255 // quaternary key ("source" is interpreted as Path)
256 return ci.route.Path < cj.route.Path
259 // Types and functions to support the sorting of Dispatchers
261 type dispatcherCandidate struct {
262 dispatcher *WebService
264 matchesCount int // the number of capturing groups
265 literalCount int // the number of literal characters (means those not resulting from template variable substitution)
266 nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
268 type sortableDispatcherCandidates struct {
269 candidates []dispatcherCandidate
272 func (dc *sortableDispatcherCandidates) Len() int {
273 return len(dc.candidates)
275 func (dc *sortableDispatcherCandidates) Swap(i, j int) {
276 dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
278 func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
279 ci := dc.candidates[i]
280 cj := dc.candidates[j]
282 if ci.matchesCount < cj.matchesCount {
285 if ci.matchesCount > cj.matchesCount {
289 if ci.literalCount < cj.literalCount {
292 if ci.literalCount > cj.literalCount {
296 return ci.nonDefaultCount < cj.nonDefaultCount