Including context objects through multiple HTTP handlers in golang

I just read this blog post about creating a function type and implementing the .ServeHTTP() method for this function in order to be able to handle errors. For instance:

 type appError struct { Error error Message string Code int } type appHandler func(http.ResponseWriter, *http.Request) *appError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } func init() { http.Handle("/view", appHandler(viewRecord)) //viewRecord is an appHandler function } 

I like this approach, but I cannot conceptually figure out how to include a context object through handler layers. For instance:

 func init() { http.Handle("/view", AuthHandler(appHandler(viewRecord))) } 

AuthHandler will most likely create the &SessionToken{User: user} object and set it in the context.Context object for each request. However, I cannot figure out how to do this with the viewRecord handler. Ideas?

+6
source share
2 answers

I can come up with a couple of approaches to do this.

Context transfer

you can change the signature first to accept the context

 type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r, nil); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } 

Now I assume that AuthHandler has to do with authenticating and setting up a user in the context of an object.

What you can do is create another type handler that sets the context. like this

 type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // setup authentication here uid := 1 // setup the context the way you want parent := context.TODO() ctx := context.WithValue(parent, userIdKey, uid) if e := fn(w, r, ctx); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } 

So you can use it as follows

 func init() { http.Handle("/view", appHandler(viewRecord)) // don't require authentication http.Handle("/viewAuth", authHandler(viewRecord)) // require authentication } 

This is the complete code.

 package main import ( "fmt" "net/http" "code.google.com/p/go.net/context" ) type appError struct { Error error Message string Code int } type key int const userIdKey key = 0 type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r, nil); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // setup authentication here uid := 1 // setup the context the way you want parent := context.TODO() ctx := context.WithValue(parent, userIdKey, uid) if e := fn(w, r, ctx); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } func viewRecord(w http.ResponseWriter, r *http.Request, c context.Context) *appError { if c == nil { fmt.Fprintf(w, "User are not logged in") } else { uid := c.Value(userIdKey) fmt.Fprintf(w, "User logged in with uid: %d", uid) } return nil } func init() { http.Handle("/view", appHandler(viewRecord)) // viewRecord is an appHandler function http.Handle("/viewAuth", authHandler(viewRecord)) // viewRecord is an authHandler function } func main() { http.ListenAndServe(":8080", nil) } 

create map context

Instead of passing context, you create

 var contexts map[*http.Request]context.Context 

and get the context in view using contexts[r] .

But because the card is not thread safe, access to the card must be protected by the mutex.

And guess what, this is what the gorilla context does for you, and I think it’s better suited to

https://github.com/gorilla/context/blob/master/context.go#l20-28

this is the full code

 package main import ( "fmt" "net/http" "github.com/gorilla/context" ) type appError struct { Error error Message string Code int } type key int const userIdKey key = 0 type appHandler func(http.ResponseWriter, *http.Request) *appError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } type authHandler func(http.ResponseWriter, *http.Request) *appError func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // setup authentication here uid := 1 context.Set(r, userIdKey, uid) if e := fn(w, r); e != nil { // e is *appError, not os.Error. http.Error(w, e.Message, e.Code) } } func viewRecord(w http.ResponseWriter, r *http.Request) *appError { if uid, ok := context.GetOk(r, userIdKey); !ok { fmt.Fprintf(w, "User are not logged in") } else { fmt.Fprintf(w, "User logged in with uid: %d", uid) } return nil } func init() { http.Handle("/view", appHandler(viewRecord)) // don't require authentication http.Handle("/viewAuth", authHandler(viewRecord)) // require authentication } func main() { http.ListenAndServe(":8080", nil) } 

you can also choose a wrapper function instead of a type function for auth

 func AuthHandler(h appHandler) appHandler { return func(w http.ResponseWriter, r *http.Request) *appError { // setup authentication here uid := 1 context.Set(r, userIdKey, uid) return h(w, r) } } func init() { http.Handle("/view", appHandler(viewRecord)) // don't require authentication http.Handle("/viewAuth", appHandler(AuthHandler(viewRecord))) // require authentication } 
+11
source

Use r.Context() , which is available since version 1.7.

See https://golang.org/pkg/net/http/#Request.Context

+1
source

Source: https://habr.com/ru/post/977741/


All Articles