英文:
Way to pass data up to parent middleware?
问题
我对如何从一个处理程序中获取包装的处理程序返回的数据有一定的了解,但是否有一种Go语言的惯用方式来从包装的处理程序中获取返回值呢?以下是一个示例:我有一个accessLogHandler
和一个authHandler
。accessLogHandler
记录每个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。
我不喜欢这种方法的一些问题:
- 上下文是不可变的,但我在其中附加了一个可变的结构体,并在下游的其他地方对该结构体进行了修改。感觉像是一个hack。
- 我的
authHandler
现在对accessLog
包有一个包级别的依赖,因为我进行了类型断言为*AccessLog
。 - 理想情况下,我的
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:
- context is immutable, but I'm sticking a mutable struct on it and mutating said struct elsewhere downstream. Feels like a hack.
- My
authHandler
now has a package level dependency on theaccessLog
package, since I'm type asserting to*AccessLog
. - 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))
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论