如何将子中间件的值传递给父中间件?

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

How to propagate value from child middleware to parent?

问题

我正在尝试通过中间件模式自定义请求管道,以下是代码:

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Hello, middleware!")
}

func middleware1(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[START] middleware1")
		ctx := r.Context()
		ctx = context.WithValue(ctx, middleware1Key, middleware1Value)
		r = r.WithContext(ctx)
		next(w, r)
		fmt.Println("[END] middleware1")
		ctx = r.Context()
		if val, ok := ctx.Value(middleware2Key).(string); ok {
			fmt.Printf("Value from middleware2 %s \n", val)
		}
	}
}

func middleware2(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[START] middleware2")
		ctx := r.Context()
		if val, ok := ctx.Value(middleware1Key).(string); ok {
			fmt.Printf("Value from middleware1 %s \n", val)
		}
		ctx = context.WithValue(ctx, middleware2Key, middleware2Value)
		r = r.WithContext(ctx)
		next(w, r)
		fmt.Println("[END] middleware2")
	}
}

func main() {
	mux := http.NewServeMux()
	middlewares := newMws(middleware1, middleware2)
	mux.HandleFunc("/hello", middlewares.then(helloHandler))
	if err := http.ListenAndServe(":8080", mux); err != nil {
		panic(err)
	}
}

输出结果为:

[START] middleware1
[START] middleware2
Value from middleware1 middleware1Value
Hello, middleware!
[END] middleware2
[END] middleware1

根据输出结果,值可以从父级传递到子级,但是如果子级向上下文中添加了一些内容,父级是看不到的。

你想知道如何将子级中间件的值传递给父级。

英文:

I am trying to customize request pipeline through middleware pattern, the code as follow:

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Hello, middleware!")
}
func middleware1(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[START] middleware1")
		ctx := r.Context()
		ctx = context.WithValue(ctx, middleware1Key, middleware1Value)
		r = r.WithContext(ctx)
		next(w, r)
		fmt.Println("[END] middleware1")
		ctx = r.Context()
		if val, ok := ctx.Value(middleware2Key).(string); ok {
			fmt.Printf("Value from middleware2 %s \n", val)
		}

	}
}
func middleware2(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[START] middleware2")
		ctx := r.Context()
		if val, ok := ctx.Value(middleware1Key).(string); ok {
			fmt.Printf("Value from middleware1 %s \n", val)
		}
		ctx = context.WithValue(ctx, middleware2Key, middleware2Value)
		r = r.WithContext(ctx)
		next(w, r)
		fmt.Println("[END] middleware2")

	}
}
func main() {
	mux := http.NewServeMux()
	middlewares := newMws(middleware1, middleware2)
	mux.HandleFunc("/hello", middlewares.then(helloHandler))
	if err := http.ListenAndServe(":8080", mux); err != nil {
		panic(err)
	}

}

and the output is :

[START] middleware1
[START] middleware2
Value from middleware1 middleware1Value
Hello, middleware!
[END] middleware2
[END] middleware1

According to the output, the value could pass from parent to the child , while, if the child add something to the context, it is invisible to the parent

How can I propagate value from child middleware to parent?

答案1

得分: 1

你正在做的是通过WithContext方法创建一个指向修改后的http.Request的新指针。所以,如果你将它传递给链中的下一个中间件,一切都会按预期工作,因为你将这个新指针作为参数传递了进去。
如果你想修改请求并使持有指针的人能够看到修改后的值,你需要取消引用指针并设置修改后的值。

所以在你的'child'中间件中,不要这样写:

r = r.WithContext(ctx)

而是这样写:

*r = *r.WithContext(ctx)

这是一个理解Go中指针的好练习,但是在生产代码中你不应该做类似的操作。文档对此有明确的说明。请参阅https://pkg.go.dev/net/http#Handler。

另一个可能的解决方案(而不是操纵请求本身)是在上下文中传递一个映射,并从映射中读取/写入。所以在你的第一个中间件中:

ctx := r.Context()
m := make(map[string]string)
m[middleware1Key] = middleware1Value
ctx = context.WithValue(ctx, dummyStoreKey, m)
r = r.WithContext(ctx)
...
if val, ok := m[middleware2Key]; ok {
	fmt.Printf("Value from middleware2 %s \n", val)
}

在第二个中间件中:

ctx := r.Context()
if store, ok := ctx.Value(dummyStoreKey).(map[string]string); ok {
if val, ok := store[middleware1Key]; ok {
fmt.Printf("Value from middleware1 %s \n", val)
}
store[middleware2Key] = middleware2Value
}

你可以在流水线中添加一个AddStoreMiddleware作为第一个中间件,然后在每个后继中使用它(如果需要)。请记住,Go中的映射不是并发安全的,所以在某些微妙的情况下,你应该对访问进行串行化处理。

英文:

What you're doing is creating a new pointer to the modified http.Request via WithContext method. So if you're passing it to next middleware in the chain everything works as expected since you're passing this new pointer as an argument.
If you want to modify the request and make it visible for those who hold the pointer to it, you need to dereference the pointer and set the modified value.

So in your 'child' middleware instead of:

r = r.WithContext(ctx)

Just do the following:

*r = *r.WithContext(ctx)

Good exercise to understand pointers in Go but you SHOULD NOT do similar operations in your production code. The docs are clear about it. See https://pkg.go.dev/net/http#Handler.

Another possible solution (without messing with the request itself) is to pass a map inside a context and read/write from/to map instead. So in your first middleware:

ctx := r.Context()
m := make(map[string]string)
m[middleware1Key] = middleware1Value
ctx = context.WithValue(ctx, dummyStoreKey, m)
r = r.WithContext(ctx)
...
if val, ok := m[middleware2Key]; ok {
	fmt.Printf("Value from middleware2 %s \n", val)
}

And in the second one:

ctx := r.Context()
if store, ok := ctx.Value(dummyStoreKey).(map[string]string); ok {
if val, ok := store[middleware1Key]; ok {
fmt.Printf("Value from middleware1 %s \n", val)
}
store[middleware2Key] = middleware2Value
}

You could add a AddStoreMiddleware as the first one in the pipeline and then use it in each successor if needed. Remember, maps in Go are not concurrently safe so in some subtle cases you should serialize access.

huangapple
  • 本文由 发表于 2022年5月28日 21:49:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/72416203.html
匿名

发表评论

匿名网友

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

确定