Context
The slim.Context interface is the context for the Slim handler function for the current HTTP request. It includes references to the request and response, route matching information, runtime data, etc., and is used for reading requests and writing responses.
We need to distinguish between slim.Context and HTTP request context (http.Request.Context).
Extending Context
Because slim.Context is an interface, we can easily extend it.
Define our own context:
type CustomContext struct {
slim.Context
}
func (c *CustomContext) Foo() {
println("foo")
}
func (c *CustomContext) Bar() {
println("bar")
}
Then create a middleware to extend the default context and pass this new context downstream:
s.Use(func(c slim.Context, next slim.HandlerFunc) error {
cc := &CustomContext{c}
return next(cc)
})
This middleware should be registered before other middlewares.
Then we can use it in handler functions:
s.GET("/", func(c slim.Context) error {
cc := c.(*CustomContext)
cc.Foo()
cc.Bar()
return cc.String(200, "OK")
})
Concurrent Operations
slim.Context cannot be accessed outside of the goroutine handling the request for two reasons:
- The default implementation of
slim.Contextis not concurrency-safe, so we should only access it in onegoroutine. - Additionally,
slim.Contextis created using the object poolsync.Pool, and will be returned to the object pool after request processing is complete for future reuse.
Due to the complexity of concurrency, be very careful to note this pitfall when using goroutines.
We can use channels to block request processing, waiting for handler function's child goroutines to finish before completing request processing:
func(c slim.Context) error {
// First, we prepare a non-blocking channel with capacity 1
ca := make(chan string, 1)
r := c.Request()
method := r.Method
go func() {
// We should prepare context-related data outside this function beforehand,
// and never use the slim.Context instance `c` to get data here.
fmt.Printf("Method: %s\n", method)
// Here, we can do some time-consuming operations
// Then use the channel to tell the handler processing is complete.
ca <- "Hey!"
}()
select {
case result := <-ca:
// Receive the result processed by other goroutines
return c.String(http.StatusOK, "Result: "+result)
case <-c.Done():
// If the code runs here, it means the context was cancelled (e.g., timeout, etc.).
return nil
}
}