Coder Social home page Coder Social logo

go-chi / chi Goto Github PK

View Code? Open in Web Editor NEW
17.0K 209.0 953.0 3.66 MB

lightweight, idiomatic and composable router for building Go HTTP services

Home Page: https://go-chi.io

License: MIT License

Go 99.82% Makefile 0.18%
http router context middleware api rest-api microservices golang go

chi's Introduction

chi

GoDoc Widget

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 project grows and changes. chi is built on the new context package introduced in Go 1.7 to handle signaling, cancelation and request-scoped values across a handler chain.

The focus of the project has been to seek out an elegant and comfortable design for writing REST API servers, written during the development of the Pressly API service that powers our public API service, which in turn powers all of our client-side applications.

The key considerations of chi's design are: project structure, maintainability, standard http handlers (stdlib-only), developer productivity, and deconstructing a large system into many small parts. The core router github.com/go-chi/chi is quite small (less than 1000 LOC), but we've also included some useful/optional subpackages: middleware, render and docgen. We hope you enjoy it too!

Install

go get -u github.com/go-chi/chi/v5

Features

  • Lightweight - cloc'd in ~1000 LOC for the chi router
  • Fast - yes, see benchmarks
  • 100% compatible with net/http - use any http or middleware pkg in the ecosystem that is also compatible with net/http
  • Designed for modular/composable APIs - middlewares, inline middlewares, route groups and sub-router mounting
  • Context control - built on new context package, providing value chaining, cancellations and timeouts
  • Robust - in production at Pressly, Cloudflare, Heroku, 99Designs, and many others (see discussion)
  • Doc generation - docgen auto-generates routing documentation from your source to JSON or Markdown
  • Go.mod support - as of v5, go.mod support (see CHANGELOG)
  • No external dependencies - plain ol' Go stdlib + net/http

Examples

See _examples/ for a variety of examples.

As easy as:

package main

import (
	"net/http"

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

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("welcome"))
	})
	http.ListenAndServe(":3000", r)
}

REST Preview:

Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs in JSON (routes.json) and in Markdown (routes.md).

I highly recommend reading the source of the examples listed above, they will show you all the features of chi and serve as a good form of documentation.

import (
  //...
  "context"
  "github.com/go-chi/chi/v5"
  "github.com/go-chi/chi/v5/middleware"
)

func main() {
  r := chi.NewRouter()

  // A good base middleware stack
  r.Use(middleware.RequestID)
  r.Use(middleware.RealIP)
  r.Use(middleware.Logger)
  r.Use(middleware.Recoverer)

  // Set a timeout value on the request context (ctx), that will signal
  // through ctx.Done() that the request has timed out and further
  // processing should be stopped.
  r.Use(middleware.Timeout(60 * time.Second))

  r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hi"))
  })

  // RESTy routes for "articles" resource
  r.Route("/articles", func(r chi.Router) {
    r.With(paginate).Get("/", listArticles)                           // GET /articles
    r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017

    r.Post("/", createArticle)                                        // POST /articles
    r.Get("/search", searchArticles)                                  // GET /articles/search

    // Regexp url parameters:
    r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto

    // Subrouters:
    r.Route("/{articleID}", func(r chi.Router) {
      r.Use(ArticleCtx)
      r.Get("/", getArticle)                                          // GET /articles/123
      r.Put("/", updateArticle)                                       // PUT /articles/123
      r.Delete("/", deleteArticle)                                    // DELETE /articles/123
    })
  })

  // Mount the admin sub-router
  r.Mount("/admin", adminRouter())

  http.ListenAndServe(":3333", r)
}

func ArticleCtx(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    articleID := chi.URLParam(r, "articleID")
    article, err := dbGetArticle(articleID)
    if err != nil {
      http.Error(w, http.StatusText(404), 404)
      return
    }
    ctx := context.WithValue(r.Context(), "article", article)
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

func getArticle(w http.ResponseWriter, r *http.Request) {
  ctx := r.Context()
  article, ok := ctx.Value("article").(*Article)
  if !ok {
    http.Error(w, http.StatusText(422), 422)
    return
  }
  w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}

// A completely separate router for administrator routes
func adminRouter() http.Handler {
  r := chi.NewRouter()
  r.Use(AdminOnly)
  r.Get("/", adminIndex)
  r.Get("/accounts", adminListAccounts)
  return r
}

func AdminOnly(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    perm, ok := ctx.Value("acl.permission").(YourPermissionType)
    if !ok || !perm.IsAdmin() {
      http.Error(w, http.StatusText(403), 403)
      return
    }
    next.ServeHTTP(w, r)
  })
}

Router interface

chi's router is based on a kind of Patricia Radix trie. The router is fully compatible with net/http.

Built on top of the tree is the Router interface:

// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
	http.Handler
	Routes

	// Use appends one or more middlewares onto the Router stack.
	Use(middlewares ...func(http.Handler) http.Handler)

	// With adds inline middlewares for an endpoint handler.
	With(middlewares ...func(http.Handler) http.Handler) Router

	// Group adds a new inline-Router along the current routing
	// path, with a fresh middleware stack for the inline-Router.
	Group(fn func(r Router)) Router

	// Route mounts a sub-Router along a `pattern`` string.
	Route(pattern string, fn func(r Router)) Router

	// Mount attaches another http.Handler along ./pattern/*
	Mount(pattern string, h http.Handler)

	// Handle and HandleFunc adds routes for `pattern` that matches
	// all HTTP methods.
	Handle(pattern string, h http.Handler)
	HandleFunc(pattern string, h http.HandlerFunc)

	// Method and MethodFunc adds routes for `pattern` that matches
	// the `method` HTTP method.
	Method(method, pattern string, h http.Handler)
	MethodFunc(method, pattern string, h http.HandlerFunc)

	// HTTP-method routing along `pattern`
	Connect(pattern string, h http.HandlerFunc)
	Delete(pattern string, h http.HandlerFunc)
	Get(pattern string, h http.HandlerFunc)
	Head(pattern string, h http.HandlerFunc)
	Options(pattern string, h http.HandlerFunc)
	Patch(pattern string, h http.HandlerFunc)
	Post(pattern string, h http.HandlerFunc)
	Put(pattern string, h http.HandlerFunc)
	Trace(pattern string, h http.HandlerFunc)

	// NotFound defines a handler to respond whenever a route could
	// not be found.
	NotFound(h http.HandlerFunc)

	// MethodNotAllowed defines a handler to respond whenever a method is
	// not allowed.
	MethodNotAllowed(h http.HandlerFunc)
}

// Routes interface adds two methods for router traversal, which is also
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
type Routes interface {
	// Routes returns the routing tree in an easily traversable structure.
	Routes() []Route

	// Middlewares returns the list of middlewares in use by the router.
	Middlewares() Middlewares

	// Match searches the routing tree for a handler that matches
	// the method/path - similar to routing a http request, but without
	// executing the handler thereafter.
	Match(rctx *Context, method, path string) bool
}

Each routing method accepts a URL pattern and chain of handlers. The URL pattern supports named params (ie. /users/{userID}) and wildcards (ie. /admin/*). URL parameters can be fetched at runtime by calling chi.URLParam(r, "userID") for named parameters and chi.URLParam(r, "*") for a wildcard parameter.

Middleware handlers

chi's middlewares are just stdlib net/http middleware handlers. There is nothing special about them, which means the router and all the tooling is designed to be compatible and friendly with any middleware in the community. This offers much better extensibility and reuse of packages and is at the heart of chi's purpose.

Here is an example of a standard net/http middleware where we assign a context key "user" the value of "123". This middleware sets a hypothetical user identifier on the request context and calls the next handler in the chain.

// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // create new context from `r` request context, and assign key `"user"`
    // to value of `"123"`
    ctx := context.WithValue(r.Context(), "user", "123")

    // call the next handler in the chain, passing the response writer and
    // the updated request object with the new context value.
    //
    // note: context.Context values are nested, so any previously set
    // values will be accessible as well, and the new `"user"` key
    // will be accessible from this point forward.
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

Request handlers

chi uses standard net/http request handlers. This little snippet is an example of a http.Handler func that reads a user identifier from the request context - hypothetically, identifying the user sending an authenticated request, validated+set by a previous middleware handler.

// HTTP handler accessing data from the request context.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
  // here we read from the request context and fetch out `"user"` key set in
  // the MyMiddleware example above.
  user := r.Context().Value("user").(string)

  // respond to the client
  w.Write([]byte(fmt.Sprintf("hi %s", user)))
}

URL parameters

chi's router parses and stores URL parameters right onto the request context. Here is an example of how to access URL params in your net/http handlers. And of course, middlewares are able to access the same information.

// HTTP handler accessing the url routing parameters.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
  // fetch the url parameter `"userID"` from the request of a matching
  // routing pattern. An example routing pattern could be: /users/{userID}
  userID := chi.URLParam(r, "userID")

  // fetch `"key"` from the request context
  ctx := r.Context()
  key := ctx.Value("key").(string)

  // respond to the client
  w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}

Middlewares

chi comes equipped with an optional middleware package, providing a suite of standard net/http middlewares. Please note, any middleware in the ecosystem that is also compatible with net/http can be used with chi's mux.

Core middlewares


chi/middleware Handler description
AllowContentEncoding Enforces a whitelist of request Content-Encoding headers
AllowContentType Explicit whitelist of accepted request Content-Types
BasicAuth Basic HTTP authentication
Compress Gzip compression for clients that accept compressed responses
ContentCharset Ensure charset for Content-Type request headers
CleanPath Clean double slashes from request path
GetHead Automatically route undefined HEAD requests to GET handlers
Heartbeat Monitoring endpoint to check the servers pulse
Logger Logs the start and end of each request with the elapsed processing time
NoCache Sets response headers to prevent clients from caching
Profiler Easily attach net/http/pprof to your routers
RealIP Sets a http.Request's RemoteAddr to either X-Real-IP or X-Forwarded-For
Recoverer Gracefully absorb panics and prints the stack trace
RequestID Injects a request ID into the context of each request
RedirectSlashes Redirect slashes on routing paths
RouteHeaders Route handling for request headers
SetHeader Short-hand middleware to set a response header key/value
StripSlashes Strip slashes on routing paths
Sunset Sunset set Deprecation/Sunset header to response
Throttle Puts a ceiling on the number of concurrent requests
Timeout Signals to the request context when the timeout deadline is reached
URLFormat Parse extension from url and put it on request context
WithValue Short-hand middleware to set a key/value on the request context

Extra middlewares & packages

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


package description
cors Cross-origin resource sharing (CORS)
docgen Print chi.Router routes at runtime
jwtauth JWT authentication
hostrouter Domain/host based request routing
httplog Small but powerful structured HTTP request logging
httprate HTTP request rate limiter
httptracer HTTP request performance tracing library
httpvcr Write deterministic tests for external sources
stampede HTTP request coalescer

context?

context is a tiny pkg that provides simple interface to signal context across call stacks and goroutines. It was originally written by Sameer Ajmani and is available in stdlib since go1.7.

Learn more at https://blog.golang.org/context

and..

Benchmarks

The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark

Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x

BenchmarkChi_Param          	3075895	        384 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Param5         	2116603	        566 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Param20        	 964117	       1227 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParamWrite     	2863413	        420 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubStatic   	3045488	        395 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubParam    	2204115	        540 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubAll      	  10000	     113811 ns/op	    81203 B/op    406 allocs/op
BenchmarkChi_GPlusStatic    	3337485	        359 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlusParam     	2825853	        423 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlus2Params   	2471697	        483 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlusAll       	 194220	       5950 ns/op	     5200 B/op     26 allocs/op
BenchmarkChi_ParseStatic    	3365324	        356 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParseParam     	2976614	        404 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Parse2Params   	2638084	        439 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParseAll       	 109567	      11295 ns/op	    10400 B/op     52 allocs/op
BenchmarkChi_StaticAll      	  16846	      71308 ns/op	    62802 B/op    314 allocs/op

Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc

NOTE: the allocs in the benchmark above are from the calls to http.Request's WithContext(context.Context) method that clones the http.Request, sets the Context() on the duplicated (alloc'd) request and returns it the new request object. This is just how setting context on a request in Go works.

Credits

We'll be more than happy to see your contributions!

Beyond REST

chi is just a http router that lets you decompose request handling into many smaller layers. Many companies use chi to write REST services for their public APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server system or network of microservices.

Looking beyond REST, I also recommend some newer works in the field:

  • webrpc - Web-focused RPC client+server framework with code-gen
  • gRPC - Google's RPC framework via protobufs
  • graphql - Declarative query language
  • NATS - lightweight pub-sub

License

Copyright (c) 2015-present Peter Kieltyka

Licensed under MIT License

chi's People

Contributors

0daryo avatar alexandear avatar angelofallars avatar beornf avatar c2h5oh avatar chemidy avatar cyx avatar flimzy avatar gdm85 avatar joseustra avatar kanocz avatar lxfontes avatar mafredri avatar mmatczuk avatar muesli avatar mvdan avatar nkovacs avatar pearcedavis avatar pkieltyka avatar purificant avatar rafavaliev avatar shubhaankar-sharma avatar someone1 avatar thedevsaddam avatar therandomcharacter avatar tmatias avatar vasayxtx avatar vladdy avatar vojtechvitek avatar xiam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chi's Issues

How to get params without route context?

Is it possible to get param from the handler without route context?

When i'm testing handler, i need to be able to retrieve params with chi.URLParam(), but when i try to do it outside router, i'm getting panic

How to handle "/" with trailing slashes in a mounted router?

Thanks for this great framework, currently I'm moving a project to chi, I want to handle all these URLs: /blog, /blog/, /blog/list.
I can do this in a plain router like this:

// r is a Router variable
r.Get("/blog", blogHandler)
r.Get("/blog/", blogWithTrailingSlashHandler)
r.Get("/blog/list", blogListHandler)

but if I mount a nested router for /blog, how can I handle /blog/?

// r is a Router variable
blogRouter := chi.NewRouter()
blogRouter.Get("/", blogHandler)
// this does not work
blogRouter.Get("//", blogWithTrailingSlashHandler)
blogRouter.Get("/list", blogListHandler)
r.Mount("/blog", blogRouter)

Hi all, who's using chi in production?

Hey everyone, I'm curious to hear which companies are using chi in production, if you see this post and use it in your products, please let me know. I'd like to include a list in the README, it's inspiring to hear all the awesome companies using chi and it helps other get comfortable to adopt it with success from others.

Route params mixup

First of all, love the project & the philosophy behind it - it's the perfect approach for Go 1.7+ IMO ๐Ÿ‘

But I'm having an issue with some supporting some legacy route formats. Requests map to the correct handlers but the parameters don't match up correctly. Here's a simplified example replacing the routes in TestTree to show the issue I'm having:

func TestTree(t *testing.T) {
    hDate := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    hCat := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

    tr := &node{}

    tr.InsertRoute(mGET, "/items/:year/:month", hDate)
    tr.InsertRoute(mGET, "/items/:category", hCat)

    tests := []struct {
        r string            // input request path
        h http.Handler      // output matched handler
        p map[string]string // output params
    }{
        {r: "/items/2016/08", h: hDate, p: map[string]string{"year": "2016", "month": "08"}},
        {r: "/items/things", h: hCat, p: map[string]string{"category": "things"}},
    }

That results in:

--- FAIL: TestTree (0.00s)
    tree_test.go:59: input [1]: find '/items/things' expecting params:map[category:things] , got:map[year:things]

Note that the /items/:category handler is correctly used but the parameter is called :year instead from the previous route.

It looks like it's due to how the tree is built with the name of the parameter ignored so it then gets the name from the previous node:

2016/08/23 10:40:56 [node 0 parent:0] typ:0 prefix: label: numEdges:1 isLeaf:false
2016/08/23 10:40:56 [node 1 parent:0] typ:0 prefix:/items/ label:/ numEdges:1 isLeaf:false
2016/08/23 10:40:56 [node 2 parent:1] typ:2 prefix::year label:: numEdges:1 isLeaf:true handler:map[512:<nil> 4:0x90450]
2016/08/23 10:40:56 [node 3 parent:2] typ:0 prefix:/ label:/ numEdges:1 isLeaf:false
2016/08/23 10:40:56 [node 4 parent:3] typ:2 prefix::month label:: numEdges:0 isLeaf:true handler:map[512:<nil> 4:0x90440]

Is this simply not supported (I know many Go routers are strict about the route variations, others such as Echo cope better with suffix variations) or is it a bug?

Many thanks.

Remove github restriction on markdown docs links

Relative links in markdown work on more places than just github. They work very nicely in our on-premis gitlab installation for example.

Is there any harm in removing this restriction? If that is not desired, could an additional option be added to MarkdownOpts to force link generation?

chi's future

Instead of an additional parameter of type context.Context with a new handler type, an alternative approach was favoured in the standard library.
See: golang/go@c1c7547

graceful shutdown?

@pkieltyka, How would you implement graceful feature for shutdown? Do we need it? If not, how would you implement this feature along side with chi?

Getting more done in GitHub with ZenHub

Hola! @xiam has created a ZenHub account for the pressly organization. ZenHub is the leading team collaboration and project management solution built for GitHub.


How do I use ZenHub?

To get set up with ZenHub, all you have to do is download the browser extension and log in with your GitHub account. Once you do, youโ€™ll get access to ZenHubโ€™s complete feature-set immediately.

What can ZenHub do?

ZenHub adds a series of enhancements directly inside the GitHub UI:

  • Real-time, customizable task boards for GitHub issues;
  • Burndown charts, estimates, and velocity tracking based on GitHub Milestones;
  • Personal to-do lists and task prioritization;
  • โ€œ+1โ€ button for GitHub issues and comments;
  • Drag-and-drop file sharing;
  • Time-saving shortcuts like a quick repo switcher.

Add ZenHub to GitHub

Still curious? See more ZenHub features or read user reviews. This issue was written by your friendly ZenHub bot, posted by request from @xiam.

ZenHub Board

appengine

i am stuck trying to use chi router for an appengine app, but because it uses "context" package from the standard library it can not build ... anyother trick that forking the router and replace "context" by "golang.org/x/net/context" ... not very happy to be stuck by this delay and inconsistency between golang 1.7 and appengine standard environment ...

Using Mux.pool

Hello, I'm using your http router chi. I like it some much, but there are several thing that I don't understand at all. Could you explain me that?

  1. Mux.pool is a pool of chi.Context. If I understood well, only reason why, is you are trying to have less allocation. But If this is true, why you don't have just one global pool for every Mux? It should not be more interesting share bigger structures like Mux into pool?
  2. In Mux.Group you again create new Mux - that should be copy of previous one. But it does not have initialized pool (neither shared with parent, nor created new). Is it good?
  3. In Mux.ServeHTTPC you use pool.Get() to obtain new Context. After that, next handler is called and at the end, context is reseted and return back to pool. It sound good, but if appeared some panic during next handler, context won't be returned back. Is it an intention or mistake?

v2 branch

Could you move the master branch to a v2 branch in order to be compatible with gopkg.in and glide ?

Type chi.Middleware

Can we type func(next chi.Handler) chi.Handler ?

I use New.. methods for some middleware to add stuff to them, which results in:

func NewSomethingCtx(stuff *Stuff)  func(next chi.Handler) chi.Handler  {
    return func(next chi.Handler) chi.Handler {
        // stuff can be used here to set something up (should be done once, as mentioned in #10).
        return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
            // stuff can also be used here, on each request.
            // actual middleware.
        })
    }
}

If would be nice if we could let NewSomethingCtx return a chi.Middleware.

SkipTrailingSlash

In project, that I'm writing for some handlers I need to remove trailing slash (REST server, where I cannot influence form of requests). I spent a few hours yesterday, but I have not find any good looking solution.

Problem, that when I handle something inside r.Group or r.Mount, inside this handler next handle to root does not work.
Example: In your example account code I don't want be able to handle only
curl 127.0.0.1:3333/accounts/123
but also
curl 127.0.0.1:3333/accounts/123/
with the same handler.

Inside accountsRouter there is no good looking solution how to use the same handler for both cases.
r.Get("/", getAccount)
handles only the first form. When I tried to change it into r.Get("//", ...) it does not handle anything.

Possible solution (if I understand well are):
a) Use two handlers for r.Route("/:accountID", ...) and r.Route("/:accountID/", ...) with same "next" function. That looks confusing, and also I can't use anonymous function. Also when some trailing slashes can appears in many handlers, I have to repeat such bad code for each handler.

b) Write middleware SkipTrailingSlash, that checks, whether path ends with /, and if it, just remove it. I made it, it works fine, but I have to modify chi.Context, because routeCtxKey is not visible neither from my application nor chi.middleware. I had to change it, but it is not nice, because main idea was publish internal routing mechanism and make it visible for everyone.

c) Add flag SkipTrailingSlash, into Mux. I thing that this is the best idea yet, but I welcome any better proposals. I could write it, that is not problem, but do you think it is a good idea?

http.Handler vs http.HandlerFunc

I couldn't grep it in the source code,

Is there a way to handle http.Handler using chi.NewRouter().Get(...) or Post(...)?

Should there be a GetFunc(pattern string, h http.HandlerFunc) and Get(pattern string, h http.Handler)?

If I have to choose one, http.Handler is more flexible than its func alternative.

Render: managing request and response payload as types

The render subpkg is pretty cool, and its great for managing the different kinds of content types a response may need, including streams. However, one of the important parts for a maintainable API is to manage the request and response payloads (the inputs and the outputs of an endpoint).

For request payloads, one simple idea is to have a Bind() middleware used in chi's r.With() inline middleware routing, that will take a request body and unmarshal it to a kind of struct. Perhaps.

type ArticleRequest {
  ID int64 `json:"id"`
  Name string `json:"name"`
}
// ...
r.Get("/", index)
r.With(render.Request(&ArticleRequest{})).Post("/articles", newArticle)

.. something like that.. the render.Request() would make a new &ArticleRequest{} object and put it on the context under render.RequestKey or something.. its a decent plan, except it would require some kind of reflection to make the new &ArticleRequest{} object.

As for response payloads, that should be much simpler, just..

type ArticleResponse {
  *data.Article // embed the core Article type
  // add more methods on it..
}
//...
render.Respond(w, r, &ArticleResponse{a})

if someone has other ideas too that would be awesome!

Middleware function gets recreated every function call

Steps to reproduce:

Use this middleware func:

func CtxMiddleware(next chi.Handler) chi.Handler {
  fmt.Println("mw recreated!")
  return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    ctx = context.WithValue(ctx, "key", "value")
    next.ServeHTTPC(ctx, w, r)
  })
}

"mw recreated!" should be echoed once on router.Use() call, but it echoes every time a request is handled.

runtime error: invalid memory address or nil pointer dereference

I accidentally mounted an empty router, I didn't realise it, and it was hard to find out the problem due to the weird error message.

Code to reproduce this issue

package main

import (
    "net/http"
    "github.com/pressly/chi"
)

func main() {
    r := chi.NewRouter()

    apiRouter := chi.NewRouter()
    r.Handle("/api", apiRouter)

    http.ListenAndServe(":3000", r)
}

127.0.0.1:3000/abc: 404 page.
127.0.0.1:3000/api: panics.
127.0.0.1:3000/api/abc: panics.

Full error log:

2016/05/21 23:18:24 http: panic serving 127.0.0.1:49239: runtime error: invalid memory address or nil pointer dereference
goroutine 8 [running]:
net/http.(*conn).serve.func1(0xc82005e980)
    /usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1389 +0xc1
panic(0x325240, 0xc82000a1a0)
    /usr/local/Cellar/go/1.6/libexec/src/runtime/panic.go:426 +0x4e9
github.com/pressly/chi.(*Mux).ServeHTTPC(0xc8200125a0, 0x1a55910, 0xc820010cc0, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    /Users/Mgen/go/src/github.com/pressly/chi/mux.go:283 +0x648
github.com/pressly/chi.treeRouter.ServeHTTPC(0xc820014e40, 0x0, 0x1a55910, 0xc820010cc0, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    /Users/Mgen/go/src/github.com/pressly/chi/mux.go:352 +0x324
github.com/pressly/chi.(*treeRouter).ServeHTTPC(0xc82000af80, 0x1a55910, 0xc820010cc0, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    <autogenerated>:18 +0xca
github.com/pressly/chi.(*Mux).ServeHTTPC(0xc820012550, 0x0, 0x0, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    /Users/Mgen/go/src/github.com/pressly/chi/mux.go:266 +0x112
github.com/pressly/chi.(*Mux).ServeHTTP(0xc820012550, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    /Users/Mgen/go/src/github.com/pressly/chi/mux.go:258 +0x4b
net/http.serverHandler.ServeHTTP(0xc82005e080, 0x1a55858, 0xc8200f41a0, 0xc8200c2380)
    /usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:2081 +0x19e
net/http.(*conn).serve(0xc82005e980)
    /usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:1472 +0xf2e
created by net/http.(*Server).Serve
    /usr/local/Cellar/go/1.6/libexec/src/net/http/server.go:2137 +0x44e

JSON response

Now it is not very comfortable to send a JSON response:

type Foo struct {
    Bar string
}

func foo(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    b, err := json.Marshal(&Foo{Bar: "bar"})
    if err != nil {
        http.Error(w, err.Error(), 422)
        return
    }
    w.WriteHeader(http.StatusOK)
    w.Write(b)
}

What about add the JSON(code int, obj interface{}) method like gin or echo?

Allow overwrite function methodNotAllowedHandler

I am just starting to use your project which seems excellent to me, but got into trouble with hard coded methodNotAllowedHandler function. I have to return JSON object on every error (like in JSON API spec) and I am not able to specify my own response to this error. Or can you suggest any solution to this? Thanks.

Add support for fasthttp

fasthttp provides some considerable performance improvements.

I had forked chi and make it work for fasthttp. The result of the benchmark on my host show that chi/fasthttp is very close to echo/fasthttp and iris:

  • chi/fasthttp
benchmarking chi...
Running 10s test @ http://localhost:8080/teams/x-men/members/wolverine
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.00ms    2.40ms  28.22ms   89.64%
    Req/Sec    53.71k     6.43k   72.56k    69.50%
  1076944 requests in 10.09s, 167.41MB read
Requests/sec: 106702.11
Transfer/sec:     16.59MB
benchmarking with pipleline...
Running 10s test @ http://localhost:8080
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.80ms    7.96ms 124.13ms   84.49%
    Req/Sec   266.17k    16.76k  320.25k    74.50%
  5299709 requests in 10.01s, 823.83MB read
Requests/sec: 529503.08
Transfer/sec:     82.31MB
  • echo/fasthttp
benchmarking echo/fasthttp...
Running 10s test @ http://localhost:8080/teams/x-men/members/wolverine
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.99ms    2.32ms  22.67ms   89.44%
    Req/Sec    53.56k     6.64k   90.73k    74.37%
  1070567 requests in 10.09s, 166.42MB read
Requests/sec: 106122.28
Transfer/sec:     16.50MB
benchmarking with pipleline...
Running 10s test @ http://localhost:8080
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.99ms    7.70ms 131.53ms   83.88%
    Req/Sec   232.53k    14.14k  273.31k    72.50%
  4630831 requests in 10.01s, 719.86MB read
Requests/sec: 462480.63
Transfer/sec:     71.89MB

Would you like to add one branch for fasthttp?

Graceful shutdown

I'd like to explore ideas in how to gracefully shutdown a running http server. Now that go 1.7 supports context, we can use the request context to signal to all running requests the intent to shutdown the server and finish up their work because we're going to be exiting the program.

Initially the thought was to have a single parent server context where each routing context was derived, and if the parent was shutting down, cancelling the server context would signal to stop each child context. The problem here, is cancelling isn't the right thing to do, and it's actually the only option thats built into context.Context itself (see: golang/go#14660 (comment)).

On second thought, we could include a ShutdownCh on the request context, that would enhance each request to signal whenever the server is intending to finish up its work and stop processing. This is the best idea I have so far.

Websockets

Hi! Not sure what's going on, but it seems that gorilla websockets (and native ones by the looks of it) aren't compatible with writers supplied by Chi.

    hijacker, ok := w.(http.Hijacker)
    if !ok {
        return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
    }
    return hijacker.Hijack()

Hijack method here returns http: Hijack is incompatible with use of CloseNotifier error (https://golang.org/src/net/http/server.go#L154)

Configurable logger middleware

The middlewares in the repo work really well, cheers for that.

I use chi to serve dynamic content as well as static content. I have the logger middleware set up on the whole thing, which is good but it's too verbose for static content.

I thought about enabling it only for non-static content, but I would still like to get logs about 4XX's (and possibly 5XX's?) on the static content, for example if a link to an image is broken.

As far as I can tell the logger middleware isn't configurable, but some others are. A few options come to mind:

  • Fork the middleware and create a new configurable middleware (e.g. with a mode flag that tells it what is to be logged) - this would mean code duplication, as far as I can tell
  • Modify the current middleware - is this possible? I assume not, thinking that with v2 the api is frozen.
  • Make the middlewares extendible at some level to enable modifications and tweaks like these - don't know if this makes sense at all

Also, I was thinking that chi would really benefit from a "contrib" middleware repo. Kind of like what golang.org/x/ is to std. Only add to chi what is really basic and important, and the rest can live in a separate repo like chi-contrib where the community plays a bigger part. This would also allow changing the API of certain middlewares without breaking backwards compatibility, as these would not be released in tags along with the main repo.

Weird (Incorrect?) URLParam results

I am not sure if this is a bug, or me just not using the routing correctly. I have some odd routes:

    r.Route("/api/v1", func(r chi.Router) {
        r.Route("/files", func(r chi.Router) {
            r.Get("/:foo/hello/:bar", a)
            r.Get("/:bar/world/:baz", b)
            r.Get("/:bar/sups/:baz/:foo", c)
        })
    })

chi correctly routes the request, however the params are not set correctly. For example a request to /api/v1/files/a/sups/b/c correctly routes to func c() however the decoded params look like {"bar":"","baz":"b","foo":"a","req":"c"}

Here is an example with failing tests:

https://gist.github.com/DylanJ/4d3c7d1ea6f99255df823be717e99bcc

Mux methods accept a HandlerFunc instead of Handler

I see the change that converts the mux signatures to accept an http.HandlerFunc instead of an http.Handler here: 2b1ed9c. There's no rationale given for the change.

In the std lib, http.Handler is the handler type and http.HandlerFunc is an adapter for functions to match the http.Handler interface. With the current mux implementation, anything that is an http.Handler type must be passed in like this:

router.Get("/", myHandler.ServeHTTP)

The ServeHTTP function is converted back into a Handler by being wrapped by the HandlerFunc type. It seems redundant and, in general I believe, breaks the expectation that a mux will accept a Handler implementation. This is also inconsistent with the Handle function of the mux and the middleware type definition of func(http.Handler) http.Handler.

Most projects in the pre-1.7 space handled this case by having two signatures for each mux function:

func (m *Mux) Get(path string, h http.Handler) {}
func (m *Mux) GetF(path string, h http.HandlerFunc) {}

I'd like to get http.Handler support back and am willing to put in a patch if we can figure out the right implementation for this project.

FileServer prefix path problem

import (
    "os"
    "net/http"
    "github.com/pressly/chi"
)

r := chi.NewRouter()

workDir, _ := os.Getwd()
r.FileServer("/static", http.Dir(filepath.Join(workDir, "static")))

Expected URL: /static/moment/moment.min.js
But I got: /moment/moment.min.js

How can I set it so the /static prefix is preserved?

v2 Roadmap

Below are remaining things I'd like to complete for v2 release:

  • Finalize chi.Router interface
  • Syntax: chi.URLParam(r, "id")
  • Example of Handler unit tests in v2
  • Include RoutePattern on the request context, which is the pattern string that matches a request, helpful for instrumentation middlewares (#45)
  • Example with handlers on a struct
  • Finalize render.Presenter (#47)
  • Godoc
  • Document examples (render, etc.)
  • Updated benchmarks
  • Real-life example with chi+config+Makefile+upper+tests+templates+http2

[Feature Request]Static File serving

When I'm using chi for a project with swagger-ui, I feel it is not so convenient for serving static files. What about adding the static file serving feature like this:

func (mx *Mux) Static(path, root string) {
    fs := http.StripPrefix(path, http.FileServer(http.Dir(root)))
    mx.Get(path+"/*", func(w http.ResponseWriter, r *http.Request) {
        fs.ServeHTTP(w, r)
    })
}

Support a mounted router that does not inherit middleware from the parent.

I have a chi server configured roughly:

package ui

import (
    "fmt"
    "net/http"

    "github.com/pressly/chi"
    "github.com/pressly/chi/middleware"
)

func ListenAndServe() error {
    r := chi.NewRouter()

    r.Use(middleware.RequestID)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.RedirectSlashes)
    r.Use(session)

    r.Get("/", homeGet)

    r.Mount("/static", staticFiles())

    return http.ListenAndServeTLS(fmt.Sprintf(":%d", *listenPort), *certFile, *keyFile, r)
}

func staticFiles() http.Handler {
    r := chi.NewRouter()

    // Do nothing, but implement http.Handler
    r.Use(func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            next.ServeHTTP(w, r)
        })
    })

    // Serve static files
    r.Mount("/",
        http.StripPrefix(
            "/static/",
            http.FileServer(http.Dir(*filesPath+"/static/")),
        ),
    )

    return r
}

It is going to serve a fairly large website and the standard handlers are web pages, and the mounted /static path are all of the static assets needed for the page, i.e. JS, CSS, images, etc.

I would like to add my own custom middleware to each web page that I serve, at the top level, and this will do things like look at whether a session cookie exists and put that in the context, determine basic authentication and authorisation as part of that session middleware and put that in the context too.

The issue I have is that adding such middleware results in the mounted static path handler receiving that middleware too.

I would like a form of Mount that resets the middleware (does not inherit).

This would allow the mounter to explicitly add only the middleware required by the mount point, and this in effect allows the mounted route to not have some other middleware (the stuff that is going to figure out session cookies, for which static files need not know about).

I'm not sure whether this is on your roadmap or whether you've encountered a similar need.

Ability to join routers.

It could be useful to be able to join multiple routers (but not forcing the second router to have a specific path).

My handlers is split into multiple packages and each package has NewRouter() function returning chi.Router.

The idea is for my main application, to import packages and then do

mainRouter.Join(package1.NewRouter())
mainRouter.Join(package2.NewRouter())

I'm currently doing it this way, but it's not that elegant

package1.AddRoutes(mainRouter)
package2.AddRoutes(mainRouter)

Mount is not useful, because it forces to pick a specific path for router.

wildcards router not working when path conflict

hi, now I am using chi on golang 1.7, I meet such a problem:

    r := chi.NewRouter()

    // A good base middleware stack
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    r.Options("/*", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hi"))
    })

    r.Get("/foo", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("foo"))
    })

    http.ListenAndServe(":3333", r)

a options request for "/foo" just return 405 method not allow.......:(

404 not found when expecting 405 method not allowed

A request sent to an existing resource without an handler for that method should return http 405.
If I understand correctly this should be the behaviour in

// Check if method is supported by chi
method, ok := methodMap[r.Method]
if !ok {
    methodNotAllowedHandler(ctx, w, r)
    return
}

but I am getting always an http 404 error.
methodMap is initialized at startup and a method will be always found in the map, after that an handler will not be found and we finish in the handler not found case.

Maybe this control has been implemented to manage unexpected http methods that the router will not be able to handle in every case. Am I right?

Ability to set the base context

I'm not sure there's a facility for this already, but after searching through the code and docs, there doesn't seem to be a way to do it without making a middleware.

Basically the usecase is like:

ctx := context.WithValue(context.Background(), "foo", "bar")

mux := chi.NewMux()
mux.SetContext(ctx) // or could be in the constructor, or a functional option thing?

In any case the underlying goal is to just easily set the base context. Let me know if this makes sense.

Subrouter hides parent routes

package main

import (
    "log"
    "net/http"

    "github.com/pressly/chi"
)

func main() {
    r := chi.NewRouter()

    // Parent route -- /foo
    r.Get("/foo", func(w http.ResponseWriter, r *http.Request) {
        log.Println("/foo")
    })

    // Subrouter -- defining /foo/bar
    r.Mount("/foo", subrouter())

    http.ListenAndServe(":3333", r)
}

func subrouter() http.Handler {
    r := chi.NewRouter()
    r.Get("/bar", func(w http.ResponseWriter, r *http.Request) {
        log.Println("/foo/bar")
    })
    return r
}
  1. $ curl -v localhost:3333/foo
    > GET /foo HTTP/1.1
    < HTTP/1.1 404 Not Found
    Not Found

The route **wasn't matched** -- seems like the subrouter hid it.
  1. $ curl -v localhost:3333/foo/
    > GET /foo/ HTTP/1.1
    < HTTP/1.1 404 Not Found
    404 page not found

Again.. but with **different body** -- different NotFound handler?
  1. $ curl -v localhost:3333/foo/bar
    > GET /foo/bar HTTP/1.1
    < HTTP/1.1 200 OK
    /foo/bar

OK

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.