传入请求的上下文

传入请求的上下文

问题描述:

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?

我有时会遇到“上下文” strong>概念, 为所有传入请求创建规则。 最近,我阅读了 Go博客文章,其中描述了使用 golang.org/x/net/context code >包装。 但是,在玩完代码并尝试重现本文的逻辑之后,我仍然几乎不了解如何在每个传入请求中使用它,以及为什么它对此有用。 p>

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

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.

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 :)