Way to pass data up to parent middleware?

huangapple go评论79阅读模式
英文:

Way to pass data up to parent middleware?

问题

我对如何从一个处理程序中获取包装的处理程序返回的数据有一定的了解,但是否有一种Go语言的惯用方式来从包装的处理程序中获取返回值呢?以下是一个示例:我有一个accessLogHandler和一个authHandleraccessLogHandler记录每个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.StatusForbidden)
			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)
	}
}

基本上,我在accessLogHandler中将一个accessLog结构体附加到上下文中,在authHandler中从上下文中读取accessLog并调用accessLog.Set来通知日志记录器存在userID。

我不喜欢这种方法的一些问题:

  1. 上下文是不可变的,但我在其中附加了一个可变的结构体,并在下游的其他地方对该结构体进行了修改。感觉像是一个hack。
  2. 我的authHandler现在对accessLog包有一个包级别的依赖,因为我进行了类型断言为*AccessLog
  3. 理想情况下,我的authHandler应该有一种方式来通知请求堆栈的任何部分有关用户数据,而不是与这些部分紧密耦合。
英文:

I've got a pretty solid grip on how to pass data from a handler to the handler it wraps, but is there a go idiomatic way to get something back from the wrapped handler? Here's a motivating example: I have an accessLogHandler and an authHandler. accessLogHandler logs every http request, with timings and other request info such as the currently logged in user's ID (if there is one). authHandler is for routes that need a logged in user, it 403's when a user isn't logged in. I want to wrap some (but perhaps not all) of my routes with the authHandler, and wrap all of my routes with the accessLogHandler. If a user is logged in, I would like my accessLogHandler to log the user info along with the access log.

Now, I have a solution I've come up with that I don't like. I'll add the code and then explain some of my issues with it.

// 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)
	}
}

Basically, what I'm doing here is attaching an accessLog struct to my context inside the accessLogHandler and inside the authHandler I'm reading accessLog from the context and calling accessLog.Set to inform the logger that a userID is present.

Some things I don't like about this approach:

  1. context is immutable, but I'm sticking a mutable struct on it and mutating said struct elsewhere downstream. Feels like a hack.
  2. My authHandler now has a package level dependency on the accessLog package, since I'm type asserting to *AccessLog.
  3. Ideally my authHandler would have some way of informing any part of the request stack about user data without tightly coupling itself to said parts.

答案1

得分: 1

上下文本身是一个接口,因此您可以在日志中间件中创建一个新的日志记录器上下文,该上下文具有您所需的方法来实现您想要的行为。

可以像这样实现:

type Logger struct{}

func (l *Logger) SetLogField(key string, value interface{}) {// 设置日志字段 }
func (l *Logger) Log(){// 记录请求}

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) {
		// 创建新的日志记录器上下文
		ctx := &LoggerCtx{}
		ctx.Context = r.Context()
		ctx.Logger = newAccessLog()

		fn.ServeHTTP(w, r.WithContext(ctx))

		// 记录HTTP访问日志,如果未设置用户信息,则省略
		ctx.Log()
	}
}

// 从请求/cookie/其他地方获取一些垃圾并检查是否有用户登录
func authHandler(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// 进行一些授权操作
		user, err := auth(r)
		if err != nil {
			// 没有用户ID,不在访问日志记录器上设置任何内容
			w.WriteHeader(http.StatusForbidden)
			return
		}

		// 成功登录用户,确保访问日志记录器知道
		ctx := r.Context()

		// 这部分可以移动 - 这里是为了清晰起见
		type setLog interface {
			SetLogField(string, interface{})
		}

		if lctx, ok := ctx.(setLog); ok {
			lctx.SetLogField("userID", user.ID)
		}

		fn.ServeHTTP(w, r.WithContext(ctx))
	}
}

以上是您提供的代码的翻译。

英文:

Context itself is an interface, so you could create a new logger context in the logger middleware that has the methods you would need to get the behavior you are after.

Something like this:

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))
}
}

huangapple
  • 本文由 发表于 2017年2月23日 04:04:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/42401197.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定