Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: go-chi/chi
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.1.1
Choose a base ref
...
head repository: go-chi/chi
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.1.2
Choose a head ref
  • 7 commits
  • 7 files changed
  • 4 contributors

Commits on Apr 16, 2020

  1. Copy the full SHA
    2333c5c View commit details

Commits on May 17, 2020

  1. README

    pkieltyka committed May 17, 2020
    Copy the full SHA
    ccb4c33 View commit details

Commits on May 22, 2020

  1. Copy the full SHA
    5704d7e View commit details

Commits on May 30, 2020

  1. Copy the full SHA
    e7728c6 View commit details

Commits on May 31, 2020

  1. Copy the full SHA
    234035e View commit details

Commits on Jun 2, 2020

  1. Copy the full SHA
    fdba45d View commit details
  2. Release v4.1.2

    pkieltyka committed Jun 2, 2020
    Copy the full SHA
    86f9a6e View commit details
Showing with 178 additions and 48 deletions.
  1. +12 −0 .github/FUNDING.yml
  2. +7 −0 CHANGELOG.md
  3. +6 −3 README.md
  4. +55 −45 context.go
  5. +78 −0 context_test.go
  6. +16 −0 mux_test.go
  7. +4 −0 tree.go
12 changes: 12 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# These are supported funding model platforms

github: [pkieltyka] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v4.1.2 (2020-06-02)

- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution
- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution
- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2


## v4.1.1 (2020-04-16)

- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -337,7 +337,7 @@ with `net/http` can be used with chi's mux.
| WithValue | Short-hand middleware to set a key/value on the request context |
-----------------------------------------------------------------------------------------------------------

### Auxiliary middlewares & packages
### Extra middlewares & packages

Please see https://github.com/go-chi for additional packages.

@@ -348,8 +348,11 @@ Please see https://github.com/go-chi for additional packages.
| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime |
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
| [httplog](https://github.com/goware/httplog) | Small but powerful structured HTTP request logging |
| [httplog](https://github.com/go-chi/httplog) | Small but powerful structured HTTP request logging |
| [httprate](https://github.com/go-chi/httprate) | HTTP request rate limiter |
| [httptracer](https://github.com/go-chi/httptracer) | HTTP request performance tracing library |
| [httpvcr](https://github.com/go-chi/httpvcr) | Write deterministic tests for external sources |
| [stampede](https://github.com/go-chi/stampede) | HTTP request coalescer |
--------------------------------------------------------------------------------------------------------------------

please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
100 changes: 55 additions & 45 deletions context.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,54 @@ import (
"strings"
)

// URLParam returns the url parameter from a http.Request object.
func URLParam(r *http.Request, key string) string {
if rctx := RouteContext(r.Context()); rctx != nil {
return rctx.URLParam(key)
}
return ""
}

// URLParamFromCtx returns the url parameter from a http.Request Context.
func URLParamFromCtx(ctx context.Context, key string) string {
if rctx := RouteContext(ctx); rctx != nil {
return rctx.URLParam(key)
}
return ""
}

// RouteContext returns chi's routing Context object from a
// http.Request Context.
func RouteContext(ctx context.Context) *Context {
val, _ := ctx.Value(RouteCtxKey).(*Context)
return val
}

// ServerBaseContext wraps an http.Handler to set the request context to the
// `baseCtx`.
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
baseCtx := baseCtx

// Copy over default net/http server context keys
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
}
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
}

h.ServeHTTP(w, r.WithContext(baseCtx))
})
return fn
}

// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
return &Context{}
}

var (
// RouteCtxKey is the context.Context key to store the request context.
RouteCtxKey = &contextKey{"RouteContext"}
@@ -46,11 +94,6 @@ type Context struct {
methodNotAllowed bool
}

// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
return &Context{}
}

// Reset a routing context to its initial state.
func (x *Context) Reset() {
x.Routes = nil
@@ -93,30 +136,17 @@ func (x *Context) URLParam(key string) string {
// }
func (x *Context) RoutePattern() string {
routePattern := strings.Join(x.RoutePatterns, "")
return strings.Replace(routePattern, "/*/", "/", -1)
return replaceWildcards(routePattern)
}

// RouteContext returns chi's routing Context object from a
// http.Request Context.
func RouteContext(ctx context.Context) *Context {
val, _ := ctx.Value(RouteCtxKey).(*Context)
return val
}

// URLParam returns the url parameter from a http.Request object.
func URLParam(r *http.Request, key string) string {
if rctx := RouteContext(r.Context()); rctx != nil {
return rctx.URLParam(key)
// replaceWildcards takes a route pattern and recursively replaces all
// occurrences of "/*/" to "/".
func replaceWildcards(p string) string {
if strings.Contains(p, "/*/") {
return replaceWildcards(strings.Replace(p, "/*/", "/", -1))
}
return ""
}

// URLParamFromCtx returns the url parameter from a http.Request Context.
func URLParamFromCtx(ctx context.Context, key string) string {
if rctx := RouteContext(ctx); rctx != nil {
return rctx.URLParam(key)
}
return ""
return p
}

// RouteParams is a structure to track URL routing parameters efficiently.
@@ -130,26 +160,6 @@ func (s *RouteParams) Add(key, value string) {
s.Values = append(s.Values, value)
}

// ServerBaseContext wraps an http.Handler to set the request context to the
// `baseCtx`.
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
baseCtx := baseCtx

// Copy over default net/http server context keys
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
}
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
}

h.ServeHTTP(w, r.WithContext(baseCtx))
})
return fn
}

// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
78 changes: 78 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package chi

import "testing"

// TestRoutePattern tests correct in-the-middle wildcard removals.
// If user organizes a router like this:
//
// (router.go)
// r.Route("/v1", func(r chi.Router) {
// r.Mount("/resources", resourcesController{}.Router())
// }
//
// (resources_controller.go)
// r.Route("/", func(r chi.Router) {
// r.Get("/{resource_id}", getResource())
// other routes...
// }
//
// This test checks how the route pattern is calculated
// "/v1/resources/{resource_id}" (right)
// "/v1/resources/*/{resource_id}" (wrong)
func TestRoutePattern(t *testing.T) {
routePatterns := []string{
"/v1/*",
"/resources/*",
"/{resource_id}",
}

x := &Context{
RoutePatterns: routePatterns,
}

if p := x.RoutePattern(); p != "/v1/resources/{resource_id}" {
t.Fatal("unexpected route pattern: " + p)
}

x.RoutePatterns = []string{
"/v1/*",
"/resources/*",
// Additional wildcard, depending on the router structure of the user
"/*",
"/{resource_id}",
}

// Correctly removes in-the-middle wildcards instead of "/v1/resources/*/{resource_id}"
if p := x.RoutePattern(); p != "/v1/resources/{resource_id}" {
t.Fatal("unexpected route pattern: " + p)
}

x.RoutePatterns = []string{
"/v1/*",
"/resources/*",
// Even with many wildcards
"/*",
"/*",
"/*",
"/{resource_id}/*", // Keeping trailing wildcard
}

// Correctly removes in-the-middle wildcards instead of "/v1/resources/*/*/{resource_id}/*"
if p := x.RoutePattern(); p != "/v1/resources/{resource_id}/*" {
t.Fatal("unexpected route pattern: " + p)
}

x.RoutePatterns = []string{
"/v1/*",
"/resources/*",
// And respects asterisks as part of the paths
"/*special_path/*",
"/with_asterisks*/*",
"/{resource_id}",
}

// Correctly removes in-the-middle wildcards instead of "/v1/resourcesspecial_path/with_asterisks{resource_id}"
if p := x.RoutePattern(); p != "/v1/resources/*special_path/with_asterisks*/{resource_id}" {
t.Fatal("unexpected route pattern: " + p)
}
}
16 changes: 16 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
@@ -417,8 +417,18 @@ func TestMuxNestedMethodNotAllowed(t *testing.T) {
w.Write([]byte("sub2"))
})

pathVar := NewRouter()
pathVar.Get("/{var}", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pv"))
})
pathVar.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(405)
w.Write([]byte("pv 405"))
})

r.Mount("/prefix1", sr1)
r.Mount("/prefix2", sr2)
r.Mount("/pathVar", pathVar)

ts := httptest.NewServer(r)
defer ts.Close()
@@ -441,6 +451,12 @@ func TestMuxNestedMethodNotAllowed(t *testing.T) {
if _, body := testRequest(t, ts, "PUT", "/prefix2/sub2", nil); body != "root 405" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/pathVar/myvar", nil); body != "pv" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "DELETE", "/pathVar/myvar", nil); body != "pv 405" {
t.Fatalf(body)
}
}

func TestMuxComplicatedNotFound(t *testing.T) {
4 changes: 4 additions & 0 deletions tree.go
Original file line number Diff line number Diff line change
@@ -452,6 +452,10 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)
return xn
}

// flag that the routing context found a route, but not a corresponding
// supported method
rctx.methodNotAllowed = true
}
}