对于如何将数据从处理程序传递到包装的处理程序,我有很扎实的把握,但是有一种惯用的方式从包装的处理程序中取回东西吗?这是一个激励性的示例:我有一个accessLogHandlerauthHandleraccessLogHandler记录每个http请求,时间和其他请求信息,例如当前登录的用户ID(如果有)。 authHandler适用于需要已登录用户的路由,当用户未登录时为403。我想用authHandler包装我的一些(但不是全部)路线,并用accessLogHandler包装我的所有路线。如果用户已登录,我希望自己的accessLogHandler记录用户信息以及访问日志。

现在,我有一个我不喜欢的解决方案。我将添加代码,然后解释一些有关它的问题。

// Log the timings of each request optionally including user data
// if there is a logged in user
func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        accessLog := newAccessLog()
        ctx := context.WithValue(r.Context(), accessLogKey, accessLog)
        fn.ServeHTTP(w, r.WithContext(ctx))

        // Logs the http access, ommit user info if not set
        accessLog.Log()
    }
}

// pull some junk off the request/cookies/whatever and check if somebody is logged in
func authHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func (w http.ResponseWriter, r *http.Request) {
        //Do some authorization
        user, err := auth(r)
        if err != nil{
            //No userId, don't set anything on the accesslogger
            w.WriteHeader(http.StatusForbiddend)
            return
        }
        //Success a user is logged in, let's make sure the access logger knows
        acessLog := r.Context().Value(accessLogKey).(*AccessLog)
        accessLog.Set("userID", user.ID)
        fn.ServeHTTP(w, r)
    }
}

基本上,我在这里所做的就是将accessLog结构附加到accessLogHandlerauthHandler内部的上下文中,我从上下文中读取accessLog并调用accessLog.Set通知记录程序存在用户ID。

我对这种方法不满意:
  • 上下文是不可变的,但是我在其上粘贴了一个可变结构,并在下游的其他地方对该结构进行了变异。感觉像一个黑客。
  • 我的authHandler现在对accessLog包具有包级别的依赖关系,因为我将类型断言为*AccessLog
  • 理想情况下,我的authHandler可以采用某种方式将用户数据通知给请求堆栈的任何部分,而无需将自身紧密耦合到所述部分。
  • 最佳答案

    上下文本身是一个接口(interface),因此您可以在logger中间件中创建一个新的logger上下文,它具有实现所要获得的行为所需的方法。

    像这样:

    type Logger struct{}
    
    func (l *Logger) SetLogField(key string, value interface{}) {// set log field }
    func (l *Logger) Log(){// log request}
    
    type LoggerCtx struct {
        context.Context
        *Logger
    }
    
    func newAccessLog() *Logger {
        return &Logger{}
    }
    
    func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            // create new logger context
            ctx := &LoggerCtx{}
            ctx.Context = r.Context()
            ctx.Logger = newAccessLog()
    
            fn.ServeHTTP(w, r.WithContext(ctx))
    
            // Logs the http access, ommit user info if not set
            ctx.Log()
        }
    }
    
    // pull some junk off the request/cookies/whatever and check if somebody is logged in
    func authHandler(fn http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            //Do some authorization
            user, err := auth(r)
            if err != nil {
                //No userId, don't set anything on the accesslogger
                w.WriteHeader(http.StatusForbiddend)
                return
            }
    
            //Success a user is logged in, let's make sure the access logger knows
            ctx := r.Context()
    
            // this could be moved - here for clarity
            type setLog interface {
                SetLogField(string, interface{})
            }
    
            if lctx, ok := ctx.(setLog); ok {
                lctx.SetLogField("userID", user.ID)
            }
    
            fn.ServeHTTP(w, r.WithContext(ctx))
        }
    }
    

    关于go - 如何将数据传递给父中间件?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42401197/

    10-11 06:37