Is a context package such as the one from Google or gorilla/context really needed to pass variables between handlers?
I've read a few blog posts and they seem to use some sort of context package to achieve this. Some solutions are implementing a global map with a mutex, or implementing a struct that embeds the ResponseWriter interface, but they still use a context package for storing the context.
Would it be possible to just create a map[string]interface{} in a lead handler function, and pass this object to each following handler? Here's an example of this idea.
As far as I can tell this should be thread/goroutine safe as the context is created within the currently running goroutine specific to each request.
Sorry if this has been done and answered already, articles I've seen seem to use some context package.
Update
Here's a working example using the method above, but with a context.Context struct used to pass the variables instead of a map[string]interface{}. It also uses the httprouter package (won't compile on play.golang.org, just copy and run it locally or on a server and browse to /user).
One caveat with that example, if you for instance changed the user ID in HandleUser, and read it again when it returns to the middleware, the middleware will not have the updated context value. In order for this to work, it seems you have to pass context.Context as a pointer.
No pointer: http://play.golang.org/p/3z7msFjUBU Pointer: http://play.golang.org/p/zc7iXj1fSL
Is using a pointer the proper way to achieve this functionality?
Update
Nevermind, can just pass a pointer to context http://play.golang.org/p/l7lu3aeGV3
评论:
karnd01:
Using context seems to be the standard approach in just about every framework or router..examples:
- https://github.com/go-playground/lars/blob/master/context.go#L39
- https://github.com/gin-gonic/gin/blob/develop/context.go#L538
- https://github.com/labstack/echo/blob/master/context.go#L21
- ... List goes on
pcj2bs:Ok, it's a little confusing still exactly how context.Context works, but I'll try using it since it seems to be the standard. Thank you!
beeker1121:We do this with our "framework" VividCortex/siesta. Here's a blog post that explains it: https://www.vividcortex.com/blog/2015/06/01/siesta/
daveddev:Awesome, thank you.
beeker1121:Try out https://github.com/codemodus/chain
Further, here is a discussion regarding moving net/context into the standard library and adding a context-passing handler func def: https://groups.google.com/forum/m/#!msg/golang-dev/cQs1z9LrJDU/S_9pzcAhCgAJ
daveddev:Thanks, seems like that chain package implements the same method but still uses the net/context package. Why is that? Is it just to have a place to store the context variables, rather than creating their own struct?
beeker1121:Yes. My goal was to use the most "blessed" (for lack of better term) approach.
This great article: https://joeshaw.org/net-context-and-http-handler/ along with this post: https://blog.golang.org/context and a distaste for burying the standard library without great cause brought me to a mindset which pushes away from http frameworks. There is no gain avoiding helpful libraries, but seeking a do-it-all http framework left me feeling somewhat duped and disconnected from the process of learning Go.
As for a takeway from this, it would be useful to focus on the context.Context changes that are likely to happen within the standard library. Once a context-based http handler func definition is decided upon, chain and libraries like it will be able to more easily operate interchangeably.
Grundlebuttskin:I appreciate it, and I agree, I prefer using as few libraries as possible. I'll keep my eye on that Google group thread for what they decide to do, thank you again!
beeker1121:I like the way goji handles this. It's minimal and uses net/context. The middleware chaining is pretty simple too. Anything that takes a context has a suffix of C (HandleC, UseC). go get goji.io
Grundlebuttskin:Is the context package required though? Is it just used, in goji and other packages, to store the context data/variables? In other words would it be ok to not use net/context and just use a map[string]interface{}?
beeker1121:I started using context because appengine requires it, then I continued because I like goji. The result? I can run my code with goji on appengine. There are other libraries (especially middleware such as github.com/rs/cors) that use context. I can drop these into my code without modification. You can use a map (or whatever you want) to pass data between handlers, but your code may be incompatible with other libraries. I personally benefit from the compatibility, but if you don't, then do whatever works for you.
pkieltyka:Thank you for the explanation!
tty5:Have a look at https://github.com/pressly/chi too which is a small router that is built for net/context handlers and middlewares. It also has a nice composable API for middleware chains and subrouters. Here's an example with the whole shebang for a REST service: https://github.com/pressly/chi/blob/master/_examples/rest/main.go
beeker1121:
net/context
is actually fairly close to what you are describing when you check the code: https://github.com/golang/net/blob/master/context/context.go#L138and this is how you implement something very close to your example: https://github.com/pressly/chi#middleware-handlers
So why shouldn't you roll your own? It's likely that
context.Context
field will be added tohttp.Request
in standard library'snet/http
in go 1.7
dsymonds:Thanks!
beeker1121:Why not just pass the arguments individually? Maybe you're attacking a peanut-sized problem with a sledgehammer.
mwholt:I was thinking about doing this, for instance just passing a User struct to the handler that needs it. Consider though using multiple middleware functions that do not have the User struct as a parameter. Each middleware function would have to pass this User struct in order for the end handler to receive it, and handlers of a different signature (different parameters) would be unable to use the same middleware.
By using a map[string]interface{} for context and passing it by default to every handler, all handlers will have the same signature and can then be used with any middleware.
beeker1121:Wow, timely question, I was also revisiting this today.
Here's a possible approach that's very, very simple: http://play.golang.org/p/DLLCCWUh1k
But as Joe Shaw's article (linked in another comment) mentions, you lose the benefit of satisfying certain other interfaces implemented by the underlying responseWriter type which is unexported; but if those are not used, then it seems to be a simple, acceptable way to do things.
mwholt:Quick question on that, he says this in the article:
There is also another hidden downside. There is a concrete value (with a type internal to package net/http) underlying the http.ResponseWriter that is passed into your handler. That value also implements additional interfaces from the net/http package. If you simply wrap http.ResponseWriter, your wrapper will not be implementing these additional interfaces.
You must implement these interfaces with wrapper functions if you hope to match the base http.ResponseWriter’s functionality.
Could you explain why that happens? I thought if a struct namelessly embeds http.ResponseWriter for instance, it inherits all of its methods.
beeker1121:"Inherit" is not quite the right word. When you embed a type into a struct, callers can use the embedded type's methods and fields on the embedding type. The difference from "inheritance" is that it doesn't go down the rabbit hole. In other words, embedding a ResponseWriter (an interface) gives the embedding type methods like Write and WriteHeader, but Go does not traverse the interface down to the underlying concrete type and also embed those methods and fields; so you literally get a ResponseWriter capabilities out of it but nothing more, not without asserting on the ResponseWriter itself.
This is actually a good thing for simplicity and clear code. And in your case, frankly, it may not be an issue unless you're using those underlying interfaces in your code (but be careful making that assumption).
mwholt:So for instance, would the 'response' struct defined but not exported in the net/http package be a 'concrete type' that implements ResponseWriter, but also implements the CloseNotifier interface? As such, my own struct type implementing ResponseWriter would not be able to access the CloseNotifier methods unless I defined them?
beeker1121:Correct!
You could "wrap" the underlying implementation on your own type as Joe suggests, but with CloseNotifier it's a little tricky, as he points out.
beeker1121:Ok very cool, thanks so much
Updated the post, can anyone answer the question about the context pointer?
