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.0.3
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.0.4
Choose a head ref
Loading
Showing with 695 additions and 311 deletions.
  1. +0 −12 .github/FUNDING.yml
  2. +1 −0 .travis.yml
  3. +7 −0 CHANGELOG.md
  4. +2 −1 README.md
  5. +1 −1 chi.go
  6. +32 −0 middleware/basic_auth.go
  7. +93 −95 middleware/compress.go
  8. +109 −116 middleware/compress_test.go
  9. +34 −0 middleware/content_encoding.go
  10. +82 −0 middleware/content_encoding_test.go
  11. +1 −4 middleware/logger.go
  12. +3 −3 middleware/realip.go
  13. +143 −3 middleware/recoverer.go
  14. +36 −8 middleware/throttle.go
  15. +49 −0 middleware/throttle_test.go
  16. +11 −5 mux.go
  17. +90 −54 mux_test.go
  18. +1 −9 tree.go
12 changes: 0 additions & 12 deletions .github/FUNDING.yml

This file was deleted.

1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ go:
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x

script:
- go get -d -t ./...
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v4.0.4 (2020-03-24)

- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496)
- a few minor improvements and fixes
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4


## v4.0.3 (2020-01-09)

- core: fix regexp routing to include default value when param is not matched
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -312,9 +312,10 @@ with `net/http` can be used with chi's mux.
### Core middlewares

-----------------------------------------------------------------------------------------------------------
| chi/middleware Handler | description |
| chi/middleware Handler | description |
|:----------------------|:---------------------------------------------------------------------------------
| AllowContentType | Explicit whitelist of accepted request Content-Types |
| BasicAuth | Basic HTTP authentication |
| Compress | Gzip compression for clients that accept compressed responses |
| GetHead | Automatically route undefined HEAD requests to GET handlers |
| Heartbeat | Monitoring endpoint to check the servers pulse |
2 changes: 1 addition & 1 deletion chi.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// Package chi is a small, idiomatic and composable router for building HTTP services.
//
// chi requires Go 1.7 or newer.
// chi requires Go 1.10 or newer.
//
// Example:
// package main
32 changes: 32 additions & 0 deletions middleware/basic_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package middleware

import (
"fmt"
"net/http"
)

// BasicAuth implements a simple middleware handler for adding basic http auth to a route.
func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok {
basicAuthFailed(w, realm)
return
}

credPass, credUserOk := creds[user]
if !credUserOk || pass != credPass {
basicAuthFailed(w, realm)
return
}

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

func basicAuthFailed(w http.ResponseWriter, realm string) {
w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
w.WriteHeader(http.StatusUnauthorized)
}
188 changes: 93 additions & 95 deletions middleware/compress.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"compress/flate"
"compress/gzip"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
@@ -26,9 +27,21 @@ var defaultCompressibleContentTypes = []string{
"image/svg+xml",
}

// A default compressor that allows for the old API to use the new code.
// DEPRECATED
var defaultCompressor *Compressor
// Compress is a middleware that compresses response
// body of a given content types to a data format based
// on Accept-Encoding request header. It uses a given
// compression level.
//
// NOTE: make sure to set the Content-Type header on your response
// otherwise this middleware will not compress the response body. For ex, in
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
// or set it manually.
//
// Passing a compression level of 5 is sensible value
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
compressor := NewCompressor(level, types...)
return compressor.Handler
}

// Compressor represents a set of encoding configurations.
type Compressor struct {
@@ -38,7 +51,8 @@ type Compressor struct {
// The mapping of pooled encoders to pools.
pooledEncoders map[string]*sync.Pool
// The set of content types allowed to be compressed.
allowedTypes map[string]bool
allowedTypes map[string]struct{}
allowedWildcards map[string]struct{}
// The list of encoders in order of decreasing precedence.
encodingPrecedence []string
}
@@ -50,22 +64,31 @@ type Compressor struct {
func NewCompressor(level int, types ...string) *Compressor {
// If types are provided, set those as the allowed types. If none are
// provided, use the default list.
allowedTypes := make(map[string]bool)
allowedTypes := make(map[string]struct{})
allowedWildcards := make(map[string]struct{})
if len(types) > 0 {
for _, t := range types {
allowedTypes[t] = true
if strings.Contains(strings.TrimSuffix(t, "/*"), "*") {
panic(fmt.Sprintf("middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported", t))
}
if strings.HasSuffix(t, "/*") {
allowedWildcards[strings.TrimSuffix(t, "/*")] = struct{}{}
} else {
allowedTypes[t] = struct{}{}
}
}
} else {
for _, t := range defaultCompressibleContentTypes {
allowedTypes[t] = true
allowedTypes[t] = struct{}{}
}
}

c := &Compressor{
level: level,
encoders: make(map[string]EncoderFunc),
pooledEncoders: make(map[string]*sync.Pool),
allowedTypes: allowedTypes,
level: level,
encoders: make(map[string]EncoderFunc),
pooledEncoders: make(map[string]*sync.Pool),
allowedTypes: allowedTypes,
allowedWildcards: allowedWildcards,
}

// Set the default encoders. The precedence order uses the reverse
@@ -168,31 +191,27 @@ func (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) {

// Handler returns a new middleware that will compress the response based on the
// current Compressor.
func (c *Compressor) Handler() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
encoder, encoding, cleanup := c.selectEncoder(r.Header, w)

cw := &compressResponseWriter{
ResponseWriter: w,
w: w,
contentTypes: c.allowedTypes,
encoding: encoding,
compressable: false, // determined in post-handler
}
if encoder != nil {
cw.w = encoder
}
// Re-add the encoder to the pool if applicable.
defer cleanup()
defer cw.Close()

next.ServeHTTP(cw, r)
func (c *Compressor) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
encoder, encoding, cleanup := c.selectEncoder(r.Header, w)

cw := &compressResponseWriter{
ResponseWriter: w,
w: w,
contentTypes: c.allowedTypes,
contentWildcards: c.allowedWildcards,
encoding: encoding,
compressable: false, // determined in post-handler
}
if encoder != nil {
cw.w = encoder
}
// Re-add the encoder to the pool if applicable.
defer cleanup()
defer cw.Close()

return http.HandlerFunc(fn)
}

next.ServeHTTP(cw, r)
})
}

// selectEncoder returns the encoder, the name of the encoder, and a closer function.
@@ -246,64 +265,36 @@ type ioResetterWriter interface {
Reset(w io.Writer)
}

// SetEncoder can be used to set the implementation of a compression algorithm.
//
// The encoding should be a standardised identifier. See:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
//
// For example, add the Brotli algortithm:
//
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
//
// middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
// params := brotli_enc.NewBrotliParams()
// params.SetQuality(level)
// return brotli_enc.NewBrotliWriter(params, w)
// })
//
// DEPRECATED
func SetEncoder(encoding string, fn EncoderFunc) {
if defaultCompressor == nil {
panic("no compressor to set encoders on. Call Compress() first")
}
defaultCompressor.SetEncoder(encoding, fn)
}

// DefaultCompress is a middleware that compresses response
// body of predefined content types to a data format based
// on Accept-Encoding request header. It uses a default
// compression level.
// DEPRECATED
func DefaultCompress(next http.Handler) http.Handler {
return Compress(flate.DefaultCompression)(next)
}

// Compress is a middleware that compresses response
// body of a given content types to a data format based
// on Accept-Encoding request header. It uses a given
// compression level.
//
// NOTE: make sure to set the Content-Type header on your response
// otherwise this middleware will not compress the response body. For ex, in
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
// or set it manually.
//
// DEPRECATED
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
defaultCompressor = NewCompressor(level, types...)
return defaultCompressor.Handler()
}

type compressResponseWriter struct {
http.ResponseWriter

// The streaming encoder writer to be used if there is one. Otherwise,
// this is just the normal writer.
w io.Writer
encoding string
contentTypes map[string]bool
wroteHeader bool
compressable bool
w io.Writer
encoding string
contentTypes map[string]struct{}
contentWildcards map[string]struct{}
wroteHeader bool
compressable bool
}

func (cw *compressResponseWriter) isCompressable() bool {
// Parse the first part of the Content-Type response header.
contentType := cw.Header().Get("Content-Type")
if idx := strings.Index(contentType, ";"); idx >= 0 {
contentType = contentType[0:idx]
}

// Is the content type compressable?
if _, ok := cw.contentTypes[contentType]; ok {
return true
}
if idx := strings.Index(contentType, "/"); idx > 0 {
contentType = contentType[0:idx]
_, ok := cw.contentWildcards[contentType]
return ok
}
return false
}

func (cw *compressResponseWriter) WriteHeader(code int) {
@@ -319,14 +310,7 @@ func (cw *compressResponseWriter) WriteHeader(code int) {
return
}

// Parse the first part of the Content-Type response header.
contentType := cw.Header().Get("Content-Type")
if idx := strings.Index(contentType, ";"); idx >= 0 {
contentType = contentType[0:idx]
}

// Is the content type compressable?
if _, ok := cw.contentTypes[contentType]; !ok {
if !cw.isCompressable() {
cw.compressable = false
return
}
@@ -357,10 +341,24 @@ func (cw *compressResponseWriter) writer() io.Writer {
}
}

type compressFlusher interface {
Flush() error
}

func (cw *compressResponseWriter) Flush() {
if f, ok := cw.writer().(http.Flusher); ok {
f.Flush()
}
// If the underlying writer has a compression flush signature,
// call this Flush() method instead
if f, ok := cw.writer().(compressFlusher); ok {
f.Flush()

// Also flush the underlying response writer
if f, ok := cw.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
}

func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
Loading