Skip to content
This repository has been archived by the owner on Dec 23, 2023. It is now read-only.

Commit

Permalink
Add raw path option
Browse files Browse the repository at this point in the history
  • Loading branch information
razonyang committed Mar 23, 2020
1 parent e08c9f1 commit 78de802
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 20 deletions.
21 changes: 13 additions & 8 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ type Router struct {

// Error Handler.
ErrorHandler ErrorHandler

// If enabled, use the request.URL.RawPath instead of request.URL.Path.
UseRawPath bool
}

// Make sure the Router conforms with the http.Handler interface
Expand Down Expand Up @@ -275,7 +278,7 @@ func (r *Router) ServeFiles(path string, root http.FileSystem) {
// the same path with an extra / without the trailing slash should be performed.
func (r *Router) Lookup(method, path string) (*Route, Params, bool) {
if root := r.trees[method]; root != nil {
route, ps, tsr := root.getValue(path, r.getParams)
route, ps, tsr := root.getValue(path, r.getParams, r.UseRawPath)
if route == nil {
return nil, nil, tsr
}
Expand Down Expand Up @@ -310,7 +313,7 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
continue
}

handle, _, _ := r.trees[method].getValue(path, nil)
handle, _, _ := r.trees[method].getValue(path, nil, r.UseRawPath)
if handle != nil {
// Add request method to list of allowed methods
allowed = append(allowed, method)
Expand Down Expand Up @@ -340,12 +343,15 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
// ServeHTTP makes the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path
if r.UseRawPath && req.URL.RawPath != "" {
path = req.URL.RawPath
}
ctx := r.getContext()
ctx.Request = req
ctx.Response = w

if root := r.trees[req.Method]; root != nil {
if route, ps, tsr := root.getValue(path, r.getParams); route != nil {
if route, ps, tsr := root.getValue(path, r.getParams, r.UseRawPath); route != nil {
ctx.Route = route
if ps != nil {
r.putParams(ps)
Expand All @@ -367,11 +373,11 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {

if tsr && r.RedirectTrailingSlash {
if len(path) > 1 && path[len(path)-1] == '/' {
req.URL.Path = path[:len(path)-1]
path = path[:len(path)-1]
} else {
req.URL.Path = path + "/"
path = path + "/"
}
http.Redirect(w, req, req.URL.String(), code)
http.Redirect(w, req, path, code)
return
}

Expand All @@ -382,8 +388,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.RedirectTrailingSlash,
)
if found {
req.URL.Path = fixedPath
http.Redirect(w, req, req.URL.String(), code)
http.Redirect(w, req, fixedPath, code)
return
}
}
Expand Down
60 changes: 60 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,3 +982,63 @@ func TestRouter_HandleError(t *testing.T) {
}
}
}

func TestRouterUseRawPath(t *testing.T) {
router := NewRouter()
router.UseRawPath = true
handled := false
handle := func(ctx *Context) error {
expected := Params{Param{"name", "foo/bar"}}
if !reflect.DeepEqual(expected, ctx.Params) {
t.Errorf("expected params %v, got %v", expected, ctx.Params)
}
handled = true
return nil
}
router.Get("/hello/:name", handle)
req := httptest.NewRequest(http.MethodGet, "/hello/foo%2fbar", nil)
router.ServeHTTP(nil, req)
if !handled {
t.Error("raw path routing failed")
}
}

func TestRouterUseRawPathMixed(t *testing.T) {
router := NewRouter()
router.UseRawPath = true
handled := false
handle := func(ctx *Context) error {
expected := Params{Param{"date", "2020/03/23"}, Param{"slug", "hello world"}}
if !reflect.DeepEqual(expected, ctx.Params) {
t.Errorf("expected params %v, got %v", expected, ctx.Params)
}
handled = true
return nil
}
router.Get("/post/:date/:slug", handle)
req := httptest.NewRequest(http.MethodGet, "/post/2020%2f03%2f23/hello%20world", nil)
router.ServeHTTP(nil, req)
if !handled {
t.Error("raw path routing failed")
}
}

func TestRouterUseRawPathCatchAll(t *testing.T) {
router := NewRouter()
router.UseRawPath = true
handled := false
handle := func(ctx *Context) error {
expected := Params{Param{"slug", "/2020/03/23-hello world"}}
if !reflect.DeepEqual(expected, ctx.Params) {
t.Errorf("expected params %v, got %v", expected, ctx.Params)
}
handled = true
return nil
}
router.Get("/post/*slug", handle)
req := httptest.NewRequest(http.MethodGet, "/post/2020%2f03%2f23-hello%20world", nil)
router.ServeHTTP(nil, req)
if !handled {
t.Error("raw path routing failed")
}
}
25 changes: 18 additions & 7 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package clevergo

import (
"net/url"
"strings"
"unicode"
"unicode/utf8"
Expand Down Expand Up @@ -323,7 +324,7 @@ func (n *node) insertChild(path, fullPath string, route *Route) {
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
// made if a handle exists with an extra (without the) trailing slash for the
// given path.
func (n *node) getValue(path string, params func() *Params) (route *Route, ps *Params, tsr bool) {
func (n *node) getValue(path string, params func() *Params, useRawPath bool) (route *Route, ps *Params, tsr bool) {
walk: // Outer loop for walking the tree
for {
prefix := n.path
Expand Down Expand Up @@ -369,10 +370,15 @@ walk: // Outer loop for walking the tree
// Expand slice within preallocated capacity
i := len(*ps)
*ps = (*ps)[:i+1]
(*ps)[i] = Param{
Key: n.path[1:],
Value: path[:end],
param := Param{
Key: n.path[1:],
}
if useRawPath {
param.Value, _ = url.PathUnescape(path[:end])
} else {
param.Value = path[:end]
}
(*ps)[i] = param
}

// We need to go deeper!
Expand Down Expand Up @@ -408,10 +414,15 @@ walk: // Outer loop for walking the tree
// Expand slice within preallocated capacity
i := len(*ps)
*ps = (*ps)[:i+1]
(*ps)[i] = Param{
Key: n.path[2:],
Value: path,
param := Param{
Key: n.path[2:],
}
if useRawPath {
param.Value, _ = url.PathUnescape(path)
} else {
param.Value = path
}
(*ps)[i] = param
}

route = n.route
Expand Down
10 changes: 5 additions & 5 deletions tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func getParams() *Params {

func checkRequests(t *testing.T, tree *node, requests testRequests) {
for _, request := range requests {
handler, psp, _ := tree.getValue(request.path, getParams)
handler, psp, _ := tree.getValue(request.path, getParams, false)

if handler == nil {
if !request.nilHandler {
Expand Down Expand Up @@ -434,7 +434,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
"/doc/",
}
for _, route := range tsrRoutes {
handler, _, tsr := tree.getValue(route, nil)
handler, _, tsr := tree.getValue(route, nil, false)
if handler != nil {
t.Fatalf("non-nil handler for TSR route '%s", route)
} else if !tsr {
Expand All @@ -451,7 +451,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
"/api/world/abc",
}
for _, route := range noTsrRoutes {
handler, _, tsr := tree.getValue(route, nil)
handler, _, tsr := tree.getValue(route, nil, false)
if handler != nil {
t.Fatalf("non-nil handler for No-TSR route '%s", route)
} else if tsr {
Expand All @@ -470,7 +470,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
t.Fatalf("panic inserting test route: %v", recv)
}

handler, _, tsr := tree.getValue("/", nil)
handler, _, tsr := tree.getValue("/", nil, false)
if handler != nil {
t.Fatalf("non-nil handler")
} else if tsr {
Expand Down Expand Up @@ -650,7 +650,7 @@ func TestTreeInvalidNodeType(t *testing.T) {

// normal lookup
recv := catchPanic(func() {
tree.getValue("/test", nil)
tree.getValue("/test", nil, false)
})
if rs, ok := recv.(string); !ok || rs != panicMsg {
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
Expand Down

0 comments on commit 78de802

Please sign in to comment.