go-chi / chi Goto Github PK
View Code? Open in Web Editor NEWlightweight, idiomatic and composable router for building Go HTTP services
Home Page: https://go-chi.io
License: MIT License
lightweight, idiomatic and composable router for building Go HTTP services
Home Page: https://go-chi.io
License: MIT License
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.
Related to #9
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.
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?
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
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 ...
golang 1.7 is still unstable for now....
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.
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.
Prevents misuse as in, https://github.com/pressly/chi/pull/64/files
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?
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!
https://github.com/nhooyr/color
It fully supports terminfo, but not windows.
Why not make them absolute? By absolute, I mean that they begin with '/'
instead of having to prepend '/'
like https://github.com/pressly/chi/blob/master/mux.go#L231
Under what circumstances would relative wildcard parameters be useful?
Below are remaining things I'd like to complete for v2 release:
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?
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
It's not currently possible to customize the output of the Logger middleware at all. I spent a little time on that, will send a PR shortly.
@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?
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)
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
Update the testRequest
helper or get rid of it altogether (I like this more - we might want to test for specific headers for example)
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.
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.
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?
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.
Hola! @xiam has created a ZenHub account for the pressly organization. ZenHub is the leading team collaboration and project management solution built for GitHub.
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.
ZenHub adds a series of enhancements directly inside the GitHub UI:
Still curious? See more ZenHub features or read user reviews. This issue was written by your friendly ZenHub bot, posted by request from @xiam.
parentRouter.Mount("/path", childRouter)
func childRouter() chi.Router {
// some routes, but no "/"
}
Request to /path
will return 404
Request to /path/
will cause a panic
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.
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
.
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
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
}
$ 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.
$ 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?
$ curl -v localhost:3333/foo/bar
> GET /foo/bar HTTP/1.1
< HTTP/1.1 200 OK
/foo/bar
OK
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.
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?
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)
})
}
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.......:(
..useful for instrumentation middleware
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:
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
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?
Hello, if the input request of ServeHTTP contains an existing context (but not a chi.Context), the context is discarded and a new chi.Context taken from the pool is set in the request. The data of the previous context is lost.
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.
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?
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:
mode
flag that tells it what is to be logged) - this would mean code duplication, as far as I can tellAlso, 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.
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.