将上下文从 Web 请求的控制器传递到数据库层

Posted

技术标签:

【中文标题】将上下文从 Web 请求的控制器传递到数据库层【英文标题】:Pass context from controller of web request to database layer 【发布时间】:2018-07-14 10:17:22 【问题描述】:

我有 REST 服务:

每个请求都有一个带有 JWT 令牌的标头 每个控制器从请求中获取参数(变量、正文..)并将它们传递给数据层

我需要将每个请求的标头中的 JWT 令牌传递给相应的数据层方法,如下所示:

func (a *App) UpdateOrder(_ http.ResponseWriter, r *http.Request) (interface, error) 

    bodyData := new(models.Order)
    err = json.NewDecoder(r.Body).Decode(&bodyData)
    if err != nil 
       return nil, err
    

    user, err := a.Saga.GetUserByToken(r.Header.Get("Authorization"))  // here
    // error handling ...

    a.DbLayer.UpdateOrder(id, bodyData, user)  // and there

在这种情况下,我必须为每个控制器编写相同的代码以通过令牌获取用户,并将此用户显式传递给数据库层。

有没有办法为每个请求传递这个用户而不在每个控制器中编写这段代码?

我了解中间件,我可以通过中间件中的令牌获取用户。但是我怎样才能将这个用户从中间件传递到相应的数据库级方法呢?

也许我正在为 goroutine 寻找类似“全局变量”的东西?我可以在我的中间件中获取用户并将其设置为“全局变量”之类的东西。我可以在数据库层得到这个“全局变量”的值。但它必须是当前网络请求的“全局变量”,并发的网络请求不能相互影响。

Go、http 模块或gorilla\mux 中是否有某种机制来实现我所说的“全局变量”?

【问题讨论】:

不要那样做。您正试图隐藏依赖关系。如果一个方法需要授权,那应该从方法签名中明确。例如,如果您使用 context.Context,编译器无法帮助您确保检查授权,并且您会失去所有类型安全性。 【参考方案1】:

您正在描述上下文。

最初是 gorilla context package,它提供了一个伪全局上下文对象 - 本质上是一个 map[interface]interface,其引用本质上可供中间件/控制器/数据层堆栈中的所有参与者使用。

除了an excellent guide to the package 之外,请查看此内容(所有内容均归功于作者,Matt Silverlock)。

type contextKey int

// Define keys that support equality.
const csrfKey contextKey = 0
const userKey contextKey = 1

var ErrCSRFTokenNotPresent = errors.New("CSRF token not present in the request context.")

// We'll need a helper function like this for every key:type
// combination we store in our context map else we repeat this
// in every middleware/handler that needs to access the value.
func GetCSRFToken(r *http.Request) (string, error) 
    val, ok := context.GetOk(r, csrfKey)
    if !ok 
        return "", ErrCSRFTokenNotPresent
    

    token, ok := val.(string)
    if !ok 
        return "", ErrCSRFTokenNotPresent
    

    return token, nil


// A bare-bones example
func CSRFMiddleware(h http.Handler) http.Handler 
    return func(w http.ResponseWriter, r *http.Request) 
        token, err := GetCSRFToken(r)
        if err != nil 
            http.Error(w, "No good!", http.StatusInternalServerError)
            return
        

        // The map is global, so we just call the Set function
        context.Set(r, csrfKey, token)

        h.ServeHTTP(w, r)
    

在 gorilla 包开始后,context package 被添加到标准库中。它略有不同,因为上下文不再是伪全局的,而是从一个方法传递到另一个方法。在此情况下,上下文附加到初始请求 - 可通过 request.Context 获得。处理程序下方的层可以接受上下文值作为其签名的一部分,并从中读取值。

这是一个简化的例子:

type contextKey string

var (
    aPreSharedKey = contextKey("a-preshared-key")
)

func someHandler(w http.ResponseWriter, req *http.Request) 
    ctx := context.WithValue(req.Context, aPreSharedKey, req.Header.Get("required-header"))
    data, err := someDataLayerFunction(ctx)
    if err != nil 
        fmt.Fprintf(w, "uhoh", http.StatusBadRequest)
        return
    
    fmt.Fprintf(w, data, http.StatusOK)


func someDataLayerFunction(ctx context.Context) (string, error) 
    val, ok := ctx.Value(aPreSharedKey).(string)
    if !ok 
        return nil, errors.New("required context value missing")
    
    return val

有关更多详细信息和不那么做作的示例,请查看 google 的 excellent blog on the context package's use.

【讨论】:

我认为您应该考虑输入上下文键以避免任何形式的冲突等等,请参阅medium.com/@matryer/context-keys-in-go-5312346a868d 这是一个很好的观点。我已经更新了这个例子。谢谢@mh-cbon

以上是关于将上下文从 Web 请求的控制器传递到数据库层的主要内容,如果未能解决你的问题,请参考以下文章

将请求上下文从 Django Rest Framework 中的 Viewset 传递给序列化程序

NextJS:通过上下文将字符串从输入传递到 getStaticProps

如何将请求正文传递到 GraphQL 上下文中?

使用选项卡控制器传递托管对象上下文

用@RequestMapping映射请求

在 golang 中将元数据传递到上下文时出错