使用`http.NewRequestWithContext()`创建的请求在传递给中间件时丢失了上下文。

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

Request created with http.NewRequestWithContext() looses context when passed to middleware

问题

在下面的程序中,我有两个路由器。一个在localhost:3000上工作,充当公共访问点。它还可以向另一个本地地址localhost:8000发送带有数据的请求,该地址用于处理数据。第二个路由器在localhost:8000上工作,处理第一个路由器的请求。

问题:
第一个路由器使用http.NewRequestWithContext()函数向第二个路由器发送带有上下文的请求。值被添加到上下文中,并将上下文添加到请求中。当请求到达第二个路由器时,之前添加的值不存在。

为了不在这里发布大量代码,一些诸如错误处理之类的东西没有被写入。

package main
import (
	"bytes"
	"context"
	"net/http"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
)

func main() {
	go func() {
		err := http.ListenAndServe(
			"localhost:3000",
			GetDataAndSolve(),
		)
		if err != nil {
			panic(err)
		}
	}()

	go func() {
		err := http.ListenAndServe( // 在GetDataAndSolve()中我们发送请求
			"localhost:8000", // 带有要处理的数据
			InternalService(),
		)
		if err != nil {
			panic(err)
		}
	}()

	// interrupt := make(chan os.Signal, 1) 
	// signal.Notify(interrupt, syscall.SIGTERM, syscall.SIGINT)
	// <-interrupt // 只是关闭程序的一种酷炫方式,如果需要可以取消注释
}
func GetDataAndSolve() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.Logger)

	r.Get("/tasks/str", func(rw http.ResponseWriter, r *http.Request) {
		// 接收要处理的数据...
		taskCtx := context.WithValue(r.Context(), "str", "strVar") // 值被
		postReq, err := http.NewRequestWithContext(                // 存储到上下文中
			taskCtx, // 上下文被赋予请求
			"POST",
			"http://localhost:8000/tasks/solution",
			bytes.NewBuffer([]byte("something")),
		)
		postReq.Header.Set("Content-Type", "application/json") // 指定要发送的
		if err != nil {                                        // 内容类型
			return
		}

		resp, err := http.DefaultClient.Do(postReq) // 执行实际请求
		// 请继续到Solver()

		// 对resp进行处理
		// 尽管没有正确的上下文到达中间件
		// 但是这里的resp包含了正确的上下文的请求
	})

	return r
}
func Solver(next http.Handler) http.Handler { // 在发送postReq后我们到达这里
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		if r.Context().Value("str").(string) == "" {
			return // 请求到达时没有在其上下文中包含"str"
		}

		ctxWithResult := context.WithValue(r.Context(), "result", mockFunc(r.Context()))
		next.ServeHTTP(rw, r.Clone(ctxWithResult))
	})
}

func InternalService() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.Logger)

	r.With(Solver).Post("/tasks/solution", emptyHandlerFunc)

	return r
}
英文:

In program bellow I have two routers. One is working at localhost:3000 and acts like a public access point. It also may send requests with data to another local address which is localhost:8000 where data is being processed. Second router is working at localhost:8000 and handles processing requests for the first router.

Problem

The first router sends a request with context to the second using http.NewRequestWithContext() function. The value is being added to the context and the context is added to request. When request arrives to the second router it does not have value that was added previously.

Some things like error handling are not being written to not post a wall of code here.

package main
import (
	"bytes"
	"context"
	"net/http"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
)

func main() {
	go func() {
		err := http.ListenAndServe(
			"localhost:3000",
			GetDataAndSolve(),
		)
		if err != nil {
			panic(err)
		}
	}()

	go func() {
		err := http.ListenAndServe( // in GetDataAndSolve() we send requests
			"localhost:8000", // with data for processing
			InternalService(),
		)
		if err != nil {
			panic(err)
		}
	}()

	// interrupt := make(chan os.Signal, 1) 
	// signal.Notify(interrupt, syscall.SIGTERM, syscall.SIGINT)
	// <-interrupt // just a cool way to close the program, uncomment if you need it
}
func GetDataAndSolve() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.Logger)

	r.Get("/tasks/str", func(rw http.ResponseWriter, r *http.Request) {
		// receiving data for processing...
		taskCtx := context.WithValue(r.Context(), "str", "strVar") // the value is being
		postReq, err := http.NewRequestWithContext(                // stored to context
			taskCtx, // context is being given to request
			"POST",
			"http://localhost:8000/tasks/solution",
			bytes.NewBuffer([]byte("something")),
		)
		postReq.Header.Set("Content-Type", "application/json") // specifying for endpoint
		if err != nil {                                        // what we are sending
			return
		}

		resp, err := http.DefaultClient.Do(postReq) // running actual request
		// pls, proceed to Solver()

		// do stuff to resp
		// also despite arriving to middleware without right context
		// here resp contains a request with correct context
	})

	return r
}
func Solver(next http.Handler) http.Handler { // here we end up after sending postReq
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		if r.Context().Value("str").(string) == "" {
			return // the request arrive without "str" in its context
		}

		ctxWithResult := context.WithValue(r.Context(), "result", mockFunc(r.Context()))
		next.ServeHTTP(rw, r.Clone(ctxWithResult))
	})
}

func InternalService() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.Logger)

	r.With(Solver).Post("/tasks/solution", emptyHandlerFunc)

	return r
}

答案1

得分: 6

你对上下文的理解是不正确的。

上下文(在某种程度上简化,并参考NewRequestWithContext API),只是一个内存中的对象,你可以使用它来控制请求的生命周期(处理/触发取消)。

然而,你的代码正在进行HTTP调用,这会通过HTTP协议在网络上传输(编组)。这个协议不理解golang的上下文或其值。
在你的场景中,/tasks/str/tasks/solution都在同一台服务器上运行。如果它们在不同的服务器上,可能还是不同的语言和应用服务器,那么上下文就无法传递。

由于这些API在同一台服务器上,也许你可以避免进行完整的HTTP调用,而是直接调用API/方法。这样可能会更快。

如果你仍然想从上下文中发送附加值,那么你将需要利用其他属性,如HTTP头、参数、主体来传递所需的信息。这篇文章提供了更多信息关于如何在HTTP中序列化上下文数据。

英文:

Your understanding of context is not correct.

Context (simplifying to an extent and in reference to NewRequestWithContext API), is just an in-memory object using which you can control the lifetime of the request (Handling/Triggering cancellations).

However your code is making a HTTP call, which goes over the wire (marshaled) using HTTP protocol. This protocol doesn't understand golang's context or its values.
In your scenario, both /tasks/str and /tasks/solution are being run on the same server. What if they were on different servers, probably different languages and application servers as well, So the context cannot be sent across.

Since the APIs are within the same server, maybe you can avoid making a full blown HTTP call and resort to directly invoking the API/Method. It might turn out to be faster as well.

If you still want to send additional values from context, then you'll have to make use of other attributes like HTTP Headers, Params, Body to send across the required information. This can provide more info on how to serialize data from context over HTTP.

huangapple
  • 本文由 发表于 2021年11月5日 03:45:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/69845005.html
匿名

发表评论

匿名网友

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

确定