传入请求的上下文

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

Context of incoming request

问题

有时候我会遇到“上下文”概念,通常为所有传入的请求创建。最近我读了一篇Go博客文章,介绍了如何使用golang.org/x/net/context包。然而,尽管我尝试使用文章中的代码并重现逻辑,我仍然很难理解如何在每个传入的请求中使用它,以及为什么这样做有用。

我应该如何组织我的代码,使用golang.org/x/net/context包为每个传入的请求创建上下文(上下文通常包含什么)?有人可以给一个简单的例子,并解释为什么这样做很有用,为什么经常使用它吗?

英文:

From time to time I am faced with the "Context" concept which, as a rule is created for all incoming requests. Recently I've read the Go blog article that describes using the golang.org/x/net/context package. However, after playing with the code and trying to reproduce the logic of the article, I still hardly understand how to use it for every incoming request and even why it is useful for this.

How should I organize my code to create context (and what should it contain, generally) for every incoming request using the golang.org/x/net/context package? Could anybody give a little example and explain what is so useful and why so frequently used?

答案1

得分: 15

上下文传递最常见的需求之一是将出站请求与入站请求相关联。我已经在各种情况下使用过这个功能,例如:

  • 我希望数据库组件的错误日志包含来自HTTP请求的完整URL。
  • 入站的HTTP请求包含一组标头,我需要保留并传递给调用的下游HTTP服务(可能是出于跟踪目的)。
  • 我希望在其他组件中检查入站的HTTP请求,以进行访问控制、用户身份验证或其他操作。这可以在HTTP处理程序层或应用程序的其他部分进行。

许多编程语言和平台都有方便/神奇的方法来获取当前的HTTP请求。C#有全局可用的HttpRequest.Current(通过线程本地存储)供任何想要了解当前HTTP请求上下文的人使用。您可以在其中设置任意数据以传递各种上下文数据。其他平台也有类似的功能。

由于Go语言没有用于goroutine本地存储的功能,因此无法在当前HTTP请求的上下文中存储全局变量。相反,惯用的做法是在系统边界(即入站请求)处初始化上下文,并将其作为参数传递给需要访问该信息的任何下游组件。

一种非常简单的方法是创建一个带有当前HTTP请求的上下文对象,并将其传递给其他组件:

func someHandler(w http.ResponseWriter, r *http.Request) {
   ctx := context.WithValue(context.Background(), "request", r)
   myDatabase.doSomething(ctx, ...)
}

当然,您可以将其限制为需要传递的更有针对性的数据,而不是整个请求。

上下文包还有助于处理超时或截止时间的常见框架(我认为该博客在这方面做得还不错)。

请注意,上下文包不会为您强制执行超时。由接收上下文对象的组件来监视Done通道并自行取消其HTTP请求、数据库调用、计算或其他操作。

编辑 - 关于超时

能够从组件外部管理超时非常有用。如果我有一个数据库模块,我不需要硬编码超时值,只需要能够处理来自外部的超时触发。

我曾经这样做过:在一个服务中,每个入站请求会进行多个数据库/服务调用。如果总时间超过1秒,我希望中止所有出站操作并返回部分或错误结果。在顶层使用带有超时的上下文进行初始化,并将其传递给所有依赖项,这是一种非常简单的管理方法。

依赖项监听Done通道并中止工作的方式并不总是优雅的,但正如该博客所示,也不是非常痛苦。

英文:

One of the most common needs for context passing is correlating outgoing requests to incoming requests. I have used this for a variety of purposes, for example:

  • I want error logs for my database component to include the full url from the http request it is a result of.
  • Incoming http requests contain a set of headers that I need to preserve and pass on to other http services I call downstream (maybe for tracking reasons).
  • I want to examine the incoming http request in some other component to do access control or user authentication or whatever. This could be at the http handler layer, or some other part of my application.

Many languages and platforms have convenient/magical ways to get the current Http request. C# has HttpRequest.Current which is globally available (via thread local storage) to anyone who wants to know the context of the current http request. You can set arbitrary data on it to communicate various context data. Other platforms have similar facilities.

Since go has no facilities for goroutine local storage, there is no way to store a global variable in the context of the current http request. Instead, it is idiomatic to initialize the context at the boundary of your system (an incoming request), and pass it as an argument to any downstream components that need access to that information.

One super simple way to do this would be to make a context object with the current http request and pass that around:

func someHandler(w http.ResponseWriter, r * http.Request){
   ctx := context.WithValue(context.Background(),"request",r)
   myDatabase.doSomething(ctx,....)
}

You can of course limit it to a more targeted set of data you need to pass around rather than the entire request.

The other thing that the context package helps with (and I think that blog does an ok job of pointing out), is a common framework for timeouts or deadlines.

Note that the context package does not enforce timeouts for you. It is up to the components receiving a context object to watch the Done channel and self-cancel their own http request or database call or calculation or whatever.

edit - on timeouts

It is extremely useful to be able to manage timeouts from the outside of a component. If I have a database module, I don't need to hardcode timeout values, just be able to handle a timeout triggered from the outside.

One way I have done this is in a service that makes multiple db / service calls per incoming request. If the total time exceeds 1 second, I want to abort all outbound operations and return a partial or error result. Initializing the context with a timeout at the top level and passing it to all dependencies is a really easy way to manage this.

It is not always pretty for the dependency to listen to the Done channel and abort it's work, but as the blog shows, it is not terribly painful either.

答案2

得分: 0

我同意@captncraig在这里的答案。但我只想更新与传递上下文相关的代码。

假设你有一个路由/foo
http.Handle("/foo", SimpleContextHandler(passContextHandler))

在这种情况下,SimpleContextHandler就像一个构造函数,你可以像下面这样初始化它。

type SimpleContextHandler func(w http.ResponseWriter, r *http.Request)

func (fn SimpleContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    ctx := context.WithValue(context.Background(), "Foo", "bar")
    fn(w, r.WithContext(ctx))
}

func passContextHandler(w http.ResponseWriter, r *http.Request) {
    bar := r.Context().Value("Foo").(string)
    w.Write([]byte(bar))
}

玩得开心,如果你觉得还有改进的地方,请编辑我的答案,因为我也是几周前开始学习Go语言的 传入请求的上下文

英文:

I agree with @captncraig Answer here. But I just wanted to update the code related to passing context with ease.

Let say you have a route /foo
http.Handle("/foo", SimpleContextHandler(passContextHandler))

In this case SimpleContextHandler is like a constructor and you initiate it like below.

type SimpleContextHandler func(w http.ResponseWriter, r *http.Request)

func (fn SimpleContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := context.WithValue(context.Background(), "Foo", "bar")
	fn(w, r.WithContext(ctx))
}

func passContextHandler(w http.ResponseWriter, r *http.Request) {
	bar := r.Context().Value("Foo").(string)
	w.Write([]byte(bar))
}

Have fun, please edit my answer if you still feel it can be improved since I have also started with GoLang few weeks back 传入请求的上下文

huangapple
  • 本文由 发表于 2015年2月13日 03:39:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/28486324.html
匿名

发表评论

匿名网友

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

确定