blob: 4360b492ec1027ea13139a60870f12618fe44c13 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package 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}