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: v3.3.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: v3.3.2
Choose a head ref
  • 6 commits
  • 8 files changed
  • 6 contributors

Commits on Dec 7, 2017

  1. Add pattern documentation (#285)

    lpar authored and pkieltyka committed Dec 7, 2017
    Copy the full SHA
    05458a1 View commit details

Commits on Dec 11, 2017

  1. Copy the full SHA
    893f598 View commit details

Commits on Dec 13, 2017

  1. Update README.md

    nikhilsaraf authored Dec 13, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    d099321 View commit details
  2. Merge pull request #287 from nikhilsaraf/patch-1

    Update README.md to fix typo
    VojtechVitek authored Dec 13, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    f2aed53 View commit details

Commits on Dec 22, 2017

  1. 1
    Copy the full SHA
    91a3777 View commit details
  2. v3.3.2

    Peter Kieltyka committed Dec 22, 2017
    1
    Copy the full SHA
    e83ac23 View commit details
Showing with 229 additions and 20 deletions.
  1. +7 −0 CHANGELOG.md
  2. +1 −1 README.md
  3. +26 −0 chi.go
  4. +51 −0 middleware/content_charset.go
  5. +124 −0 middleware/content_charset_test.go
  6. +9 −0 middleware/strip_test.go
  7. +1 −5 mux.go
  8. +10 −14 mux_test.go
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v3.3.2 (2017-12-22)

- Support to route trailing slashes on mounted sub-routers (#281)
- middleware: new `ContentCharset` to check matching charsets. Thank you
@csucu for your community contribution!


## v3.3.1 (2017-11-20)

- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -318,7 +318,7 @@ with `net/http` can be used with chi's mux.
### Core middlewares

-----------------------------------------------------------------------------------------------------------
| chi/middlware Handler | description |
| chi/middleware Handler | description |
|:----------------------|:---------------------------------------------------------------------------------
| AllowContentType | Explicit whitelist of accepted request Content-Types |
| Compress | Gzip compression for clients that accept compressed responses |
26 changes: 26 additions & 0 deletions chi.go
Original file line number Diff line number Diff line change
@@ -27,6 +27,32 @@
//
// See github.com/go-chi/chi/_examples/ for more in-depth examples.
//
// URL patterns allow for easy matching of path components in HTTP
// requests. The matching components can then be accessed using
// chi.URLParam(). All patterns must begin with a slash.
//
// A simple named placeholder {name} matches any sequence of characters
// up to the next / or the end of the URL. Trailing slashes on paths must
// be handled explicitly.
//
// A placeholder with a name followed by a colon allows a regular
// expression match, for example {number:\\d+}. The regular expression
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
// including { or } are not supported, and / will never be
// matched. An anonymous regexp pattern is allowed, using an empty string
// before the colon in the placeholder, such as {:\\d+}
//
// The special placeholder of asterisk matches the rest of the requested
// URL. Any trailing characters in the pattern are ignored. This is the only
// placeholder which will match / characters.
//
// Examples:
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
// "/user/{name}/info" matches "/user/jsmith/info"
// "/page/*" matches "/page/intro/latest"
// "/page/*/index" also matches "/page/intro/latest"
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
//
package chi

import "net/http"
51 changes: 51 additions & 0 deletions middleware/content_charset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package middleware

import (
"net/http"
"strings"
)

// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.
// An empty charset will allow requests with no Content-Type header or no specified charset.
func ContentCharset(charsets ...string) func(next http.Handler) http.Handler {
for i, c := range charsets {
charsets[i] = strings.ToLower(c)
}

return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !contentEncoding(r.Header.Get("Content-Type"), charsets...) {
w.WriteHeader(http.StatusUnsupportedMediaType)
return
}

next.ServeHTTP(w, r)
})
}
}

// Check the content encoding against a list of acceptable values.
func contentEncoding(ce string, charsets ...string) bool {
_, ce = split(strings.ToLower(ce), ";")
_, ce = split(ce, "charset=")
ce, _ = split(ce, ";")
for _, c := range charsets {
if ce == c {
return true
}
}

return false
}

// Split a string in two parts, cleaning any whitespace.
func split(str, sep string) (string, string) {
var a, b string
var parts = strings.SplitN(str, sep, 2)
a = strings.TrimSpace(parts[0])
if len(parts) == 2 {
b = strings.TrimSpace(parts[1])
}

return a, b
}
124 changes: 124 additions & 0 deletions middleware/content_charset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/go-chi/chi"
)

func TestContentCharset(t *testing.T) {
t.Parallel()

var tests = []struct {
name string
inputValue string
inputContentCharset []string
want int
}{
{
"should accept requests with a matching charset",
"application/json; charset=UTF-8",
[]string{"UTF-8"},
http.StatusOK,
},
{
"should be case-insensitive",
"application/json; charset=utf-8",
[]string{"UTF-8"},
http.StatusOK,
},
{
"should accept requests with a matching charset with extra values",
"application/json; foo=bar; charset=UTF-8; spam=eggs",
[]string{"UTF-8"},
http.StatusOK,
},
{
"should accept requests with a matching charset when multiple charsets are supported",
"text/xml; charset=UTF-8",
[]string{"UTF-8", "Latin-1"},
http.StatusOK,
},
{
"should accept requests with no charset if empty charset headers are allowed",
"text/xml",
[]string{"UTF-8", ""},
http.StatusOK,
},
{
"should not accept requests with no charset if empty charset headers are not allowed",
"text/xml",
[]string{"UTF-8"},
http.StatusUnsupportedMediaType,
},
{
"should not accept requests with a mismatching charset",
"text/plain; charset=Latin-1",
[]string{"UTF-8"},
http.StatusUnsupportedMediaType,
},
{
"should not accept requests with a mismatching charset even if empty charsets are allowed",
"text/plain; charset=Latin-1",
[]string{"UTF-8", ""},
http.StatusUnsupportedMediaType,
},
}

for _, tt := range tests {
var tt = tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var recorder = httptest.NewRecorder()

var r = chi.NewRouter()
r.Use(ContentCharset(tt.inputContentCharset...))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {})

var req, _ = http.NewRequest("GET", "/", nil)
req.Header.Set("Content-Type", tt.inputValue)

r.ServeHTTP(recorder, req)
var res = recorder.Result()

if res.StatusCode != tt.want {
t.Errorf("response is incorrect, got %d, want %d", recorder.Code, tt.want)
}
})
}
}

func TestSplit(t *testing.T) {
t.Parallel()

var s1, s2 = split(" type1;type2 ", ";")

if s1 != "type1" || s2 != "type2" {
t.Errorf("Want type1, type2 got %s, %s", s1, s2)
}

s1, s2 = split("type1 ", ";")

if s1 != "type1" {
t.Errorf("Want \"type1\" got \"%s\"", s1)
}
}

func TestContentEncoding(t *testing.T) {
t.Parallel()

if !contentEncoding("application/json; foo=bar; charset=utf-8; spam=eggs", []string{"utf-8"}...) {
t.Error("Want true, got false")
}

if contentEncoding("text/plain; charset=latin-1", []string{"utf-8"}...) {
t.Error("Want false, got true")
}

if !contentEncoding("text/xml; charset=UTF-8", []string{"latin-1", "utf-8"}...) {
t.Error("Want true, got false")
}
}
9 changes: 9 additions & 0 deletions middleware/strip_test.go
Original file line number Diff line number Diff line change
@@ -65,6 +65,9 @@ func TestStripSlashesInRoute(t *testing.T) {

r.Route("/accounts/{accountID}", func(r chi.Router) {
r.Use(StripSlashes)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("accounts index"))
})
r.Get("/query", func(w http.ResponseWriter, r *http.Request) {
accountID := chi.URLParam(r, "accountID")
w.Write([]byte(accountID))
@@ -80,6 +83,12 @@ func TestStripSlashesInRoute(t *testing.T) {
if _, resp := testRequest(t, ts, "GET", "/hi/", nil); resp != "nothing here" {
t.Fatalf(resp)
}
if _, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "accounts index" {
t.Fatalf(resp)
}
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "accounts index" {
t.Fatalf(resp)
}
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/query", nil); resp != "admin" {
t.Fatalf(resp)
}
6 changes: 1 addition & 5 deletions mux.go
Original file line number Diff line number Diff line change
@@ -292,12 +292,8 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
})

if pattern == "" || pattern[len(pattern)-1] != '/' {
notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mx.NotFoundHandler().ServeHTTP(w, r)
})

mx.handle(mALL|mSTUB, pattern, mountHandler)
mx.handle(mALL|mSTUB, pattern+"/", notFoundHandler)
mx.handle(mALL|mSTUB, pattern+"/", mountHandler)
pattern += "/"
}

24 changes: 10 additions & 14 deletions mux_test.go
Original file line number Diff line number Diff line change
@@ -492,13 +492,16 @@ func TestMuxComplicatedNotFound(t *testing.T) {
ts := httptest.NewServer(r)
defer ts.Close()

// check that we didn't broke correct routes
// check that we didn't break correct routes
if _, body := testRequest(t, ts, "GET", "/auth", nil); body != "auth get" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/public", nil); body != "public get" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/public/", nil); body != "public get" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/private/resource", nil); body != "private get" {
t.Fatalf(body)
}
@@ -519,15 +522,6 @@ func TestMuxComplicatedNotFound(t *testing.T) {
if _, body := testRequest(t, ts, "GET", "/auth/", nil); body != "custom not-found" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/public/", nil); body != "custom not-found" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/private/", nil); body != "custom not-found" {
t.Fatalf(body)
}
if _, body := testRequest(t, ts, "GET", "/private/resource/", nil); body != "custom not-found" {
t.Fatalf(body)
}
}

func TestMuxWith(t *testing.T) {
@@ -1048,6 +1042,9 @@ func TestMuxSubroutes(t *testing.T) {
sr := NewRouter()
sr.Get("/", hHubView3)
r.Mount("/hubs/{hubID}/users", sr)
r.Get("/hubs/{hubID}/users/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hub3 override"))
})

sr3 := NewRouter()
sr3.Get("/", hAccountView1)
@@ -1069,7 +1066,6 @@ func TestMuxSubroutes(t *testing.T) {
defer ts.Close()

var body, expected string
var resp *http.Response

_, body = testRequest(t, ts, "GET", "/hubs/123/view", nil)
expected = "hub1"
@@ -1086,9 +1082,9 @@ func TestMuxSubroutes(t *testing.T) {
if body != expected {
t.Fatalf("expected:%s got:%s", expected, body)
}
resp, body = testRequest(t, ts, "GET", "/hubs/123/users/", nil)
expected = "404 page not found\n"
if resp.StatusCode != 404 || body != expected {
_, body = testRequest(t, ts, "GET", "/hubs/123/users/", nil)
expected = "hub3 override"
if body != expected {
t.Fatalf("expected:%s got:%s", expected, body)
}
_, body = testRequest(t, ts, "GET", "/accounts/44", nil)