1

我正在使用Echo 框架,并希望在设置一些自定义值后传递 Go 内置的context.Context 底层 echo.Context 。

为了实现它,我想我可以先应用Set(key string, val interface{})方法,echo.Context然后提取底层的context.Context.

问题是可以这样做吗?换句话说,doesecho.Context.Set(...)直接设置值context.Context就像WithValuedoes一样?或者我应该采取额外的步骤来复制我的自定义条目。

PS我不想传递echo.Context到我的应用程序的更深层,这就是为什么我不想直接使用它但得到引用context.Context

4

1 回答 1

4

方法一:重新实现 echo.Context.Get 和 echo.Context.Set 方法来操作 ctx.Request().Context() 对象。

缺点:每个Set方法都会调用一次http.Request.WithContext,而*http.Request会被复制一次。有关详细信息,请参阅 WithContext 方法的实现。

方法二:重新实现 echo.Context.Get 和 echo.Context.Set 方法来操作 contextValueData2 对象,并将 http.Request.WithContext 设置为自定义的 context.Context contextValueData2。

缺点:在go1.13之前,context.Context需要Type断言。不要实现 context.Context 方法。与方法一相比,实现只需要一次WithContext。

建议使用方法1,清晰简单,方法2复杂,没有经过充分测试。

示例导入包使用了gopath,这个特性的实现也体现了echo.Context作为接口的优势。

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)
}
于 2021-09-26T01:52:03.070 回答