Correct use of go context.Context

5.7k views Asked by At

I just read the article: Build You Own Web Framework In Go and for sharing values among handlers I picked the context.Context and I'm using it in the following way to share values across handlers and middlewares:

type appContext struct {
    db     *sql.DB
    ctx    context.Context
    cancel context.CancelFunc
 }


func (c *appContext)authHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request {
        defer c.cancel() //this feels weird
        authToken := r.Header.Get("Authorization") // this fakes a form
        c.ctx = getUser(c.ctx, c.db, authToken) // this also feels weird
        next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

func (c *appContext)adminHandler(w http.ResponseWriter, r *http.Request) {
    defer c.cancel()
    user := c.ctx.Value(0).(user)
    json.NewEncoder(w).Encode(user)
}

func getUser(ctx context.Context, db *sql.DB, token string) context.Context{
    //this function mimics a database access
    return context.WithValue(ctx, 0, user{Nome:"Default user"})
}

func main() {
    db, err := sql.Open("my-driver", "my.db")
    if err != nil {
        panic(err)
    }
    ctx, cancel := context.WithCancel(context.Background())
    appC := appContext{db, ctx, cancel}
    //....
}

Everything is working and handlers are loading faster than using gorilla/context So my questions are:

  1. Is this approach safe?
  2. Is it really necessary to defer the c.cancel() function the way I'm doing it?
  3. Can I use it to implement a custom web framework by using controllers like struct to share values with models?
3

There are 3 answers

1
Nicolas Merouze On BEST ANSWER

You have a problem with your code because you are storing the user into the app context. With multiple users at the same time, it doesn't work. The context must be related to the request to not be overwrote by other requests. The user must be stored in a request context. In my articles I use the following gorilla function: context.Set(r, "user", user). r is the request.

If you want to use context.Context in your app, you should use their gorilla wrapper (you can find it at the end of this article: https://blog.golang.org/context).

Also, you don't need a context with cancel. context.Background() is okay for the root context.

0
user27111987 On

Two major use cases of Context package are:

  1. For storing request scoped values - Using context.WithValue()
  2. For cancellation - using context.WithCancel(), context.WithTimeout(), context.WithDeadline()

Use of context forms a context tree with context.Background() as root. WithValue() ,context.WithCancel(), WithTimeout(), WithDeadline() are derived context from root context which can be further divide. An example of each can make it clear so ast to what is the correct use context. Came across this guide which provides the use of all discussed above with proper examples.

Source: https://golangbyexample.com/using-context-in-golang-complete-guide/

0
VonC On

Note: go 1.7.0-rc2 does clarify a bit how to release resources associated with Contexts (Sameer Ajmani):

Some users don't realize that creating a Context with a CancelFunc attaches a subtree to the parent, and that that subtree is not released until the CancelFunc is called or the parent is canceled.

Make this clear early in the package docs, so that people learning about this package have the right conceptual model.

The documentation now includes:

Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context.
The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue.
These Context values form a tree: when a Context is canceled, all Contexts derived from it are also canceled.

The WithCancel, WithDeadline, and WithTimeout functions return a derived Context and a CancelFunc.
Calling the CancelFunc cancels the new Context and any Contexts derived from it, removes the Context from the parent's tree, and stops any associated timers.
Failing to call the CancelFunc leaks the associated resources until the parent Context is canceled or the timer fires.