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