Custom values in the request context #2

Closed
opened 2025-07-17 23:26:55 +00:00 by vickodev · 5 comments
Contributor

Sometimes we have the neccesity to set a session object, mark a request as white list, set some var/object in the request context, thinks like that into the middlewares, and this neccesity depents on many situations and the kind of our web api.

The proposal is to have a customValues object in context struct, but defined only when is going to be used, like follow:

type context struct {
	request
	response
	server       *server
	handlerCount uint8
	customValues map[string]any
}

With two methods to interact with it:

type Context interface {
    // Current code
	SetCustomValue(key string, value any)
	GetCustomValue(key string, target interface{}) error
}

// SetCustomValue sets a custom value for the context.
func (s *context) SetCustomValue(key string, value any) {
	if s.customValues == nil {
		s.customValues = make(map[string]any)
	}
	s.customValues[key] = value
}

// getCustomValue retrieves a custom value from the context.
func (s *context) getCustomValue(key string) (interface{}, bool) {
	if s.customValues == nil {
		return nil, false
	}
	value, exists := s.customValues[key]
	return value, exists
}

// GetCustomValue retrieves a custom value and attempts to cast it to the target type.
func (s *context) GetCustomValue(key string, target interface{}) error {
	if target == nil {
		return fmt.Errorf("target cannot be nil")
	}

	value, exists := s.getCustomValue(key)
	if !exists {
		return fmt.Errorf("key '%s' not found", key)
	}

	targetValue := reflect.ValueOf(target)
	if targetValue.Kind() != reflect.Ptr {
		return fmt.Errorf("target must be a pointer")
	}

	targetElem := targetValue.Elem()
	if !targetElem.CanSet() {
		return fmt.Errorf("target cannot be set")
	}

	sourceValue := reflect.ValueOf(value)
	if !sourceValue.Type().AssignableTo(targetElem.Type()) {
		return fmt.Errorf("cannot assign %T to %T", value, targetElem.Interface())
	}

	targetElem.Set(sourceValue)
	return nil
}

And the way that we can use it, is somethig like this:

// Setting in some Middleware
	ctx.SetCustomValue("testKey", "testValue")
	ctx.SetCustomValue("user", myUser)
	return ctx.Next()

// Getting in some request handler
	var stringValue string
	ctx.GetCustomValue("testKey", &stringValue)
	var user userInfo
	ctx.GetCustomValue("user", &user)

So, that is my initial approximation, but let me know what do you think about that and if you are agree about the feature, or what is the best way to implement it?
Maybe is a better option to have this feature into the request and no in context?

Let me know!!

Sometimes we have the neccesity to set a session object, mark a request as white list, set some var/object in the request context, thinks like that into the middlewares, and this neccesity depents on many situations and the kind of our web api. The proposal is to have a customValues object in context struct, but defined only when is going to be used, like follow: ```go type context struct { request response server *server handlerCount uint8 customValues map[string]any } ``` With two methods to interact with it: ```go type Context interface { // Current code SetCustomValue(key string, value any) GetCustomValue(key string, target interface{}) error } // SetCustomValue sets a custom value for the context. func (s *context) SetCustomValue(key string, value any) { if s.customValues == nil { s.customValues = make(map[string]any) } s.customValues[key] = value } // getCustomValue retrieves a custom value from the context. func (s *context) getCustomValue(key string) (interface{}, bool) { if s.customValues == nil { return nil, false } value, exists := s.customValues[key] return value, exists } // GetCustomValue retrieves a custom value and attempts to cast it to the target type. func (s *context) GetCustomValue(key string, target interface{}) error { if target == nil { return fmt.Errorf("target cannot be nil") } value, exists := s.getCustomValue(key) if !exists { return fmt.Errorf("key '%s' not found", key) } targetValue := reflect.ValueOf(target) if targetValue.Kind() != reflect.Ptr { return fmt.Errorf("target must be a pointer") } targetElem := targetValue.Elem() if !targetElem.CanSet() { return fmt.Errorf("target cannot be set") } sourceValue := reflect.ValueOf(value) if !sourceValue.Type().AssignableTo(targetElem.Type()) { return fmt.Errorf("cannot assign %T to %T", value, targetElem.Interface()) } targetElem.Set(sourceValue) return nil } ``` And the way that we can use it, is somethig like this: ```go // Setting in some Middleware ctx.SetCustomValue("testKey", "testValue") ctx.SetCustomValue("user", myUser) return ctx.Next() // Getting in some request handler var stringValue string ctx.GetCustomValue("testKey", &stringValue) var user userInfo ctx.GetCustomValue("user", &user) ``` So, that is my initial approximation, but let me know what do you think about that and if you are agree about the feature, or what is the best way to implement it? Maybe is a better option to have this feature into the request and no in context? Let me know!!
Owner

Since Context is an interface, you can just provide your own context:

type Custom struct {
	web.Context
	UserId string
}

s.Use(func(ctx web.Context) error {
	custom := &Custom{Context: ctx, UserId: "admin"}
	return ctx.Next(custom)
})

However, this doesn't work right now because ctx.Next doesn't accept an additional parameter.
This could be added with a trivial 1-2 line change in ctx.Next.

Then it can be consumed with type safety:

s.Get("/", func(ctx web.Context) error {
	custom := ctx.(*Custom)
	return ctx.String(ctx.UserId)
})
Since `Context` is an interface, you can just provide your own context: ```go type Custom struct { web.Context UserId string } s.Use(func(ctx web.Context) error { custom := &Custom{Context: ctx, UserId: "admin"} return ctx.Next(custom) }) ``` However, this doesn't work right now because `ctx.Next` doesn't accept an additional parameter. This could be added with a trivial 1-2 line change in `ctx.Next`. Then it can be consumed with type safety: ```go s.Get("/", func(ctx web.Context) error { custom := ctx.(*Custom) return ctx.String(ctx.UserId) }) ```
Owner

I've added this in c5cf8488b3 let me know if this works for you!

I've added this in https://git.urbach.dev/go/web/commit/c5cf8488b3b249184bf78cf93ce628336843b421 let me know if [this](https://git.urbach.dev/go/web/src/branch/main/examples/context/main.go) works for you!
ed changed title from [PROPOSAL]: Feature to support custom values into the request context to Custom values in the request context 2025-07-18 08:55:22 +00:00
ed self-assigned this 2025-07-18 08:55:43 +00:00
Author
Contributor

@ed wrote in #2 (comment):

I've added this in c5cf8488b3 let me know if this works for you!

Hey @ed I like it, I believe your approach is simple because it remove the overhead about Reflection, but there are two things that maybe can carry with unexpected situations, one more or less complicated and the other one is manegeable:

Situations:

  1. We have to manage the cast in all the middleware chain about Custom context to pass it throught the Next function (Manegeable)
    Line custom := ctx.(*Custom) or return ctx.Next(ctx.(*Custom))
  2. The advange of this methodology is the performance, but dangerous because if the cast fail, the server is going to go to falldown, so we need a way to manage this behavior using a safeCast or other strategy, something like this:
func SafeCast(ctx web.Context) (*Custom, bool) {
  if custom, ok := ctx.(*Custom); ok {
    return custom, true
  }
  return nil, false
} 

Observation:
The other way to avoid the problems with cast Panic without add the safeCast function, is take care in the first middleware chain to create in a safe way the Custom object to avoid problems, somethink like this:

// In the first middleware chain.
        var custom *Custom
        if c, ok := ctx.(*Custom); ok {
            custom = c
        } else {
            custom = &Custom{
                Context:       ctx,
                Session:       nil,
                IsWhiteListed: false,
            }
        }

So in that way, the rest of the middleware chain, the cast for Custom is naturally safe.

Let me know your point of view, but I think your approach is a good and performanced solution.

@ed wrote in https://git.urbach.dev/go/web/issues/2#issuecomment-34: > I've added this in [`c5cf8488b3`](https://git.urbach.dev/go/web/commit/c5cf8488b3b249184bf78cf93ce628336843b421) let me know if [this](https://git.urbach.dev/go/web/src/branch/main/examples/context/main.go) works for you! Hey @ed I like it, I believe your approach is simple because it remove the overhead about Reflection, but there are two things that maybe can carry with unexpected situations, one more or less complicated and the other one is manegeable: Situations: 1. We have to manage the cast in all the middleware chain about Custom context to pass it throught the Next function (Manegeable) Line `custom := ctx.(*Custom)` or `return ctx.Next(ctx.(*Custom))` 2. The advange of this methodology is the performance, but dangerous because if the cast fail, the server is going to go to falldown, so we need a way to manage this behavior using a safeCast or other strategy, something like this: ```go func SafeCast(ctx web.Context) (*Custom, bool) { if custom, ok := ctx.(*Custom); ok { return custom, true } return nil, false } ``` Observation: The other way to avoid the problems with cast Panic without add the safeCast function, is take care in the first middleware chain to create in a safe way the Custom object to avoid problems, somethink like this: ```go // In the first middleware chain. var custom *Custom if c, ok := ctx.(*Custom); ok { custom = c } else { custom = &Custom{ Context: ctx, Session: nil, IsWhiteListed: false, } } ``` So in that way, the rest of the middleware chain, the cast for Custom is naturally safe. Let me know your point of view, but I think your approach is a good and performanced solution.
Owner

Hi,

both of these points are based on the assumption that you have to cast to *Custom on every ctx.Next call.
However, that is not the case:

s.Use(func(ctx web.Context) error {
	custom := &Custom{Context: ctx, UserId: "admin"}
	return ctx.Next(custom)
})

s.Use(func(ctx web.Context) error {
	return ctx.Next(ctx) // no cast needed, it will still be a *Custom
})

An interface is a 16-byte structure containing the type and the pointer. As long as interfaces are passed to interface parameters by value, all of that type and pointer information gets passed along.

You don't need to cast to *Custom everywhere down the chain and you don't need a safeCast function because it's guaranteed to be your custom context type.

Hi, both of these points are based on the assumption that you have to cast to `*Custom` on every `ctx.Next` call. However, that is not the case: ```go s.Use(func(ctx web.Context) error { custom := &Custom{Context: ctx, UserId: "admin"} return ctx.Next(custom) }) s.Use(func(ctx web.Context) error { return ctx.Next(ctx) // no cast needed, it will still be a *Custom }) ``` An interface is a 16-byte structure containing the type and the pointer. As long as interfaces are passed to interface parameters by value, all of that type and pointer information gets passed along. You don't need to cast to `*Custom` everywhere down the chain and you don't need a `safeCast` function because it's guaranteed to be your custom context type.
Author
Contributor

Hi man, you're right, so your implemention works. TK!!

Hi man, you're right, so your implemention works. TK!!
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: go/web#2
No description provided.