This is a creation in Article, where the information may have evolved or changed.
Brief introduction
In the go HTTP package server, each request has a corresponding goroutine to handle. The request handler function typically launches additional goroutine to access the backend services, such as the database and the RPC service. The goroutine used to process a request usually requires access to some data that is specific to the request, such as the authentication information of the end user, the authentication token, and the deadline for the request. When a request is canceled or timed out, all the goroutine that are used to process the request should exit quickly before the system can release the resources that these goroutine occupy.
Inside Google, we've developed Context
packages that are designed to simplify the processing of data, cancellation signals, deadlines, and so on for multiple goroutine that process a single request, which can involve multiple API calls. You can go get golang.org/x/net/context
get this package by command. What this article says is that if you use this package, you will also provide a complete example.
Read suggestions
This article covers the done channel, and if you don't understand the concept, read the "Go language concurrency Model: using channel as a UNIX pipe."
Since access golang.org/x/net/context
requires a ladder, you can access it on GitHub on the mirror.
If you want to download the code in this article, you can see the "Related LINKS" link at the end of the article.
Package context
The core of the context package is the struct context, which declares the following:
// A Context carries a deadline, cancelation signal, and request-scoped values// across API boundaries. Its methods are safe for simultaneous use by multiple// goroutines.type Context interface { // Done returns a channel that is closed when this `Context` is canceled // or times out. Done() <-chan struct{} // Err indicates why this Context was canceled, after the Done channel // is closed. Err() error // Deadline returns the time when this Context will be canceled, if any. Deadline() (deadline time.Time, ok bool) // Value returns the value associated with key or nil if none. Value(key interface{}) interface{}}
Note: Here we have simplified the description, a more detailed description of the view Godoc:context
Done
method returns a channel, which Context
is a cancellation signal for functions that run in a way. When this channel is closed, the functions mentioned above should terminate the work at hand and return immediately. After that, the Err
method returns an error telling you why it Context
was canceled. Done
more details on Channel View previous article "Go language concurrency Model: using Channel as UNIX pipe".
One Context
cannot have a Cancel
method, and we can only channel it to Done
receive data. The reason behind this is consistent: the function that receives the cancellation signal and the function that sends the signal is usually not one. A typical scenario is that the parent operation starts Goroutine for a child action operation, and the child action cannot cancel the parent operation. As a compromise, the WithCancel
function (which is discussed later) provides a way to cancel the new one Context
.
Context
The object is thread-safe, and you can Context
pass an object to any number of gorotuine,
When the cancel operation is performed on it, all Goroutine will receive a cancellation signal.
Deadline
method allows the function to determine whether they should start working. If there is too little time left, perhaps these functions are not worth starting. Code, we can also use Deadline
objects to set deadlines for I/O operations.
Value
Method allows an Context
object to carry data from the request scope, which must be thread-safe.
Inherit context
The context package provides functions to assist users in Context
creating new objects from existing objects Context
.
These Context
objects form a tree: When an Context
object is canceled, all inherited from it Context
will be canceled.
Background
is Context
the root of all object trees and it cannot be canceled. It declares the following:
// Background returns an empty Context. It is never canceled, has no deadline,// and has no values. Background is typically used in main, init, and tests,// and as the top-level `Context` for incoming requests.func Background() Context
WithCancel
and WithTimeout
functions return inherited Context
objects that can be canceled earlier than their parents Context
.
When the request handler function returns, the request is associated with a Context
cancellation. When you use multiple replicas to send a request, you can use WithCancel
the Cancel extra request. WithTimeout
useful when setting a deadline for back-end server requests. Here are the declarations of these three functions:
// WithCancel returns a copy of parent whose Done channel is closed as soon as// parent.Done is closed or cancel is called.func WithCancel(parent Context) (ctx Context, cancel CancelFunc)// A CancelFunc cancels a Context.type CancelFunc func()// WithTimeout returns a copy of parent whose Done channel is closed as soon as// parent.Done is closed, cancel is called, or timeout elapses. The new// Context's Deadline is the sooner of now+timeout and the parent's deadline, if// any. If the timer is still running, the cancel function releases its// resources.func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithValue
The function can relate the data of the request scope to the Context
object. The statement reads as follows:
// WithValue returns a copy of parent whose Value method returns val for key.func WithValue(parent Context, key interface{}, val interface{}) Context
Of course, to know Context
how the package works, the best way is to look at a chestnut.
A chestnut: Google Web Search
Our example is an HTTP service that can forward a similar /search?q=golang&timeout=1s
request to a
Google Web Search API, and then render the returned results. timeout
The parameter is used to tell the server when the request is canceled.
The code for this example is stored in three packages:
Server: It provides the main function and handles /search
the HTTP handler
Userip: It is able to parse the user's IP from the request and bind the request to an Context
object.
Google: It contains the Search function, which is used to send a request to Google.
Drill down into the server program
The server program processes similar /search?q=golang
requests and returns search results for the Google API. It handleSearch
registers the function with the /search
route. The handler function creates a Context
ctx and initializes it to ensure that the Context
handler returns when canceled. If the requested URL parameter is included timeout
, it timeout
Context
is automatically canceled when it expires.
The code for Handlesearch is as follows:
func handleSearch(w http.ResponseWriter, req *http.Request) { // ctx is the `Context` for this handler. Calling cancel closes the // ctx.Done channel, which is the cancellation signal for requests // started by this handler. var ( ctx context.Context cancel context.CancelFunc ) timeout, err := time.ParseDuration(req.FormValue("timeout")) if err == nil { // The request has a timeout, so create a `Context` that is // canceled automatically when the timeout expires. ctx, cancel = context.WithTimeout(context.Background(), timeout) } else { ctx, cancel = context.WithCancel(context.Background()) } defer cancel() // Cancel ctx as soon as handleSearch returns.
The handler function (handlesearch) resolves the query parameter from the request and then resolves the client IP through the Userip package. The Client IP is used here when the backend sends the request, so the Handlesearch function attach it to the Context
object ctx. The code is as follows:
// Check the search query.query := req.FormValue("q")if query == "" { http.Error(w, "no query", http.StatusBadRequest) return}// Store the user IP in ctx for use by code in other packages.userIP, err := userip.FromRequest(req)if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return}ctx = userip.NewContext(ctx, userIP)
The handler takes Context
ctx
the object and query
calls with google.Search
the code as follows:
// Run the Google search and print the results.start := time.Now()results, err := google.Search(ctx, query)elapsed := time.Since(start)
If the search succeeds, the processing function renders the search results, with the following code:
if err := resultsTemplate.Execute(w, struct { Results google.Results Timeout, Elapsed time.Duration}{ Results: results, Timeout: timeout, Elapsed: elapsed,}); err != nil { log.Print(err) return}
Deep Userip Package
The Userip package offers two features:
Resolves the client IP from the request;
Associates the Client IP to an Context
object.
An Context
object provides a key-value mapping, both the type of key and value are interface{}, but the key must satisfy the equivalence (which can be compared) and value must be thread safe. A package similar to userip
the one that hides the details of the mapping provides a Context
strongly typed access to a particular type.
To avoid key collisions, userip
a non-output type is defined key
, and a value of that type is used as Context
the key. The code is as follows:
// 为了避免与其他包中的 `Context` key 冲突// 这里不输出 key 类型 (首字母小写)type key int// userIPKey 是 user IP 的 `Context` key// 它的值是随意写的。如果这个包中定义了其他// `Context` key,这些 key 必须不同const userIPKey key = 0
The function is FromRequest
used from an HTTP. The Request object resolves the userip:
func FromRequest(req *http.Request) (net.IP, error) { ip, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr) }
NewContext
the function returns a new Context
object, which is userip by the carrier:
func NewContext(ctx context.Context, userIP net.IP) context.Context { return context.WithValue(ctx, userIPKey, userIP)}
The function FromContext
Context
parses userip from an object:
func FromContext(ctx context.Context) (net.IP, bool) { // ctx.Value returns nil if ctx has no value for the key; // the net.IP type assertion returns ok=false for nil. userIP, ok := ctx.Value(userIPKey).(net.IP) return userIP, ok}
Dive into Google Pack
function google.Search
to send an HTTP request to the Google Web Search API and parse the returned JSON data. The function receives an Context
object CTX as the first parameter, and once the request has not been returned, ctx.Done
the function returns immediately if it is closed.
The Google Web Search API request contains the query keyword and user IP two parameters. The specific implementation is as follows:
func Search(ctx context.Context, query string) (Results, error) { // Prepare the Google Search API request. req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil) if err != nil { return nil, err } q := req.URL.Query() q.Set("q", query) // If ctx is carrying the user IP address, forward it to the server. // Google APIs use the user IP to distinguish server-initiated requests // from end-user requests. if userIP, ok := userip.FromContext(ctx); ok { q.Set("userip", userIP.String()) } req.URL.RawQuery = q.Encode()
The function Search
uses an auxiliary function httpDo
to send an HTTP request and ctx.Done
cancel the request when it is closed (if the request is still being processed or returned). The function is Search
passed to httpDo
a closure to handle HTTP results. Here are the specific implementations:
var results Resultserr = httpDo(ctx, req, func(resp *http.Response, err error) error { if err != nil { return err } defer resp.Body.Close() // Parse the JSON search result. // https://developers.google.com/web-search/docs/#fonje var data struct { ResponseData struct { Results []struct { TitleNoFormatting string URL string } } } if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } for _, res := range data.ResponseData.Results { results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL}) } return nil})// httpDo waits for the closure we provided to return, so it's safe to// read results here.return results, err
The function httpDo
sends HTTP requests and processing results in a new goroutine. If it ctx.Done
is already closed and the goroutine of the processing request still exists, then the request is canceled. Here are the specific implementations:
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { // Run the HTTP request in a goroutine and pass the response to f. tr := &http.Transport{} client := &http.Client{Transport: tr} c := make(chan error, 1) go func() { c <- f(client.Do(req)) }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for f to return. return ctx.Err() case err := <-c: return err }}
Use in your own codeContext
Many server frameworks provide the packages and types that manage request scope data. We can define an Context
implementation of an interface,
Glue the code that already has Context
the code and the expected parameters together.
For a chestnut, the Gorilla Framework's Github.com/gorilla/context package allows the handler function (handlers) to combine the data and the request with the HTTP request to the Key-value pair's mapping. In Gorilla.go, we provide a Context
concrete implementation where the value returned by this implementation is associated with a specific HTTP request in the gorilla package.
There are also some packages that implement a similar Context
cancellation mechanism. For example, there is a Kill method in Tomb, which sends a Dying
cancellation signal by closing the channel named. Tomb
also provides a way to wait for the goroutine to exit, similar to the sync.WaitGroup
. In Tomb.go, we provide an Context
implementation when its parent Context
is canceled
Or Tomb
when an object is killed, the Context
object is also canceled.
Conclusion
At Google, we ask the Go programmer to Context
pass each function as the first parameter to the portal request and the export request link. This mechanism ensures that the go projects developed by multiple teams work well together, on the other hand, it is a simple timeout and cancellation mechanism, which ensures that critical section data (such as security credentials) can be transferred smoothly in different go projects.
If you are Context
building a server framework on top, you need an implementation of your own to Context
Context
build a bridge between the framework and the code of the desired parameters.
Of course, the Client library also needs to receive an Context
object. After a common interface between the request scope data and the cancellation is established, the developer uses the Context
It's easy to share code and create scalable services.
Original Author: Sameer Ajmani Translator: Oscar
Next preview: Go language concurrency Model: Use Select (original link).
RELATED LINKS
Original link
Code location
Code location (Mirror)
Mirror of the package net
Google Web Search API
Sweep code attention to the public number "go language deep"