Using request context
At the moment our logic for authenticating a user consists of simply checking whether a "authenticatedUserID" value exists in their session data, like so:
func (app *application) isAuthenticated(r *http.Request) bool { return app.sessionManager.Exists(r.Context(), "authenticatedUserID") }
We could make this check more robust by querying our users database table to make sure that the "authenticatedUserID" value is a real, valid value (i.e we haven’t deleted the user’s account since they last logged in).
But there is a slight problem with doing this additional database check.
Our isAuthenticated() helper can potentially be called multiple times in each request cycle. Currently we use it twice — once in the requireAuthentication() middleware and again in the newTemplateData() helper. So, if we query the database from the isAuthenticated() helper directly, we would end up making duplicated round-trips to the database during every request. And that’s not very efficient.
A better approach would be to carry out this check in some middleware to determine whether the current request is from an authenticated user or not, and then pass that information down to all subsequent handlers in the chain.
So how do we do this? Enter request context.
How request context works
Every http.Request that our middleware and handlers process has a context.Context object embedded in it, which we can use to store information during the lifetime of the request.
As I’ve already hinted at, in a web application a common use-case for this is to pass information between your pieces of middleware and other handlers.
In our case, we want to use it to check if a user is authenticated once in some middleware, and if they are, then make this information available to all our other middleware and handlers.
The request context syntax
The basic code for adding information to a request’s context looks like this:
// Where r is a *http.Request... ctx := r.Context() ctx = context.WithValue(ctx, "isAuthenticated", true) r = r.WithContext(ctx)
Let’s step through this line-by-line.
- First, we use the r.Context() method to retrieve the existing context from a request and assign it to the ctx variable.
- Then we use the context.WithValue() method to create a new copy of the existing context, containing the key "isAuthenticated" and a value of true .
- Then finally we use the r.WithContext() method to create a copy of the request containing our new context.
Important: Notice that we don’t actually update the context for a request directly. What we’re doing is creating a new copy of the http.Request object with our new context in it.
I should also point out that, for clarity, I made that code snippet a bit more verbose than it needs to be. It’s more typical to write it like this:
ctx = context.WithValue(r.Context(), "isAuthenticated", true) r = r.WithContext(ctx)
So that’s how you add data to a request’s context. But what about retrieving it again?
The important thing to explain is that, behind the scenes, request context values are stored with the type any . And that means that, after retrieving them from the context, you’ll need to assert them to their original type before you use them.
To retrieve a value we need to use the r.Context().Value() method, like so:
isAuthenticated, ok := r.Context().Value("isAuthenticated").(bool) if !ok { return errors.New("could not convert value to bool") }
Avoiding key collisions
In the code samples above, I’ve used the string "isAuthenticated" as the key for storing and retrieving the data from a request’s context. But this isn’t recommended because there’s a risk that other third-party packages used by your application will also want to store data using the key "isAuthenticated" — and that would cause a naming collision.
To avoid this, it’s good practice to create your own custom type which you can use for your context keys. Extending our sample code, it’s much better to do something like this:
// Declare a custom "contextKey" type for your context keys. type contextKey string // Create a constant with the type contextKey that we can use. const isAuthenticatedContextKey = contextKey("isAuthenticated") ... // Set the value in the request context, using our isAuthenticatedContextKey constant as the key. ctx := r.Context() ctx = context.WithValue(ctx, isAuthenticatedContextKey, true) r = r.WithContext(ctx) ... // Retrieve the value from the request context using our constant as the key. isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool) if !ok { return errors.New("could not convert value to bool") }
标签:Web,use,ctx,request,Application,context,Go,our,isAuthenticated From: https://www.cnblogs.com/zhangzhihui/p/18401329