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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
type CancelFunc func()
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
`WithValue`用于将一个请求范围相关的值设置到Context.
下面是源码包里的描述:
func WithValue(parent Context, key interface{}, val interface{}) Context
## 实战
说了这么多道理,还是来个实际的例子理解起来容易。
我们实现一个HTTP服务程序,这个程序处理 `/search?q=golang&timeout=1s` 这个的URL。这个URL查询"golang"关键字的相关信息,超时时间是1s.我们将这个请求转给Google查询API处理,并对查询结果进行一些渲染工作。
我们的代码由以下3部分组成:
* [server](https: * [userip](https: * [google](https:
### server实现
server程序处理像`/search?q=golang`这样的url请求.我们注册handleSearch函数用于处理/search这个url.这个handler会创建一个初始的Context的名为ctx,并会在处理结束时调用cancel.如果请求URL里有timeout参数,Context会在超时时自动取消:.
下面是具体的代码:
func handleSearch(w http.ResponseWriter, req *http.Request) { var ( ctx context.Context cancel context.CancelFunc ) timeout, err := time.ParseDuration(req.FormValue("timeout")) if err == nil { ctx, cancel = context.WithTimeout(context.Background(), timeout) } else { ctx, cancel = context.WithCancel(context.Background()) } defer cancel()
handler会使用userip包里提供的函数从请求里取出用户IP并设置到context里,后面的请求会使用这个ip.
query := req.FormValue("q") if query == "" { http.Error(w, "no query", http.StatusBadRequest) return }
userIP, err := userip.FromRequest(req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } ctx = userip.NewContext(ctx, userIP)
然后调用Google api进行查询.
start := time.Now() results, err := google.Search(ctx, query) elapsed := time.Since(start)
查询成功,处理结果.
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 }
### userip实现
userip包提供了解析用户IP的功能.Context提供了一个key-value map.key和value都是interface{}类型.key必须支持相等判断,values必须能够安全地并多个goroutines并发访问.userip包隐藏了这个map的细节,并提供了对Context值的强类型访问.
为了避免冲突,userip定义了未导出的key类型.
type key int
const userIPKey key = 0
* `FromRequest`从http.Request里解析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`返回一个设置了userIP的新Context:
func NewContext(ctx context.Context, userIP net.IP) context.Context { return context.WithValue(ctx, userIPKey, userIP) }
* `FromContext`从Context里获取userIP:
func FromContext(ctx context.Context) (net.IP, bool) { net.IP断言对于nil返回的ok=false. userIP, ok := ctx.Value(userIPKey).(net.IP) return userIP, ok }
### google包
gooe.Search函数创建一个使用Google Web Search API的HTTP请求,并解析返回的JSON结果.它接受一个Context参数并在ctx.Done关闭时直接返回。
Google Web Search API需要使用user IP作为查询参数.
func Search(ctx context.Context, query string) (Results, error) { 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 userIP, ok := userip.FromContext(ctx); ok { q.Set("userip", userIP.String()) } req.URL.RawQuery = q.Encode()
* Search使用了httpDo这个帮助函数用于提交http请求,并在ctx.Done时取消正在进行的处理.Search向httpDo传递了一个闭包函数用于处理HTTP响应:
var results Results err = httpDo(ctx, req, func(resp *http.Response, err error) error { if err != nil { return err } defer resp.Body.Close()
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 }) return results, err
* `httpDo`函数在一个新的goroutine里发起HTTP请求并处理响应.如果在goroutine退出之前ctx.Done则取消请求。
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { c := make(chan error, 1) req = req.WithContext(ctx) go func() { c <- f(http.DefaultClient.Do(req)) }() select { case <-ctx.Done(): <-c return ctx.Err() case err := <-c: return err } }
|