Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame^] | 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 | ) |
| 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. |
| 18 | type 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. |
| 22 | func (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. |
| 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 |
| 53 | } |
| 54 | return pathParameters |
| 55 | } |
| 56 | |
| 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] |
| 62 | } |
| 63 | } |
| 64 | return params |
| 65 | } |
| 66 | |
| 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 | 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/* > */* |
| 142 | func (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) |
| 148 | func (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) |
| 181 | func (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 | |
| 202 | type 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 | |
| 209 | func (r routeCandidate) expressionToMatch() string { |
| 210 | return r.route.pathExpr.Source |
| 211 | } |
| 212 | |
| 213 | func (r routeCandidate) String() string { |
| 214 | return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount) |
| 215 | } |
| 216 | |
| 217 | type sortableRouteCandidates struct { |
| 218 | candidates []routeCandidate |
| 219 | } |
| 220 | |
| 221 | func (rcs *sortableRouteCandidates) Len() int { |
| 222 | return len(rcs.candidates) |
| 223 | } |
| 224 | func (rcs *sortableRouteCandidates) Swap(i, j int) { |
| 225 | rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i] |
| 226 | } |
| 227 | func (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 | |
| 257 | type 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 | } |
| 264 | type sortableDispatcherCandidates struct { |
| 265 | candidates []dispatcherCandidate |
| 266 | } |
| 267 | |
| 268 | func (dc *sortableDispatcherCandidates) Len() int { |
| 269 | return len(dc.candidates) |
| 270 | } |
| 271 | func (dc *sortableDispatcherCandidates) Swap(i, j int) { |
| 272 | dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i] |
| 273 | } |
| 274 | func (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 | } |