mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-22 05:01:26 +00:00
ad0f7a5305
Upgrade to Go 1.20.5, Hydra v2 SDK, rules-go v0.44.2 (with proper resolves), protobuf v25.3 and mass upgrade of Go dependencies.
327 lines
10 KiB
Go
327 lines
10 KiB
Go
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"
|
||
"strings"
|
||
)
|
||
|
||
// 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
|
||
}
|
||
|
||
// ExtractParameters is used to obtain the path parameters from the route using the same matching
|
||
// engine as the JSR 311 router.
|
||
func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
|
||
webServiceExpr := webService.pathExpr
|
||
webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
|
||
pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
|
||
routeExpr := route.pathExpr
|
||
routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
|
||
routeParams := r.extractParams(routeExpr, routeMatches)
|
||
for key, value := range routeParams {
|
||
pathParameters[key] = value
|
||
}
|
||
return pathParameters
|
||
}
|
||
|
||
func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
|
||
params := map[string]string{}
|
||
for i := 1; i < len(matches); i++ {
|
||
if len(pathExpr.VarNames) >= i {
|
||
params[pathExpr.VarNames[i-1]] = matches[i]
|
||
}
|
||
}
|
||
return params
|
||
}
|
||
|
||
// 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) {
|
||
candidates := make([]*Route, 0, 8)
|
||
for i, each := range routes {
|
||
ok := true
|
||
for _, fn := range each.If {
|
||
if !fn(httpRequest) {
|
||
ok = false
|
||
break
|
||
}
|
||
}
|
||
if ok {
|
||
candidates = append(candidates, &routes[i])
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
|
||
}
|
||
return nil, NewError(http.StatusNotFound, "404: Not Found")
|
||
}
|
||
|
||
// http method
|
||
previous := candidates
|
||
candidates = candidates[:0]
|
||
for _, each := range previous {
|
||
if httpRequest.Method == each.Method {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
|
||
}
|
||
allowed := []string{}
|
||
allowedLoop:
|
||
for _, candidate := range previous {
|
||
for _, method := range allowed {
|
||
if method == candidate.Method {
|
||
continue allowedLoop
|
||
}
|
||
}
|
||
allowed = append(allowed, candidate.Method)
|
||
}
|
||
header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
|
||
return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
|
||
}
|
||
|
||
// content-type
|
||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
||
previous = candidates
|
||
candidates = candidates[:0]
|
||
for _, each := range previous {
|
||
if each.matchesContentType(contentType) {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
|
||
}
|
||
if httpRequest.ContentLength > 0 {
|
||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
||
}
|
||
}
|
||
|
||
// accept
|
||
previous = candidates
|
||
candidates = candidates[:0]
|
||
accept := httpRequest.Header.Get(HEADER_Accept)
|
||
if len(accept) == 0 {
|
||
accept = "*/*"
|
||
}
|
||
for _, each := range previous {
|
||
if each.matchesAccept(accept) {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
|
||
}
|
||
available := []string{}
|
||
for _, candidate := range previous {
|
||
available = append(available, candidate.Produces...)
|
||
}
|
||
// if POST,PUT,PATCH without body
|
||
method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length")
|
||
if (method == http.MethodPost ||
|
||
method == http.MethodPut ||
|
||
method == http.MethodPatch) && length == "" {
|
||
return nil, NewError(
|
||
http.StatusUnsupportedMediaType,
|
||
fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
||
)
|
||
}
|
||
return nil, NewError(
|
||
http.StatusNotAcceptable,
|
||
fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
||
)
|
||
}
|
||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
||
return candidates[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
|
||
}
|