Does Set() method of echo.Context saves the value to the underlying context.Context?

7.1k Views Asked by At

I am using Echo framework and want to pass the Go's built-in context.Context underlying echo.Context after setting some custom values.

To achieve it, I think I could first apply Set(key string, val interface{}) method of echo.Context and then extract the underlying context.Context.

Question is is it possible to do it this way? In other words, does echo.Context.Set(...) sets the value directly on the context.Context just like WithValue does? Or should I take extra steps to copy my custom entries down.

P.S. I do not want to pass echo.Context to deeper layers of my app, that's why I do not want to directly use it but get the referring context.Context

1

There are 1 best solutions below

0
On

Method 1: Reimplement the echo.Context.Get and echo.Context.Set methods to manipulate the ctx.Request().Context() object.

Disadvantages: http.Request.WithContext will be called once for each Set method, and *http.Request will be copied once. See the implementation of WithContext method for details.

Method 2: Reimplement the echo.Context.Get and echo.Context.Set methods to manipulate the contextValueData2 object, and set http.Request.WithContext to a custom context.Context contextValueData2.

Disadvantages: Before go1.13, context.Context requires Type assertions. Don't implement the context.Context method. Compared with method 1, the implementation only requires WithContext once.

It is recommended to use method 1, which is clear and simple, and method 2 is complicated and not fully tested.

The example import package uses gopath, and the implementation of this feature also reflects the advantage of echo.Context as an interface.

package main

import (
    "context"
    "fmt"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "net/http"
)

func main() {
    // Echo instance
    e := echo.New()

    // Middleware
    e.Use(NewMiddlewareContextValue)
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Routes
    e.GET("/", hello)
    e.GET("/val", getval)

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

// Handler
func hello(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
}

func getval(c echo.Context) error {
    c.Set("111", "aa")
    c.Set("222", "bb")
    return c.String(http.StatusOK, fmt.Sprint(c.Request().Context()))
}

// ---------- method1 ----------
func NewMiddlewareContextValue(fn echo.HandlerFunc) echo.HandlerFunc {
    return func(ctx echo.Context) error {
        return fn(contextValue{ctx})
    }
}

type contextValue struct {
    echo.Context
}

// Get retrieves data from the context.
func (ctx contextValue) Get(key string) interface{} {
    // get old context value
    val := ctx.Context.Get(key)
    if val != nil {
        return val
    }
    return ctx.Request().Context().Value(key)
}

// Set saves data in the context.
func (ctx contextValue) Set(key string, val interface{}) {
ctx.SetRequest(ctx.Request().WithContext(context.WithValue(ctx.Request().Context(), key, val)))
}

// ---------- method2 ----------

func NewMiddlewareContextValue2(fn echo.HandlerFunc) echo.HandlerFunc {
    return func(ctx echo.Context) error {
        ctxdata := contextValueData2{
            Context: ctx.Request().Context(),
        }
        ctx.SetRequest(ctx.Request().WithContext(ctxdata))
        return fn(&contextValue2{Context: ctx, contextValueData2: ctxdata})
    }
}

type contextValue2 struct {
    echo.Context
    contextValueData2
}

type contextValueData2 struct {
    context.Context
    Data map[string]interface{}
}

// Get retrieves data from the context.
func (ctx *contextValue2) Get(key string) interface{} {
    // get old context value
    val := ctx.Context.Get(key)
    if val != nil {
        return val
    }
    // get my data value
    val, ok := ctx.contextValueData2.Data[key]
    if ok {
        return val
    }
    return ctx.contextValueData2.Context.Value(key)
}

// Set saves data in the context.
func (ctx *contextValue2) Set(key string, val interface{}) {
    if ctx.Data == nil {
        ctx.contextValueData2.Data = make(map[string]interface{})
    }
    ctx.contextValueData2.Data[key] = val
}

func (ctx contextValueData2) Value(key interface{}) interface{} {
    str, ok := key.(string)
    if ok {
        val, ok := ctx.Data[str]
        if ok {
            return val
        }
    }
    return ctx.Context.Value(key)
}