Context refers to the standard library context.Context, which is an interface object commonly used for asynchronous IO control and transmission of context flow variables. This article aims to demonstrate how to use Context to transmit inter-process shared variables.

In the execution flow of Go, especially in HTTP/RPC execution flows, there is no “global variable” method to obtain request parameters; instead, context Context variables are passed to subsequent process methods, and Context variables contain all the shared variables needed. Moreover, the shared variables in this Context should be agreed upon in advance and are often stored in the form of object pointers.

Sharing variables through Context is very simple. Here, we demonstrate how to transmit and use general shared variables in a practical project through an example in a project.

1. Structure Definition

Context objects often store some variables that need to be shared, and these variables are usually stored in structured objects for easy maintenance. For example, we define shared variables in the model in a context:

  1. const (
  2. // Key name to store context variables, shared by front and back systems
  3. ContextKey = "ContextKey"
  4. )
  5. // Request context structure
  6. type Context struct {
  7. Session *ghttp.Session // Current Session management object
  8. User *ContextUser // Context user information
  9. Data g.Map // Custom KV variables set according to business module needs, not fixed
  10. }
  11. // User information in the request context
  12. type ContextUser struct {
  13. Id uint // User ID
  14. Passport string // User account
  15. Nickname string // User name
  16. Avatar string // User avatar
  17. }

Where:

  1. model.ContextKey constant represents the key name stored in the context.Context variable, used to store/retrieve business custom shared variables from the transmitted context.Context variable.
  2. Session in model.Context structure represents the Session object of the current request. In the GoFrame framework, each HTTP request object has an empty Session object, which adopts lazy initialization design and is initialized only when real read and write operations are performed.
  3. User in model.Context structure represents the basic information of the currently logged-in user, with data available only after user login; otherwise, it is nil.
  4. Data attribute in model.Context structure is used to store custom KV variables, so developers generally do not need to add custom key-value pairs to context.Context, but directly use this Data attribute of the model.Context object. See details in later sections.

2. Logical Encapsulation

The context object is also related to business logic, so we need to encapsulate the context variables with a service object for easy use by other modules.

  1. // Context management service
  2. var Context = new(contextService)
  3. type contextService struct{}
  4. // Initialize context object pointer into context object for subsequent request flow modifications.
  5. func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) {
  6. r.SetCtxVar(model.ContextKey, customCtx)
  7. }
  8. // Get context variables, return nil if not set
  9. func (s *contextService) Get(ctx context.Context) *model.Context {
  10. value := ctx.Value(model.ContextKey)
  11. if value == nil {
  12. return nil
  13. }
  14. if localCtx, ok := value.(*model.Context); ok {
  15. return localCtx
  16. }
  17. return nil
  18. }
  19. // Set context information into context request, note it's complete overwrite
  20. func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
  21. s.Get(ctx).User = ctxUser
  22. }

3. Context Variable Injection

Context variables must be injected into the request flow at the very beginning of the request to be available for other method calls. In HTTP requests, we can achieve this with GoFrame middleware. For GRPC requests, interceptors can be used. We can define this in the middleware management object in the service layer as follows:

  1. // Custom context object
  2. func (s *middlewareService) Ctx(r *ghttp.Request) {
  3. // Initialize, must be executed first
  4. customCtx := &model.Context{
  5. Session: r.Session,
  6. Data: make(g.Map),
  7. }
  8. service.Context.Init(r, customCtx)
  9. if userEntity := Session.GetUser(r.Context()); userEntity != nil {
  10. customCtx.User = &model.ContextUser{
  11. Id: userEntity.Id,
  12. Passport: userEntity.Passport,
  13. Nickname: userEntity.Nickname,
  14. Avatar: userEntity.Avatar,
  15. }
  16. }
  17. // Pass the custom context object to template variables for use
  18. r.Assigns(g.Map{
  19. "Context": customCtx,
  20. })
  21. // Execute the next request logic
  22. r.Middleware.Next()
  23. }

This middleware initializes a user-executed flow shared object, and the object stored in the context.Context variable is a pointer type *model.Context. By fetching this pointer anywhere, you can both access and modify the data within.

If there is stored user login information in Session, the required shared user basic information will also be written into *model.Context.

4. Context Variable Usage

Method Definition

By convention, the first input parameter of a method is often reserved for context.Context type parameters to accept context variables, especially in the service layer methods. For example:

  1. // Execute user login
  2. func (s *userService) Login(ctx context.Context, loginReq *define.UserServiceLoginReq) error {
  3. ...
  4. }
  5. // Query content list
  6. func (s *contentService) GetList(ctx context.Context, r *define.ContentServiceGetListReq) (*define.ContentServiceGetListRes, error) {
  7. ...
  8. }
  9. // Create reply content
  10. func (s *replyService) Create(ctx context.Context, r *define.ReplyServiceCreateReq) error {
  11. ...
  12. }

Moreover, by convention, the last return parameter of a method is often of error type. If you are sure that there will never be an error inside this method, you can ignore it.

Context Object Retrieval

Using the following method encapsulated in service, you can pass the context.Context variable. In the GoFrame framework’s HTTP request, you can get the context.Context variable through the r.Context() method, and in GRPC requests, the first parameter of the methods generated in the compiled pb files is fixed as context.Context.

  1. service.Context.Get(ctx)

Custom Key-Value

Set/Get custom key-value pairs via:

  1. // Set custom key-value pair
  2. service.Context.Get(ctx).Data[key] = value
  3. ...
  4. // Get custom key-value pair
  5. service.Context.Get(ctx).Data[key]

5. Precautions

  1. Only pass required link parameter data in context variables, don’t put everything in them. Especially for some method argument passing data, not everything should be put into them; instead, explicit parameter transmission should be used.
  2. Context variables should only be used for temporary runtime use and not for long-term persistent storage. For example, serializing ctx and storing it in a database, then deserializing it for use in the next request is incorrect.