-
Notifications
You must be signed in to change notification settings - Fork 4
/
values.go
67 lines (63 loc) · 2.33 KB
/
values.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package ctxutil
import "context"
// WithValues composes values from multiple contexts.
// It returns a context that exposes the deadline and cancel of `ctx`,
// and combined values from `ctx` and `values`.
// A value in `ctx` context overrides a value with the same key in `values` context.
//
//Consider the following standard HTTP Go server stack:
//
// 1. Middlewares that extract user credentials and request id from the
// headers and inject them to the `http.Request` context as values.
// 2. The `http.Handler` launches an asynchronous goroutine task which
// needs those values from the context.
// 3. After launching the asynchronous task the handler returns 202 to
// the client, the goroutine continues to run in background.
//
// Problem Statement:
// * The async task can't use the request context - it is cancelled
// as the `http.Handler` returns.
// * There is no way to use the context values in the async task.
// * Specially if those values are used automatically with client
// `http.Roundtripper` (extract the request id from the context
// and inject it to http headers in a following request.)
//
// The suggested function `ctx := ctxutil.WithValues(ctx, values)`
// does the following:
//
// 1. When `ctx.Value()` is called, the key is searched in the
// original `ctx` and if not found it searches in `values`.
// 2. When `Done()`/`Deadline()`/`Err()` are called, it is uses
// original `ctx`'s state.
//
// ### Example
//
// This is how an `http.Handler` should run a goroutine that need
// values from the context.
//
// func handle(w http.ResponseWriter, r *http.Request) {
// // [ do something ... ]
//
// // Create async task context that enables it run for 1 minute, for example
// asyncCtx, asyncCancel = ctxutil.WithTimeout(context.Background(), time.Minute)
// // Use values from the request context
// asyncCtx = ctxutil.WithValues(asyncCtx, r.Context())
// // Run the async task with it's context
// go func() {
// defer asyncCancel()
// asyncTask(asyncCtx)
// }()
// }
func WithValues(ctx, values context.Context) context.Context {
return &composed{Context: ctx, nextValues: values}
}
type composed struct {
context.Context
nextValues context.Context
}
func (c *composed) Value(key interface{}) interface{} {
if v := c.Context.Value(key); v != nil {
return v
}
return c.nextValues.Value(key)
}