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.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: v3.3.4
Choose a head ref
  • 15 commits
  • 8 files changed
  • 9 contributors

Commits on Sep 11, 2018

  1. Add support for Brotli compression (#326)

    * Add support for Brotli compression
    * Allow compression algorithms to be added at runtime
    polyfloyd authored and pkieltyka committed Sep 11, 2018
    Copy the full SHA
    235fa5d View commit details

Commits on Oct 14, 2018

  1. s/Goji/chi

    pearcedavis committed Oct 14, 2018
    Copy the full SHA
    16fa821 View commit details
  2. fix golint installation

    pearcedavis committed Oct 14, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    a8101e5 View commit details
  3. Merge pull request #351 from pdedmon/fix-docs

    Fix leftover Goji reference
    VojtechVitek authored Oct 14, 2018

    Verified

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

Commits on Oct 23, 2018

  1. Exported HTTP method names to avoid manual entry (#353)

    * Using package http's already defined Method constants instead of redefining them
    rodney-b authored and VojtechVitek committed Oct 23, 2018
    Copy the full SHA
    daa22f6 View commit details

Commits on Oct 24, 2018

  1. Copy the full SHA
    be3aea5 View commit details
  2. Copy the full SHA
    0ebf779 View commit details

Commits on Nov 6, 2018

  1. Copy the full SHA
    263880d View commit details

Commits on Dec 10, 2018

  1. Copy the full SHA
    def7567 View commit details

Commits on Dec 21, 2018

  1. fix travis

    pkieltyka committed Dec 21, 2018
    Copy the full SHA
    0ae339d View commit details
  2. Copy the full SHA
    efb8f44 View commit details

Commits on Jan 3, 2019

  1. Update timeout example (#377)

    iriri authored and VojtechVitek committed Jan 3, 2019
    Copy the full SHA
    6172f3d View commit details

Commits on Jan 8, 2019

  1. Add no-transform to NoCache middleware

    From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Other:
    
    No transformations or conversions should be made to the resource. The Content-Encoding, Content-Range, Content-Type headers must not be modified by a proxy. A non- transparent proxy might, for example, convert between image formats in order to save cache space or to reduce the amount of traffic on a slow link. The no-transform directive disallows this.
    someone1 authored and pkieltyka committed Jan 8, 2019
    Copy the full SHA
    fad5e30 View commit details
  2. Do not remove content-length when not compressing

    The compress middleware removes `content-length` header before writing headers as the resulting length is not known at the time anymore. However it also does this if there is no suitable encoder found for the content type we have, forcing chunked encoding on every response.
    eknkc authored and pkieltyka committed Jan 8, 2019
    Copy the full SHA
    2a9a6cc View commit details
  3. Copy the full SHA
    08d9051 View commit details
Showing with 161 additions and 101 deletions.
  1. +1 −7 .travis.yml
  2. +3 −3 _examples/versions/main.go
  3. +138 −77 middleware/compress.go
  4. +1 −1 middleware/nocache.go
  5. +1 −1 middleware/realip.go
  6. +6 −2 middleware/request_id.go
  7. +2 −1 middleware/timeout.go
  8. +9 −9 tree.go
8 changes: 1 addition & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
language: go

go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x

install:
- go get -u golang.org/x/tools/cmd/goimports
- go get -u github.com/golang/lint/golint

script:
- go get -d -t ./...
- go vet ./...
- golint ./...
- go test ./...
- >
go_version=$(go version);
if [ ${go_version:13:4} = "1.11" ]; then
go get -u golang.org/x/tools/cmd/goimports;
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
fi
6 changes: 3 additions & 3 deletions _examples/versions/main.go
Original file line number Diff line number Diff line change
@@ -16,9 +16,9 @@ import (

"github.com/go-chi/chi"
"github.com/go-chi/chi/_examples/versions/data"
"github.com/go-chi/chi/_examples/versions/presenter/v1"
"github.com/go-chi/chi/_examples/versions/presenter/v2"
"github.com/go-chi/chi/_examples/versions/presenter/v3"
v1 "github.com/go-chi/chi/_examples/versions/presenter/v1"
v2 "github.com/go-chi/chi/_examples/versions/presenter/v2"
v3 "github.com/go-chi/chi/_examples/versions/presenter/v3"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/render"
)
215 changes: 138 additions & 77 deletions middleware/compress.go
Original file line number Diff line number Diff line change
@@ -8,27 +8,91 @@ import (
"io"
"net"
"net/http"
"regexp"
"sort"
"strings"
)

type encoding int
var encoders = map[string]EncoderFunc{}

const (
encodingNone encoding = iota
encodingGzip
encodingDeflate
)
var acceptEncodingAlgorithmsRe = regexp.MustCompile(`([a-z]{2,}|\*)`)

func init() {
// TODO:
// lzma: Opera.
// sdch: Chrome, Android. Gzip output + dictionary header.
// br: Brotli.

// TODO: Exception for old MSIE browsers that can't handle non-HTML?
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
SetEncoder("gzip", encoderGzip)

// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
// checksum compared to CRC-32 used in "gzip" and thus is faster.
//
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
// raw DEFLATE data only, without the mentioned zlib wrapper.
// Because of this major confusion, most modern browsers try it
// both ways, first looking for zlib headers.
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
//
// The list of browsers having problems is quite big, see:
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
//
// That's why we prefer gzip over deflate. It's just more reliable
// and not significantly slower than gzip.
SetEncoder("deflate", encoderDeflate)

// NOTE: Not implemented, intentionally:
// case "compress": // LZW. Deprecated.
// case "bzip2": // Too slow on-the-fly.
// case "zopfli": // Too slow on-the-fly.
// case "xz": // Too slow on-the-fly.
}

// An EncoderFunc is a function that wraps the provided ResponseWriter with a
// streaming compression algorithm and returns it.
//
// In case of failure, the function should return nil.
type EncoderFunc func(w http.ResponseWriter, level int) 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)
// })
func SetEncoder(encoding string, fn EncoderFunc) {
if encoding == "" {
panic("the encoding can not be empty")
}
if fn == nil {
panic("attempted to set a nil encoder function")
}
encoders[encoding] = fn
}

var defaultContentTypes = map[string]struct{}{
"text/html": struct{}{},
"text/css": struct{}{},
"text/plain": struct{}{},
"text/javascript": struct{}{},
"application/javascript": struct{}{},
"application/x-javascript": struct{}{},
"application/json": struct{}{},
"application/atom+xml": struct{}{},
"application/rss+xml": struct{}{},
"text/html": {},
"text/css": {},
"text/plain": {},
"text/javascript": {},
"application/javascript": {},
"application/x-javascript": {},
"application/json": {},
"application/atom+xml": {},
"application/rss+xml": {},
"image/svg+xml": {},
}

// DefaultCompress is a middleware that compresses response
@@ -54,11 +118,13 @@ func Compress(level int, types ...string) func(next http.Handler) http.Handler {

return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
encoder, encoding := selectEncoder(r.Header)
mcw := &maybeCompressResponseWriter{
ResponseWriter: w,
w: w,
contentTypes: contentTypes,
encoding: selectEncoding(r.Header),
encoder: encoder,
encoding: encoding,
level: level,
}
defer mcw.Close()
@@ -70,53 +136,46 @@ func Compress(level int, types ...string) func(next http.Handler) http.Handler {
}
}

func selectEncoding(h http.Header) encoding {
enc := h.Get("Accept-Encoding")
func selectEncoder(h http.Header) (EncoderFunc, string) {
header := h.Get("Accept-Encoding")

switch {
// TODO:
// case "br": // Brotli, experimental. Firefox 2016, to-be-in Chromium.
// case "lzma": // Opera.
// case "sdch": // Chrome, Android. Gzip output + dictionary header.

case strings.Contains(enc, "gzip"):
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
return encodingGzip

case strings.Contains(enc, "deflate"):
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
// checksum compared to CRC-32 used in "gzip" and thus is faster.
//
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
// raw DEFLATE data only, without the mentioned zlib wrapper.
// Because of this major confusion, most modern browsers try it
// both ways, first looking for zlib headers.
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
//
// The list of browsers having problems is quite big, see:
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
//
// That's why we prefer gzip over deflate. It's just more reliable
// and not significantly slower than gzip.
return encodingDeflate

// NOTE: Not implemented, intentionally:
// case "compress": // LZW. Deprecated.
// case "bzip2": // Too slow on-the-fly.
// case "zopfli": // Too slow on-the-fly.
// case "xz": // Too slow on-the-fly.
}

return encodingNone
// Parse the names of all accepted algorithms from the header.
var accepted []string
for _, m := range acceptEncodingAlgorithmsRe.FindAllStringSubmatch(header, -1) {
accepted = append(accepted, m[1])
}

sort.Sort(byPerformance(accepted))

// Select the first mutually supported algorithm.
for _, name := range accepted {
if fn, ok := encoders[name]; ok {
return fn, name
}
}
return nil, ""
}

type byPerformance []string

func (l byPerformance) Len() int { return len(l) }
func (l byPerformance) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l byPerformance) Less(i, j int) bool {
// Higher number = higher preference. This causes unknown names, which map
// to 0, to always be less prefered.
scores := map[string]int{
"br": 3,
"gzip": 2,
"deflate": 1,
}
return scores[l[i]] > scores[l[j]]
}

type maybeCompressResponseWriter struct {
http.ResponseWriter
w io.Writer
encoding encoding
encoder EncoderFunc
encoding string
contentTypes map[string]struct{}
level int
wroteHeader bool
@@ -133,8 +192,6 @@ func (w *maybeCompressResponseWriter) WriteHeader(code int) {
if w.ResponseWriter.Header().Get("Content-Encoding") != "" {
return
}
// The content-length after compression is unknown
w.ResponseWriter.Header().Del("Content-Length")

// Parse the first part of the Content-Type response header.
contentType := ""
@@ -148,25 +205,13 @@ func (w *maybeCompressResponseWriter) WriteHeader(code int) {
return
}

// Select the compress writer.
switch w.encoding {
case encodingGzip:
gw, err := gzip.NewWriterLevel(w.ResponseWriter, w.level)
if err != nil {
w.w = w.ResponseWriter
return
if w.encoder != nil && w.encoding != "" {
if wr := w.encoder(w.ResponseWriter, w.level); wr != nil {
w.w = wr
w.Header().Set("Content-Encoding", w.encoding)
// The content-length after compression is unknown
w.Header().Del("Content-Length")
}
w.w = gw
w.ResponseWriter.Header().Set("Content-Encoding", "gzip")

case encodingDeflate:
dw, err := flate.NewWriter(w.ResponseWriter, w.level)
if err != nil {
w.w = w.ResponseWriter
return
}
w.w = dw
w.ResponseWriter.Header().Set("Content-Encoding", "deflate")
}
}

@@ -210,3 +255,19 @@ func (w *maybeCompressResponseWriter) Close() error {
}
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
}

func encoderGzip(w http.ResponseWriter, level int) io.Writer {
gw, err := gzip.NewWriterLevel(w, level)
if err != nil {
return nil
}
return gw
}

func encoderDeflate(w http.ResponseWriter, level int) io.Writer {
dw, err := flate.NewWriter(w, level)
if err != nil {
return nil
}
return dw
}
2 changes: 1 addition & 1 deletion middleware/nocache.go
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ var epoch = time.Unix(0, 0).Format(time.RFC1123)
// Taken from https://github.com/mytrile/nocache
var noCacheHeaders = map[string]string{
"Expires": epoch,
"Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0",
"Cache-Control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0",
"Pragma": "no-cache",
"X-Accel-Expires": "0",
}
2 changes: 1 addition & 1 deletion middleware/realip.go
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ var xRealIP = http.CanonicalHeaderKey("X-Real-IP")
// You should only use this middleware if you can trust the headers passed to
// you (in particular, the two headers this middleware uses), for example
// because you have placed a reverse proxy like HAProxy or nginx in front of
// Goji. If your reverse proxies are configured to pass along arbitrary header
// chi. If your reverse proxies are configured to pass along arbitrary header
// values from the client, or if you use this middleware without a reverse
// proxy, malicious clients will be able to make you very sad (or, depending on
// how you're using RemoteAddr, vulnerable to an attack of some sort).
8 changes: 6 additions & 2 deletions middleware/request_id.go
Original file line number Diff line number Diff line change
@@ -62,9 +62,13 @@ func init() {
// counter.
func RequestID(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
myid := atomic.AddUint64(&reqid, 1)
ctx := r.Context()
ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, myid))
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
3 changes: 2 additions & 1 deletion middleware/timeout.go
Original file line number Diff line number Diff line change
@@ -15,7 +15,8 @@ import (
//
// ie. a route/handler may look like:
//
// r.Get("/long", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// r.Get("/long", func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// processTime := time.Duration(rand.Intn(4)+1) * time.Second
//
// select {
18 changes: 9 additions & 9 deletions tree.go
Original file line number Diff line number Diff line change
@@ -33,15 +33,15 @@ var mALL = mCONNECT | mDELETE | mGET | mHEAD |
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE

var methodMap = map[string]methodTyp{
"CONNECT": mCONNECT,
"DELETE": mDELETE,
"GET": mGET,
"HEAD": mHEAD,
"OPTIONS": mOPTIONS,
"PATCH": mPATCH,
"POST": mPOST,
"PUT": mPUT,
"TRACE": mTRACE,
http.MethodConnect: mCONNECT,
http.MethodDelete: mDELETE,
http.MethodGet: mGET,
http.MethodHead: mHEAD,
http.MethodOptions: mOPTIONS,
http.MethodPatch: mPATCH,
http.MethodPost: mPOST,
http.MethodPut: mPUT,
http.MethodTrace: mTRACE,
}

// RegisterMethod adds support for custom HTTP method handlers, available