// FullPath returns the action full path computed by concatenating the API and resource base paths // with the action specific path. func (r *RouteDefinition) FullPath() string { if r.IsAbsolute() { return httppath.Clean(r.Path[1:]) } var base string if r.Parent != nil && r.Parent.Parent != nil { base = r.Parent.Parent.FullPath() } return httppath.Clean(path.Join(base, r.Path)) }
func cleanPath(path string, o MatchingOptions) string { path = httppath.Clean(path) if o.ignoreTrailingSlash() && len(path) > 1 && path[len(path)-1] == '/' { path = path[:len(path)-1] } return path }
// FullPath computes the base path to the resource actions concatenating the API and parent resource // base paths as needed. func (r *ResourceDefinition) FullPath() string { if strings.HasPrefix(r.BasePath, "//") { return httppath.Clean(r.BasePath) } var basePath string if p := r.Parent(); p != nil { if ca := p.CanonicalAction(); ca != nil { if routes := ca.Routes; len(routes) > 0 { // Note: all these tests should be true at code generation time // as DSL validation makes sure that parent resources have a // canonical path. basePath = path.Join(routes[0].FullPath()) } } } else { basePath = Design.BasePath } return httppath.Clean(path.Join(basePath, r.BasePath)) }
// constructs a matcher based on the provided definitions. // // If `ignoreTrailingSlash` is true, the matcher handles // paths with or without a trailing slash equally. // // It constructs the route definition into a trie structure // based on their path condition, if any, and puts the routes // with the same path condition into a leaf matcher structure // where they get evaluated after the leaf was matched based // on the rest of the conditions so that most strict route // definition matches first. func newMatcher(rs []*Route, o MatchingOptions) (*matcher, []*definitionError) { var ( errors []*definitionError rootLeaves leafMatchers ) pathMatchers := make(map[string]*pathMatcher) for i, r := range rs { l, err := newLeaf(r) if err != nil { errors = append(errors, &definitionError{r.Id, i, err}) continue } p := r.Path if p == "" { rootLeaves = append(rootLeaves, l) continue } // normalize path // in case ignoring trailing slashes, store and match all paths // without the trailing slash p = httppath.Clean(p) if o.ignoreTrailingSlash() && p[len(p)-1] == '/' { p = p[:len(p)-1] } pm := pathMatchers[p] if pm == nil { pm = &pathMatcher{freeWildcardParam: freeWildcardParam(p)} pathMatchers[p] = pm } pm.leaves = append(pm.leaves, l) } pathTree := &pathmux.Tree{} for p, m := range pathMatchers { // sort leaves during construction time, based on their priority sort.Sort(m.leaves) err := pathTree.Add(p, m) if err != nil { errors = append(errors, &definitionError{"", -1, err}) } } // sort root leaves during construction time, based on their priority sort.Sort(rootLeaves) return &matcher{pathTree, rootLeaves, o}, errors }
// tries to match a request against the available definitions. If a match is found, // returns the associated value, and the wildcard parameters from the path definition, // if any. func (m *matcher) match(r *http.Request) (*Route, map[string]string) { // normalize path before matching // in case ignoring trailing slashes, match without the trailing slash path := httppath.Clean(r.URL.Path) if m.matchingOptions.ignoreTrailingSlash() && path[len(path)-1] == '/' { path = path[:len(path)-1] } // first match fixed and wildcard paths leaves, params := matchPathTree(m.paths, path) l := matchLeaves(leaves, r, path) if l != nil { return l.route, params } // if no path match, match root leaves for other conditions l = matchLeaves(m.rootLeaves, r, path) if l != nil { return l.route, nil } return nil, nil }
func (t *TreeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if t.PanicHandler != nil { defer t.serveHTTPPanic(w, r) } path := r.RequestURI pathLen := len(path) if pathLen > 0 && t.PathSource == RequestURI { rawQueryLen := len(r.URL.RawQuery) if rawQueryLen != 0 || path[pathLen-1] == '?' { // Remove any query string and the ?. path = path[:pathLen-rawQueryLen-1] pathLen = len(path) } } else { // In testing with http.NewRequest, // RequestURI is not set so just grab URL.Path instead. path = r.URL.Path pathLen = len(path) } trailingSlash := path[pathLen-1] == '/' && pathLen > 1 if trailingSlash && t.RedirectTrailingSlash { path = path[:pathLen-1] } // params := make(map[string]string) var params map[string]string n := t.root.search(path[1:], ¶ms) if n == nil { if t.RedirectCleanPath { // Path was not found. Try cleaning it up and search again. // TODO Test this cleanPath := httppath.Clean(path) n = t.root.search(cleanPath[1:], ¶ms) if n == nil { // Still nothing found. t.NotFoundHandler(w, r) return } else { if statusCode, ok := t.redirectStatusCode(r.Method); ok { // Redirect to the actual path http.Redirect(w, r, cleanPath, statusCode) return } } } else { t.NotFoundHandler(w, r) return } } handler, ok := n.leafHandler[r.Method] if !ok { if r.Method == "HEAD" && t.HeadCanUseGet { handler, ok = n.leafHandler["GET"] } if !ok { t.MethodNotAllowedHandler(w, r, n.leafHandler) return } } if !n.isCatchAll || t.RemoveCatchAllTrailingSlash { if trailingSlash != n.addSlash && t.RedirectTrailingSlash { if statusCode, ok := t.redirectStatusCode(r.Method); ok { if n.addSlash { // Need to add a slash. http.Redirect(w, r, path+"/", statusCode) } else if path != "/" { // We need to remove the slash. This was already done at the // beginning of the function. http.Redirect(w, r, path, statusCode) } return } } } handler(w, r, params) }
func (t *TreeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if t.PanicHandler != nil { defer t.serveHTTPPanic(w, r) } path := r.RequestURI pathLen := len(path) if pathLen > 0 && t.PathSource == RequestURI { rawQueryLen := len(r.URL.RawQuery) if rawQueryLen != 0 || path[pathLen-1] == '?' { // Remove any query string and the ?. path = path[:pathLen-rawQueryLen-1] pathLen = len(path) } } else { // In testing with http.NewRequest, // RequestURI is not set so just grab URL.Path instead. path = r.URL.Path pathLen = len(path) } trailingSlash := path[pathLen-1] == '/' && pathLen > 1 if trailingSlash && t.RedirectTrailingSlash { path = path[:pathLen-1] } n, params := t.root.search(path[1:]) if n == nil { if t.RedirectCleanPath { // Path was not found. Try cleaning it up and search again. // TODO Test this cleanPath := httppath.Clean(path) n, params = t.root.search(cleanPath[1:]) if n == nil { // Still nothing found. t.NotFoundHandler(w, r) return } else { if statusCode, ok := t.redirectStatusCode(r.Method); ok { // Redirect to the actual path http.Redirect(w, r, cleanPath, statusCode) return } } } else { t.NotFoundHandler(w, r) return } } handler, ok := n.leafHandler[r.Method] if !ok { if r.Method == "HEAD" && t.HeadCanUseGet { handler, ok = n.leafHandler["GET"] } if !ok { t.MethodNotAllowedHandler(w, r, n.leafHandler) return } } if !n.isCatchAll || t.RemoveCatchAllTrailingSlash { if trailingSlash != n.addSlash && t.RedirectTrailingSlash { if statusCode, ok := t.redirectStatusCode(r.Method); ok { if n.addSlash { // Need to add a slash. http.Redirect(w, r, path+"/", statusCode) } else if path != "/" { // We need to remove the slash. This was already done at the // beginning of the function. http.Redirect(w, r, path, statusCode) } return } } } var paramMap map[string]string if len(params) != 0 { if len(params) != len(n.leafWildcardNames) { // Need better behavior here. Should this be a panic? panic(fmt.Sprintf("httptreemux parameter list length mismatch: %v, %v", params, n.leafWildcardNames)) } paramMap = make(map[string]string) numParams := len(params) for index := 0; index < numParams; index++ { paramMap[n.leafWildcardNames[numParams-index-1]] = params[index] } } handler(w, r, paramMap) }