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: v5.0.8
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: v5.0.9
Choose a head ref
  • 16 commits
  • 17 files changed
  • 13 contributors

Commits on Dec 10, 2022

  1. Fix typo (#767)

    michalbric authored Dec 10, 2022

    Verified

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

Commits on Dec 18, 2022

  1. Use timestamps in GMT format per RFC2616 (#772)

    * chore(middleware/nocache): using `http.TimeFormat` instead of `time.RFC1123` while formatting expires header
    
    * fix(middleware/nocache): use utc based time
    falentio authored Dec 18, 2022
    Copy the full SHA
    e6baba6 View commit details

Commits on Dec 22, 2022

  1. Correct doc comment (#775)

    The function must be of type middleware.EncoderFunc, and this has an io.Writer
    as its first argument.
    bronger authored Dec 22, 2022
    Copy the full SHA
    85f523b View commit details
  2. Copy the full SHA
    528cd32 View commit details

Commits on Jan 9, 2023

  1. Copy the full SHA
    df664f1 View commit details

Commits on Jan 11, 2023

  1. test ci (#782)

    pkieltyka authored Jan 11, 2023
    Copy the full SHA
    da1c870 View commit details
  2. update ci

    pkieltyka committed Jan 11, 2023
    Copy the full SHA
    2590e81 View commit details
  3. update ci

    pkieltyka committed Jan 11, 2023
    Copy the full SHA
    16a24da View commit details

Commits on Feb 1, 2023

  1. go 1.20.x in ci (#791)

    purificant authored Feb 1, 2023
    Copy the full SHA
    883aa98 View commit details

Commits on Feb 20, 2023

  1. Copy the full SHA
    51068a7 View commit details

Commits on Mar 31, 2023

  1. Copy the full SHA
    fd8a51e View commit details

Commits on Apr 14, 2023

  1. Update README.md

    pkieltyka authored Apr 14, 2023
    Copy the full SHA
    77d709f View commit details

Commits on May 2, 2023

  1. Copy the full SHA
    8cbdac6 View commit details
  2. Copy the full SHA
    7f28096 View commit details

Commits on Jul 13, 2023

  1. Copy the full SHA
    4b14b83 View commit details
  2. v5.0.9

    pkieltyka committed Jul 13, 2023
    Copy the full SHA
    cfd6c46 View commit details
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -21,8 +21,8 @@ jobs:

strategy:
matrix:
go-version: [1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x]
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
os: [ubuntu-latest, windows-latest]

runs-on: ${{ matrix.os }}

11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v5.0.9 (2023-07-13)

- History of changes: see https://github.com/go-chi/chi/compare/v5.0.8...v5.0.9


## v5.0.8 (2022-12-07)

- History of changes: see https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8
@@ -90,14 +95,14 @@ incremental, with the architecture and api being the same today as it was origin
makes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years
to who all help make chi better (total of 86 contributors to date -- thanks all!).

Chi has been an labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance
Chi has been a labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance
and simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size,
and always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting
middlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from
companies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of
joy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :)

For me, the asthetics of chi's code and usage are very important. With the introduction of Go's module support
For me, the aesthetics of chi's code and usage are very important. With the introduction of Go's module support
(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path
of "github.com/go-chi/chi/v4", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462.
Haha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt "/vXX" convention in the import
@@ -289,7 +294,7 @@ Cheers all, happy coding!

## v2.0.0-rc1 (2016-07-26)

- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular
- Huge update! chi v2 is a large refactor targeting Go 1.7+. As of Go 1.7, the popular
community `"net/context"` package has been included in the standard library as `"context"` and
utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other
request-scoped values. We're very excited about the new context addition and are proud to
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" />


[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
[![GoDoc Widget]][GoDoc]

`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's
especially good at helping you write large REST API services that are kept maintainable as your
2 changes: 1 addition & 1 deletion _examples/graceful/main.go
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ func service() http.Handler {
// so consider the work here as some background routine to fetch a long running
// search query to find as many results as possible, but, instead we cut it short
// and respond with what we have so far. How a shutdown is handled is entirely
// up to the developer, as some code blocks are preemptable, and others are not.
// up to the developer, as some code blocks are preemptible, and others are not.
time.Sleep(5 * time.Second)

w.Write([]byte(fmt.Sprintf("all done.\n")))
97 changes: 50 additions & 47 deletions _examples/logging/main.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
//
// Custom Structured Logger
// ========================
// This example demonstrates how to use middleware.RequestLogger,
// middleware.LogFormatter and middleware.LogEntry to build a structured
// logger using the amazing sirupsen/logrus package as the logging
// logger using the preview version of the new log/slog package as the logging
// backend.
//
// Also: check out https://github.com/goware/httplog for an improved context
// logger with support for HTTP request logging, based on the example below.
//
package main

import (
"fmt"
"net/http"
"os"
"time"

"golang.org/x/exp/slog"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/sirupsen/logrus"
)

func main() {

// Setup the logger backend using sirupsen/logrus and configure
// it to use a custom JSONFormatter. See the logrus docs for how to
// configure the backend at github.com/sirupsen/logrus
logger := logrus.New()
logger.Formatter = &logrus.JSONFormatter{
// disable, as we set our own
DisableTimestamp: true,
}
// Setup a JSON handler for the new log/slog library
slogJSONHandler := slog.HandlerOptions{
// Remove default time slog.Attr, we create our own later
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
}.NewJSONHandler(os.Stdout)

// Routes
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(NewStructuredLogger(logger))
r.Use(NewStructuredLogger(slogJSONHandler))
r.Use(middleware.Recoverer)

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
@@ -49,70 +50,70 @@ func main() {
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("oops")
})
r.Get("/add_fields", func(w http.ResponseWriter, r *http.Request) {
LogEntrySetFields(r, map[string]interface{}{"foo": "bar", "bar": "foo"})
})
http.ListenAndServe(":3333", r)
}

// StructuredLogger is a simple, but powerful implementation of a custom structured
// logger backed on logrus. I encourage users to copy it, adapt it and make it their
// logger backed on log/slog. I encourage users to copy it, adapt it and make it their
// own. Also take a look at https://github.com/go-chi/httplog for a dedicated pkg based
// on this work, designed for context-based http routers.

func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
return middleware.RequestLogger(&StructuredLogger{logger})
func NewStructuredLogger(handler slog.Handler) func(next http.Handler) http.Handler {
return middleware.RequestLogger(&StructuredLogger{Logger: handler})
}

type StructuredLogger struct {
Logger *logrus.Logger
Logger slog.Handler
}

func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
logFields := logrus.Fields{}

logFields["ts"] = time.Now().UTC().Format(time.RFC1123)
var logFields []slog.Attr
logFields = append(logFields, slog.String("ts", time.Now().UTC().Format(time.RFC1123)))

if reqID := middleware.GetReqID(r.Context()); reqID != "" {
logFields["req_id"] = reqID
logFields = append(logFields, slog.String("req_id", reqID))
}

scheme := "http"
if r.TLS != nil {
scheme = "https"
}
logFields["http_scheme"] = scheme
logFields["http_proto"] = r.Proto
logFields["http_method"] = r.Method

logFields["remote_addr"] = r.RemoteAddr
logFields["user_agent"] = r.UserAgent()

logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
handler := l.Logger.WithAttrs(append(logFields,
slog.String("http_scheme", scheme),
slog.String("http_proto", r.Proto),
slog.String("http_method", r.Method),
slog.String("remote_addr", r.RemoteAddr),
slog.String("user_agent", r.UserAgent()),
slog.String("uri", fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI))))

entry.Logger = entry.Logger.WithFields(logFields)
entry := StructuredLoggerEntry{Logger: slog.New(handler)}

entry.Logger.Infoln("request started")
entry.Logger.LogAttrs(slog.LevelInfo, "request started")

return entry
return &entry
}

type StructuredLoggerEntry struct {
Logger logrus.FieldLogger
Logger *slog.Logger
}

func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
l.Logger = l.Logger.WithFields(logrus.Fields{
"resp_status": status, "resp_bytes_length": bytes,
"resp_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
})

l.Logger.Infoln("request complete")
l.Logger.LogAttrs(slog.LevelInfo, "request complete",
slog.Int("resp_status", status),
slog.Int("resp_byte_length", bytes),
slog.Float64("resp_elapsed_ms", float64(elapsed.Nanoseconds())/1000000.0),
)
}

func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
l.Logger = l.Logger.WithFields(logrus.Fields{
"stack": string(stack),
"panic": fmt.Sprintf("%+v", v),
})
l.Logger.LogAttrs(slog.LevelInfo, "",
slog.String("stack", string(stack)),
slog.String("panic", fmt.Sprintf("%+v", v)),
)
}

// Helper methods used by the application to get the request-scoped
@@ -122,19 +123,21 @@ func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
// passes through the handler chain, which at any point can be logged
// with a call to .Print(), .Info(), etc.

func GetLogEntry(r *http.Request) logrus.FieldLogger {
func GetLogEntry(r *http.Request) *slog.Logger {
entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
return entry.Logger
}

func LogEntrySetField(r *http.Request, key string, value interface{}) {
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
entry.Logger = entry.Logger.WithField(key, value)
entry.Logger = entry.Logger.With(key, value)
}
}

func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
entry.Logger = entry.Logger.WithFields(fields)
for k, v := range fields {
entry.Logger = entry.Logger.With(k, v)
}
}
}
2 changes: 1 addition & 1 deletion _examples/rest/main.go
Original file line number Diff line number Diff line change
@@ -337,7 +337,7 @@ func (a *ArticleRequest) Bind(r *http.Request) error {

// a.User is nil if no Userpayload fields are sent in the request. In this app
// this won't cause a panic, but checks in this Bind method may be required if
// a.User or futher nested fields like a.User.Name are accessed elsewhere.
// a.User or further nested fields like a.User.Name are accessed elsewhere.

// just a post-process after a decode..
a.ProtectedID = "" // unset the protected ID
1 change: 1 addition & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -76,6 +76,7 @@ type Context struct {

// methodNotAllowed hint
methodNotAllowed bool
methodsAllowed []methodTyp // allowed methods in case of a 405
}

// Reset a routing context to its initial state.
10 changes: 7 additions & 3 deletions middleware/compress.go
Original file line number Diff line number Diff line change
@@ -135,12 +135,12 @@ func NewCompressor(level int, types ...string) *Compressor {
// 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:
// For example, add the Brotli algorithm:
//
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
//
// compressor := middleware.NewCompressor(5, "text/html")
// compressor.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
// compressor.SetEncoder("br", func(w io.Writer, level int) io.Writer {
// params := brotli_enc.NewBrotliParams()
// params.SetQuality(level)
// return brotli_enc.NewBrotliWriter(params, w)
@@ -285,7 +285,7 @@ func (cw *compressResponseWriter) isCompressable() bool {
contentType = contentType[0:idx]
}

// Is the content type compressable?
// Is the content type compressible?
if _, ok := cw.contentTypes[contentType]; ok {
return true
}
@@ -382,6 +382,10 @@ func (cw *compressResponseWriter) Close() error {
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
}

func (cw *compressResponseWriter) Unwrap() http.ResponseWriter {
return cw.ResponseWriter
}

func encoderGzip(w io.Writer, level int) io.Writer {
gw, err := gzip.NewWriterLevel(w, level)
if err != nil {
2 changes: 1 addition & 1 deletion middleware/middleware_test.go
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ func TestWrapWriterHTTP2(t *testing.T) {
}
_, rf := w.(io.ReaderFrom)
if rf {
t.Fatal("request should not have been a io.ReaderFrom")
t.Fatal("request should not have been an io.ReaderFrom")
}
_, ps := w.(http.Pusher)
if !ps {
2 changes: 1 addition & 1 deletion middleware/nocache.go
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import (
)

// Unix epoch time
var epoch = time.Unix(0, 0).Format(time.RFC1123)
var epoch = time.Unix(0, 0).UTC().Format(http.TimeFormat)

// Taken from https://github.com/mytrile/nocache
var noCacheHeaders = map[string]string{
4 changes: 3 additions & 1 deletion middleware/recoverer.go
Original file line number Diff line number Diff line change
@@ -36,7 +36,9 @@ func Recoverer(next http.Handler) http.Handler {
PrintPrettyStack(rvr)
}

w.WriteHeader(http.StatusInternalServerError)
if r.Header.Get("Connection") != "Upgrade" {
w.WriteHeader(http.StatusInternalServerError)
}
}
}()

18 changes: 18 additions & 0 deletions middleware/request_size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package middleware

import (
"net/http"
)

// RequestSize is a middleware that will limit request sizes to a specified
// number of bytes. It uses MaxBytesReader to do so.
func RequestSize(bytes int64) func(http.Handler) http.Handler {
f := func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, bytes)
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
return f
}
2 changes: 1 addition & 1 deletion middleware/throttle.go
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ type ThrottleOpts struct {

// Throttle is a middleware that limits number of currently processed requests
// at a time across all users. Note: Throttle is not a rate-limiter per user,
// instead it just puts a ceiling on the number of currentl in-flight requests
// instead it just puts a ceiling on the number of currently in-flight requests
// being processed from the point from where the Throttle middleware is mounted.
func Throttle(limit int) func(http.Handler) http.Handler {
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout})
2 changes: 1 addition & 1 deletion middleware/throttle_test.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ func TestThrottleBacklog(t *testing.T) {

var wg sync.WaitGroup

// The throttler proccesses 10 consecutive requests, each one of those
// The throttler processes 10 consecutive requests, each one of those
// requests lasts 1s. The maximum number of requests this can possible serve
// before the clients time out (5s) is 40.
for i := 0; i < 40; i++ {
Loading