package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. import ( "errors" "fmt" "net/http" "sort" ) // RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions) // as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html. // RouterJSR311 implements the Router interface. // Concept of locators is not implemented. type RouterJSR311 struct{} // SelectRoute is part of the Router interface and returns the best match // for the WebService and its Route for the given Request. func (r RouterJSR311) SelectRoute( webServices []*WebService, httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) { // Identify the root resource class (WebService) dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices) if err != nil { return nil, nil, NewError(http.StatusNotFound, "") } // Obtain the set of candidate methods (Routes) routes := r.selectRoutes(dispatcher, finalMatch) if len(routes) == 0 { return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found") } // Identify the method (Route) that will handle the request route, ok := r.detectRoute(routes, httpRequest) return dispatcher, route, ok } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) { // http method methodOk := []Route{} for _, each := range routes { if httpRequest.Method == each.Method { methodOk = append(methodOk, each) } } if len(methodOk) == 0 { if trace { traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method) } return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") } inputMediaOk := methodOk // content-type contentType := httpRequest.Header.Get(HEADER_ContentType) inputMediaOk = []Route{} for _, each := range methodOk { if each.matchesContentType(contentType) { inputMediaOk = append(inputMediaOk, each) } } if len(inputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(methodOk), contentType) } return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") } // accept outputMediaOk := []Route{} accept := httpRequest.Header.Get(HEADER_Accept) if len(accept) == 0 { accept = "*/*" } for _, each := range inputMediaOk { if each.matchesAccept(accept) { outputMediaOk = append(outputMediaOk, each) } } if len(outputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept) } return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable") } // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil return &outputMediaOk[0], nil } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 // n/m > n/* > */* func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route { // TODO return &routes[0] } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2) func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route { filtered := &sortableRouteCandidates{} for _, each := range dispatcher.Routes() { pathExpr := each.pathExpr matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder) if matches != nil { lastMatch := matches[len(matches)-1] if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’. filtered.candidates = append(filtered.candidates, routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount}) } } } if len(filtered.candidates) == 0 { if trace { traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder) } return []Route{} } sort.Sort(sort.Reverse(filtered)) // select other routes from candidates whoes expression matches rmatch matchingRoutes := []Route{filtered.candidates[0].route} for c := 1; c < len(filtered.candidates); c++ { each := filtered.candidates[c] if each.route.pathExpr.Matcher.MatchString(pathRemainder) { matchingRoutes = append(matchingRoutes, each.route) } } return matchingRoutes } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1) func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) { filtered := &sortableDispatcherCandidates{} for _, each := range dispatchers { matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath) if matches != nil { filtered.candidates = append(filtered.candidates, dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount}) } } if len(filtered.candidates) == 0 { if trace { traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath) } return nil, "", errors.New("not found") } sort.Sort(sort.Reverse(filtered)) return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil } // Types and functions to support the sorting of Routes type routeCandidate struct { route Route matchesCount int // the number of capturing groups literalCount int // the number of literal characters (means those not resulting from template variable substitution) nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’) } func (r routeCandidate) expressionToMatch() string { return r.route.pathExpr.Source } func (r routeCandidate) String() string { return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount) } type sortableRouteCandidates struct { candidates []routeCandidate } func (rcs *sortableRouteCandidates) Len() int { return len(rcs.candidates) } func (rcs *sortableRouteCandidates) Swap(i, j int) { rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i] } func (rcs *sortableRouteCandidates) Less(i, j int) bool { ci := rcs.candidates[i] cj := rcs.candidates[j] // primary key if ci.literalCount < cj.literalCount { return true } if ci.literalCount > cj.literalCount { return false } // secundary key if ci.matchesCount < cj.matchesCount { return true } if ci.matchesCount > cj.matchesCount { return false } // tertiary key if ci.nonDefaultCount < cj.nonDefaultCount { return true } if ci.nonDefaultCount > cj.nonDefaultCount { return false } // quaternary key ("source" is interpreted as Path) return ci.route.Path < cj.route.Path } // Types and functions to support the sorting of Dispatchers type dispatcherCandidate struct { dispatcher *WebService finalMatch string matchesCount int // the number of capturing groups literalCount int // the number of literal characters (means those not resulting from template variable substitution) nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’) } type sortableDispatcherCandidates struct { candidates []dispatcherCandidate } func (dc *sortableDispatcherCandidates) Len() int { return len(dc.candidates) } func (dc *sortableDispatcherCandidates) Swap(i, j int) { dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i] } func (dc *sortableDispatcherCandidates) Less(i, j int) bool { ci := dc.candidates[i] cj := dc.candidates[j] // primary key if ci.matchesCount < cj.matchesCount { return true } if ci.matchesCount > cj.matchesCount { return false } // secundary key if ci.literalCount < cj.literalCount { return true } if ci.literalCount > cj.literalCount { return false } // tertiary key return ci.nonDefaultCount < cj.nonDefaultCount }