传入请求的上下文

Context of incoming request

我有时会遇到 "Context" 概念,通常这是为所有传入请求创建的。最近我阅读了描述使用 golang.org/x/net/context 包的 Go blog article。然而,在玩代码并尝试重现文章的逻辑之后,我仍然很难理解如何为每个传入请求使用它,甚至为什么它对此有用。

我应该如何组织我的代码来为使用 golang.org/x/net/context 包的每个传入请求创建上下文(以及它通常应该包含什么)?任何人都可以举一个小例子并解释什么如此有用以及为什么如此频繁使用吗?

上下文传递的最常见需求之一是将传出请求与传入请求相关联。我将其用于多种用途,例如:

  • 我希望我的数据库组件的错误日志包含完整的 url 来自 http 请求的结果。
  • 传入的 http 请求包含一组 headers,我需要将其保存并传递给我调用下游的其他 http 服务(可能出于跟踪原因)。
  • 我想检查一些其他组件中传入的 http 请求,以执行访问控制或用户身份验证或其他任何操作。这可能在 http 处理程序层,或我的应用程序的其他部分。

许多语言和平台都有 convenient/magical 获取当前 Http 请求的方法。 C# 有 HttpRequest.Current 全局可用(通过线程本地存储)给任何想知道当前 http 请求上下文的人。您可以在其上设置任意数据以传达各种上下文数据。其他平台也有类似的设施。

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

一个超级简单的方法是用当前的 http 请求创建一个上下文 object 并传递它:

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

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

context 包有帮助的另一件事(我认为博客在指出方面做得很好)是超时或截止日期的通用框架。

请注意,上下文包不会为您强制执行超时。由接收上下文 object 的组件来观看 Done 通道和 self-cancel 它们自己的 http 请求或数据库调用或计算等等。

编辑 - 关于超时

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

我完成此操作的一种方法是在每个传入请求进行多个数据库/服务调用的服务中。如果总时间超过 1 秒,我想中止所有出站操作和 return 部分或错误结果。在顶层用超时初始化上下文并将其传递给所有依赖项是一种非常简单的管理方法。

对于依赖项来说,监听 Done 通道并中止它的工作并不总是很好,但正如博客所示,它也不是非常痛苦。

我同意@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))
}

玩得开心,如果您仍然觉得可以改进,请编辑我的答案,因为几周前我也开始使用 GoLang :)