将数据传递给父中间件的方法是什么?

时间:2017-02-22 20:04:51

标签: go

我已经非常可靠地掌握了如何将数据从处理程序传递到它包装的处理程序,但是有没有一种惯用的方式从包装的处理程序中获取某些东西?这是一个激励性的例子:我有accessLogHandlerauthHandleraccessLogHandler记录每个http请求,包括时间和其他请求信息,例如当前登录用户的ID(如果有的话)。 authHandler用于需要登录用户的路由,当用户没有登录时,它是403。我想用{{包裹一些(但可能不是全部)我的路由1}},用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内部和accessLogHandler内部authHandler内部的上下文中。 }从上下文中调用accessLog以通知记录器存在用户ID。

我不喜欢这种做法的一些事情:

  1. 上下文是不可变的,但我在其上粘贴了一个可变结构,并在下游的其他地方改变了所述结构。感觉像是黑客。
  2. 我的accessLog.Set现在对authHandler包具有包级依赖性,因为我键入了断言为accessLog
  3. 理想情况下,我的*AccessLog可以通过某种方式通知请求堆栈的任何部分,而不会将自身与所述部分紧密耦合。

1 个答案:

答案 0 :(得分:1)

Context本身是一个接口,因此您可以在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))
    }
}