Merge lp:~pedronis/ubuntu-push/update-http13 into lp:ubuntu-push/automatic
- update-http13
- Merge into automatic
Proposed by
Samuele Pedroni
Status: | Merged |
---|---|
Approved by: | Samuele Pedroni |
Approved revision: | 187 |
Merged at revision: | 186 |
Proposed branch: | lp:~pedronis/ubuntu-push/update-http13 |
Merge into: | lp:ubuntu-push/automatic |
Diff against target: |
4164 lines (+1453/-1014) 20 files modified
debian/changelog (+1/-0) http13client/Makefile (+2/-1) http13client/_patches/empty_server.patch (+118/-42) http13client/_patches/fix_code.patch (+73/-65) http13client/_patches/fix_status.patch (+33/-20) http13client/_patches/fix_tests.patch (+338/-384) http13client/_using.txt (+3/-3) http13client/client.go (+11/-3) http13client/client_test.go (+92/-0) http13client/cookie.go (+19/-37) http13client/cookie_test.go (+0/-304) http13client/request.go (+62/-50) http13client/request_test.go (+48/-8) http13client/response.go (+52/-5) http13client/response_test.go (+4/-0) http13client/responsewrite_test.go (+117/-1) http13client/server.go (+11/-5) http13client/transfer.go (+59/-27) http13client/transport.go (+114/-42) http13client/transport_test.go (+296/-17) |
To merge this branch: | bzr merge lp:~pedronis/ubuntu-push/update-http13 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+223916@code.launchpad.net |
Commit message
update http13client from the actual go1.3 release
Description of the change
update http13client from the actual go1.3 release
To post a comment you must log in.
- 187. By Samuele Pedroni
-
changelog
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2014-06-20 12:33:03 +0000 |
3 | +++ debian/changelog 2014-06-20 13:24:39 +0000 |
4 | @@ -3,6 +3,7 @@ |
5 | [ Samuele Pedroni ] |
6 | * Support registering tokens and sending notifications with a token |
7 | * Register script and scripts unicast support |
8 | + * Update http13client from the actual go1.3 release |
9 | |
10 | [ Roberto Alsina ] |
11 | * Make signing-helper generate a HTTP header instead of a querystring, |
12 | |
13 | === modified file 'http13client/Makefile' |
14 | --- http13client/Makefile 2014-03-20 12:15:36 +0000 |
15 | +++ http13client/Makefile 2014-06-20 13:24:39 +0000 |
16 | @@ -20,7 +20,8 @@ |
17 | |
18 | prune: |
19 | rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \ |
20 | - sniff*.go httptest httputil testdata triv.go jar.go status.go |
21 | + sniff*.go httptest httputil testdata triv.go jar.go status.go \ |
22 | + cookie_test.go |
23 | sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go |
24 | |
25 | fix: |
26 | |
27 | === modified file 'http13client/_patches/empty_server.patch' |
28 | --- http13client/_patches/empty_server.patch 2014-03-19 23:13:58 +0000 |
29 | +++ http13client/_patches/empty_server.patch 2014-06-20 13:24:39 +0000 |
30 | @@ -1,6 +1,6 @@ |
31 | === modified file 'http13client/serve_test.go' |
32 | ---- http13client/serve_test.go 2014-03-19 21:38:56 +0000 |
33 | -+++ http13client/serve_test.go 2014-03-19 22:27:37 +0000 |
34 | +--- http13client/serve_test.go 2014-06-20 11:00:47 +0000 |
35 | ++++ http13client/serve_test.go 2014-06-20 12:00:22 +0000 |
36 | @@ -2,60 +2,15 @@ |
37 | // Use of this source code is governed by a BSD-style |
38 | // license that can be found in the LICENSE file. |
39 | @@ -62,7 +62,7 @@ |
40 | |
41 | func (a dummyAddr) Network() string { |
42 | return string(a) |
43 | -@@ -93,1289 +48,6 @@ |
44 | +@@ -93,1325 +48,6 @@ |
45 | return nil |
46 | } |
47 | |
48 | @@ -906,31 +906,50 @@ |
49 | -} |
50 | - |
51 | -type serverExpectTest struct { |
52 | -- contentLength int // of request body |
53 | +- contentLength int // of request body |
54 | +- chunked bool |
55 | - expectation string // e.g. "100-continue" |
56 | - readBody bool // whether handler should read the body (if false, sends StatusUnauthorized) |
57 | - expectedResponse string // expected substring in first line of http response |
58 | -} |
59 | - |
60 | +-func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest { |
61 | +- return serverExpectTest{ |
62 | +- contentLength: contentLength, |
63 | +- expectation: expectation, |
64 | +- readBody: readBody, |
65 | +- expectedResponse: expectedResponse, |
66 | +- } |
67 | +-} |
68 | +- |
69 | -var serverExpectTests = []serverExpectTest{ |
70 | - // Normal 100-continues, case-insensitive. |
71 | -- {100, "100-continue", true, "100 Continue"}, |
72 | -- {100, "100-cOntInUE", true, "100 Continue"}, |
73 | +- expectTest(100, "100-continue", true, "100 Continue"), |
74 | +- expectTest(100, "100-cOntInUE", true, "100 Continue"), |
75 | - |
76 | - // No 100-continue. |
77 | -- {100, "", true, "200 OK"}, |
78 | +- expectTest(100, "", true, "200 OK"), |
79 | - |
80 | - // 100-continue but requesting client to deny us, |
81 | - // so it never reads the body. |
82 | -- {100, "100-continue", false, "401 Unauthorized"}, |
83 | +- expectTest(100, "100-continue", false, "401 Unauthorized"), |
84 | - // Likewise without 100-continue: |
85 | -- {100, "", false, "401 Unauthorized"}, |
86 | +- expectTest(100, "", false, "401 Unauthorized"), |
87 | - |
88 | - // Non-standard expectations are failures |
89 | -- {0, "a-pony", false, "417 Expectation Failed"}, |
90 | +- expectTest(0, "a-pony", false, "417 Expectation Failed"), |
91 | - |
92 | -- // Expect-100 requested but no body |
93 | -- {0, "100-continue", true, "400 Bad Request"}, |
94 | +- // Expect-100 requested but no body (is apparently okay: Issue 7625) |
95 | +- expectTest(0, "100-continue", true, "200 OK"), |
96 | +- // Expect-100 requested but handler doesn't read the body |
97 | +- expectTest(0, "100-continue", false, "401 Unauthorized"), |
98 | +- // Expect-100 continue with no body, but a chunked body. |
99 | +- { |
100 | +- expectation: "100-continue", |
101 | +- readBody: true, |
102 | +- chunked: true, |
103 | +- expectedResponse: "100 Continue", |
104 | +- }, |
105 | -} |
106 | - |
107 | -// Tests that the server responds to the "Expect" request header |
108 | @@ -959,21 +978,38 @@ |
109 | - |
110 | - // Only send the body immediately if we're acting like an HTTP client |
111 | - // that doesn't send 100-continue expectations. |
112 | -- writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue" |
113 | +- writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue" |
114 | - |
115 | - go func() { |
116 | +- contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength) |
117 | +- if test.chunked { |
118 | +- contentLen = "Transfer-Encoding: chunked" |
119 | +- } |
120 | - _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+ |
121 | - "Connection: close\r\n"+ |
122 | -- "Content-Length: %d\r\n"+ |
123 | +- "%s\r\n"+ |
124 | - "Expect: %s\r\nHost: foo\r\n\r\n", |
125 | -- test.readBody, test.contentLength, test.expectation) |
126 | +- test.readBody, contentLen, test.expectation) |
127 | - if err != nil { |
128 | - t.Errorf("On test %#v, error writing request headers: %v", test, err) |
129 | - return |
130 | - } |
131 | - if writeBody { |
132 | +- var targ io.WriteCloser = struct { |
133 | +- io.Writer |
134 | +- io.Closer |
135 | +- }{ |
136 | +- conn, |
137 | +- ioutil.NopCloser(nil), |
138 | +- } |
139 | +- if test.chunked { |
140 | +- targ = httputil.NewChunkedWriter(conn) |
141 | +- } |
142 | - body := strings.Repeat("A", test.contentLength) |
143 | -- _, err = fmt.Fprint(conn, body) |
144 | +- _, err = fmt.Fprint(targ, body) |
145 | +- if err == nil { |
146 | +- err = targ.Close() |
147 | +- } |
148 | - if err != nil { |
149 | - if !test.readBody { |
150 | - // Server likely already hung up on us. |
151 | @@ -1352,7 +1388,7 @@ |
152 | type neverEnding byte |
153 | |
154 | func (b neverEnding) Read(p []byte) (n int, err error) { |
155 | -@@ -1384,1344 +56,3 @@ |
156 | +@@ -1420,1392 +56,3 @@ |
157 | } |
158 | return len(p), nil |
159 | } |
160 | @@ -2080,7 +2116,7 @@ |
161 | - got := ht.rawResponse(req) |
162 | - wantStatus := fmt.Sprintf("%d %s", code, StatusText(code)) |
163 | - if !strings.Contains(got, wantStatus) { |
164 | -- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got) |
165 | +- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got) |
166 | - } else if strings.Contains(got, "Content-Length") { |
167 | - t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got) |
168 | - } else if strings.Contains(got, "stuff") { |
169 | @@ -2090,6 +2126,21 @@ |
170 | - } |
171 | -} |
172 | - |
173 | +-func TestContentTypeOkayOn204(t *testing.T) { |
174 | +- ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { |
175 | +- w.Header().Set("Content-Length", "123") // suppressed |
176 | +- w.Header().Set("Content-Type", "foo/bar") |
177 | +- w.WriteHeader(204) |
178 | +- })) |
179 | +- got := ht.rawResponse("GET / HTTP/1.1") |
180 | +- if !strings.Contains(got, "Content-Type: foo/bar") { |
181 | +- t.Errorf("Response = %q; want Content-Type: foo/bar", got) |
182 | +- } |
183 | +- if strings.Contains(got, "Content-Length: 123") { |
184 | +- t.Errorf("Response = %q; don't want a Content-Length", got) |
185 | +- } |
186 | +-} |
187 | +- |
188 | -// Issue 6995 |
189 | -// A server Handler can receive a Request, and then turn around and |
190 | -// give a copy of that Request.Body out to the Transport (e.g. any |
191 | @@ -2261,7 +2312,7 @@ |
192 | - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) |
193 | - ts.Config.ConnState = func(c net.Conn, state ConnState) { |
194 | - if c == nil { |
195 | -- t.Error("nil conn seen in state %s", state) |
196 | +- t.Errorf("nil conn seen in state %s", state) |
197 | - return |
198 | - } |
199 | - mu.Lock() |
200 | @@ -2397,6 +2448,39 @@ |
201 | - } |
202 | -} |
203 | - |
204 | +-// golang.org/issue/7856 |
205 | +-func TestServerEmptyBodyRace(t *testing.T) { |
206 | +- defer afterTest(t) |
207 | +- var n int32 |
208 | +- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { |
209 | +- atomic.AddInt32(&n, 1) |
210 | +- })) |
211 | +- defer ts.Close() |
212 | +- var wg sync.WaitGroup |
213 | +- const reqs = 20 |
214 | +- for i := 0; i < reqs; i++ { |
215 | +- wg.Add(1) |
216 | +- go func() { |
217 | +- defer wg.Done() |
218 | +- res, err := Get(ts.URL) |
219 | +- if err != nil { |
220 | +- t.Error(err) |
221 | +- return |
222 | +- } |
223 | +- defer res.Body.Close() |
224 | +- _, err = io.Copy(ioutil.Discard, res.Body) |
225 | +- if err != nil { |
226 | +- t.Error(err) |
227 | +- return |
228 | +- } |
229 | +- }() |
230 | +- } |
231 | +- wg.Wait() |
232 | +- if got := atomic.LoadInt32(&n); got != reqs { |
233 | +- t.Errorf("handler ran %d times; want %d", got, reqs) |
234 | +- } |
235 | +-} |
236 | +- |
237 | -func TestServerConnStateNew(t *testing.T) { |
238 | - sawNew := false // if the test is buggy, we'll race on this variable. |
239 | - srv := &Server{ |
240 | @@ -2699,9 +2783,9 @@ |
241 | -} |
242 | |
243 | === modified file 'http13client/server.go' |
244 | ---- http13client/server.go 2014-03-19 20:20:19 +0000 |
245 | -+++ http13client/server.go 2014-03-19 22:27:37 +0000 |
246 | -@@ -2,1984 +2,17 @@ |
247 | +--- http13client/server.go 2014-06-20 11:00:47 +0000 |
248 | ++++ http13client/server.go 2014-06-20 12:05:53 +0000 |
249 | +@@ -2,1976 +2,16 @@ |
250 | // Use of this source code is governed by a BSD-style |
251 | // license that can be found in the LICENSE file. |
252 | |
253 | @@ -2723,7 +2807,7 @@ |
254 | - "path" |
255 | - "runtime" |
256 | - "strconv" |
257 | - "strings" |
258 | +- "strings" |
259 | "sync" |
260 | - "sync/atomic" |
261 | - "time" |
262 | @@ -3506,18 +3590,16 @@ |
263 | - } |
264 | - |
265 | - code := w.status |
266 | -- if !bodyAllowedForStatus(code) { |
267 | -- // Must not have body. |
268 | -- // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" |
269 | -- for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} { |
270 | -- delHeader(k) |
271 | -- } |
272 | -- } else { |
273 | +- if bodyAllowedForStatus(code) { |
274 | - // If no content type, apply sniffing algorithm to body. |
275 | - _, haveType := header["Content-Type"] |
276 | - if !haveType { |
277 | - setHeader.contentType = DetectContentType(p) |
278 | - } |
279 | +- } else { |
280 | +- for _, k := range suppressedHeaders(code) { |
281 | +- delHeader(k) |
282 | +- } |
283 | - } |
284 | - |
285 | - if _, ok := header["Date"]; !ok { |
286 | @@ -3865,16 +3947,10 @@ |
287 | - // Expect 100 Continue support |
288 | - req := w.req |
289 | - if req.expectsContinue() { |
290 | -- if req.ProtoAtLeast(1, 1) { |
291 | +- if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 { |
292 | - // Wrap the Body reader with one that replies on the connection |
293 | - req.Body = &expectContinueReader{readCloser: req.Body, resp: w} |
294 | - } |
295 | -- if req.ContentLength == 0 { |
296 | -- w.Header().Set("Connection", "close") |
297 | -- w.WriteHeader(StatusBadRequest) |
298 | -- w.finishRequest() |
299 | -- break |
300 | -- } |
301 | - req.Header.Del("Expect") |
302 | - } else if req.Header.get("Expect") != "" { |
303 | - w.sendExpectationFailed() |
304 | @@ -4685,11 +4761,11 @@ |
305 | -} |
306 | +) |
307 | |
308 | - // eofReader is a non-nil io.ReadCloser that always returns EOF. |
309 | - // It embeds a *strings.Reader so it still has a WriteTo method |
310 | -@@ -1992,28 +25,6 @@ |
311 | - ioutil.NopCloser(nil), |
312 | - } |
313 | + type eofReaderWithWriteTo struct{} |
314 | + |
315 | +@@ -1991,28 +31,6 @@ |
316 | + // Verify that an io.Copy from an eofReader won't require a buffer. |
317 | + var _ io.WriterTo = eofReader |
318 | |
319 | -// initNPNRequest is an HTTP handler that initializes certain |
320 | -// uninitialized fields in its *Request. Such partially-initialized |
321 | |
322 | === modified file 'http13client/_patches/fix_code.patch' |
323 | --- http13client/_patches/fix_code.patch 2014-03-19 23:13:58 +0000 |
324 | +++ http13client/_patches/fix_code.patch 2014-06-20 13:24:39 +0000 |
325 | @@ -1,6 +1,6 @@ |
326 | === modified file 'http13client/client.go' |
327 | ---- http13client/client.go 2014-03-19 20:20:19 +0000 |
328 | -+++ http13client/client.go 2014-03-19 22:27:37 +0000 |
329 | +--- http13client/client.go 2014-06-20 11:00:47 +0000 |
330 | ++++ http13client/client.go 2014-06-20 12:05:53 +0000 |
331 | @@ -17,6 +17,7 @@ |
332 | "io/ioutil" |
333 | "log" |
334 | @@ -18,7 +18,7 @@ |
335 | |
336 | // Timeout specifies a time limit for requests made by this |
337 | // Client. The timeout includes connection time, any |
338 | -@@ -177,7 +178,7 @@ |
339 | +@@ -184,7 +185,7 @@ |
340 | // Headers, leaving it uninitialized. We guarantee to the |
341 | // Transport that this has been initialized, though. |
342 | if req.Header == nil { |
343 | @@ -27,7 +27,7 @@ |
344 | } |
345 | |
346 | if u := req.URL.User; u != nil { |
347 | -@@ -308,7 +309,7 @@ |
348 | +@@ -316,7 +317,7 @@ |
349 | if ireq.Method == "POST" || ireq.Method == "PUT" { |
350 | nreq.Method = "GET" |
351 | } |
352 | @@ -38,8 +38,8 @@ |
353 | break |
354 | |
355 | === modified file 'http13client/cookie.go' |
356 | ---- http13client/cookie.go 2014-03-19 20:20:19 +0000 |
357 | -+++ http13client/cookie.go 2014-03-19 22:27:37 +0000 |
358 | +--- http13client/cookie.go 2014-06-20 11:00:47 +0000 |
359 | ++++ http13client/cookie.go 2014-06-20 12:05:53 +0000 |
360 | @@ -5,10 +5,9 @@ |
361 | package http |
362 | |
363 | @@ -94,7 +94,7 @@ |
364 | Name: name, |
365 | Value: value, |
366 | Raw: line, |
367 | -@@ -129,59 +108,12 @@ |
368 | +@@ -125,59 +104,12 @@ |
369 | return cookies |
370 | } |
371 | |
372 | @@ -156,7 +156,7 @@ |
373 | lines, ok := h["Cookie"] |
374 | if !ok { |
375 | return cookies |
376 | -@@ -213,7 +145,7 @@ |
377 | +@@ -209,7 +141,7 @@ |
378 | if !success { |
379 | continue |
380 | } |
381 | @@ -167,8 +167,8 @@ |
382 | } |
383 | |
384 | === modified file 'http13client/header.go' |
385 | ---- http13client/header.go 2014-03-19 20:20:19 +0000 |
386 | -+++ http13client/header.go 2014-03-19 22:27:37 +0000 |
387 | +--- http13client/header.go 2014-06-20 11:00:47 +0000 |
388 | ++++ http13client/header.go 2014-06-20 12:00:22 +0000 |
389 | @@ -5,176 +5,9 @@ |
390 | package http |
391 | |
392 | @@ -348,8 +348,8 @@ |
393 | // token must be all lowercase. |
394 | |
395 | === modified file 'http13client/request.go' |
396 | ---- http13client/request.go 2014-03-19 20:20:19 +0000 |
397 | -+++ http13client/request.go 2014-03-19 22:27:37 +0000 |
398 | +--- http13client/request.go 2014-06-20 11:00:47 +0000 |
399 | ++++ http13client/request.go 2014-06-20 12:05:53 +0000 |
400 | @@ -16,6 +16,7 @@ |
401 | "io/ioutil" |
402 | "mime" |
403 | @@ -358,25 +358,25 @@ |
404 | "net/textproto" |
405 | "net/url" |
406 | "strconv" |
407 | -@@ -103,7 +104,7 @@ |
408 | - // The request parser implements this by canonicalizing the |
409 | - // name, making the first character and any characters |
410 | - // following a hyphen uppercase and the rest lowercase. |
411 | +@@ -121,7 +122,7 @@ |
412 | + // added and may override values in Header. |
413 | + // |
414 | + // See the documentation for the Request.Write method. |
415 | - Header Header |
416 | + Header http.Header |
417 | |
418 | // Body is the request's body. |
419 | // |
420 | -@@ -164,7 +165,7 @@ |
421 | - // For server requests, Trailer is only populated after Body has been |
422 | - // closed or fully consumed. |
423 | - // Trailer support is only partially complete. |
424 | +@@ -199,7 +200,7 @@ |
425 | + // not mutate Trailer. |
426 | + // |
427 | + // Few HTTP clients, servers, or proxies support HTTP trailers. |
428 | - Trailer Header |
429 | + Trailer http.Header |
430 | |
431 | // RemoteAddr allows HTTP servers and other software to record |
432 | // the network address that sent the request, usually for |
433 | -@@ -204,7 +205,7 @@ |
434 | +@@ -239,7 +240,7 @@ |
435 | } |
436 | |
437 | // Cookies parses and returns the HTTP cookies sent with the request. |
438 | @@ -385,7 +385,7 @@ |
439 | return readCookies(r.Header, "") |
440 | } |
441 | |
442 | -@@ -212,7 +213,7 @@ |
443 | +@@ -247,7 +248,7 @@ |
444 | |
445 | // Cookie returns the named cookie provided in the request or |
446 | // ErrNoCookie if not found. |
447 | @@ -394,7 +394,7 @@ |
448 | for _, c := range readCookies(r.Header, name) { |
449 | return c, nil |
450 | } |
451 | -@@ -223,7 +224,7 @@ |
452 | +@@ -258,7 +259,7 @@ |
453 | // AddCookie does not attach more than one Cookie header field. That |
454 | // means all cookies, if any, are written into the same line, |
455 | // separated by semicolon. |
456 | @@ -403,7 +403,7 @@ |
457 | s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) |
458 | if c := r.Header.Get("Cookie"); c != "" { |
459 | r.Header.Set("Cookie", c+"; "+s) |
460 | -@@ -326,7 +327,7 @@ |
461 | +@@ -361,7 +362,7 @@ |
462 | } |
463 | |
464 | // extraHeaders may be nil |
465 | @@ -412,7 +412,7 @@ |
466 | host := req.Host |
467 | if host == "" { |
468 | if req.URL == nil { |
469 | -@@ -456,7 +457,7 @@ |
470 | +@@ -490,7 +491,7 @@ |
471 | Proto: "HTTP/1.1", |
472 | ProtoMajor: 1, |
473 | ProtoMinor: 1, |
474 | @@ -421,7 +421,7 @@ |
475 | Body: rc, |
476 | Host: u.Host, |
477 | } |
478 | -@@ -571,7 +572,7 @@ |
479 | +@@ -605,7 +606,7 @@ |
480 | if err != nil { |
481 | return nil, err |
482 | } |
483 | @@ -430,7 +430,7 @@ |
484 | |
485 | // RFC2616: Must treat |
486 | // GET /index.html HTTP/1.1 |
487 | -@@ -582,7 +583,7 @@ |
488 | +@@ -616,7 +617,7 @@ |
489 | // the same. In the second case, any Host line is ignored. |
490 | req.Host = req.URL.Host |
491 | if req.Host == "" { |
492 | @@ -439,7 +439,7 @@ |
493 | } |
494 | delete(req.Header, "Host") |
495 | |
496 | -@@ -630,12 +631,12 @@ |
497 | +@@ -638,12 +639,12 @@ |
498 | // |
499 | // MaxBytesReader prevents clients from accidentally or maliciously |
500 | // sending a large request and wasting server resources. |
501 | @@ -454,7 +454,7 @@ |
502 | r io.ReadCloser // underlying reader |
503 | n int64 // max bytes remaining |
504 | stopped bool |
505 | -@@ -645,9 +646,6 @@ |
506 | +@@ -653,9 +654,6 @@ |
507 | if l.n <= 0 { |
508 | if !l.stopped { |
509 | l.stopped = true |
510 | @@ -464,7 +464,7 @@ |
511 | } |
512 | return 0, errors.New("http: request body too large") |
513 | } |
514 | -@@ -852,16 +850,16 @@ |
515 | +@@ -858,18 +856,18 @@ |
516 | } |
517 | |
518 | func (r *Request) expectsContinue() bool { |
519 | @@ -484,11 +484,13 @@ |
520 | - return hasToken(r.Header.get("Connection"), "close") |
521 | + return hasToken(r.Header.Get("Connection"), "close") |
522 | } |
523 | + |
524 | + func (r *Request) closeBody() { |
525 | |
526 | === modified file 'http13client/response.go' |
527 | ---- http13client/response.go 2014-03-19 20:20:19 +0000 |
528 | -+++ http13client/response.go 2014-03-19 22:27:37 +0000 |
529 | -@@ -11,6 +11,7 @@ |
530 | +--- http13client/response.go 2014-06-20 11:00:47 +0000 |
531 | ++++ http13client/response.go 2014-06-20 12:05:53 +0000 |
532 | +@@ -12,6 +12,7 @@ |
533 | "crypto/tls" |
534 | "errors" |
535 | "io" |
536 | @@ -496,7 +498,7 @@ |
537 | "net/textproto" |
538 | "net/url" |
539 | "strconv" |
540 | -@@ -40,7 +41,7 @@ |
541 | +@@ -41,7 +42,7 @@ |
542 | // omitted from Header. |
543 | // |
544 | // Keys in the map are canonicalized (see CanonicalHeaderKey). |
545 | @@ -505,7 +507,7 @@ |
546 | |
547 | // Body represents the response body. |
548 | // |
549 | -@@ -69,7 +70,7 @@ |
550 | +@@ -71,7 +72,7 @@ |
551 | |
552 | // Trailer maps trailer keys to values, in the same |
553 | // format as the header. |
554 | @@ -514,7 +516,7 @@ |
555 | |
556 | // The Request that was sent to obtain this Response. |
557 | // Request's Body is nil (having already been consumed). |
558 | -@@ -84,7 +85,7 @@ |
559 | +@@ -86,7 +87,7 @@ |
560 | } |
561 | |
562 | // Cookies parses and returns the cookies set in the Set-Cookie headers. |
563 | @@ -523,7 +525,7 @@ |
564 | return readSetCookies(r.Header) |
565 | } |
566 | |
567 | -@@ -153,7 +154,7 @@ |
568 | +@@ -155,7 +156,7 @@ |
569 | } |
570 | return nil, err |
571 | } |
572 | @@ -532,7 +534,7 @@ |
573 | |
574 | fixPragmaCacheControl(resp.Header) |
575 | |
576 | -@@ -169,7 +170,7 @@ |
577 | +@@ -171,7 +172,7 @@ |
578 | // Pragma: no-cache |
579 | // like |
580 | // Cache-Control: no-cache |
581 | @@ -543,17 +545,17 @@ |
582 | header["Cache-Control"] = []string{"no-cache"} |
583 | |
584 | === modified file 'http13client/transfer.go' |
585 | ---- http13client/transfer.go 2014-03-19 20:20:19 +0000 |
586 | -+++ http13client/transfer.go 2014-03-19 22:27:37 +0000 |
587 | +--- http13client/transfer.go 2014-06-20 11:00:47 +0000 |
588 | ++++ http13client/transfer.go 2014-06-20 12:05:53 +0000 |
589 | @@ -11,6 +11,7 @@ |
590 | "fmt" |
591 | "io" |
592 | "io/ioutil" |
593 | + "net/http" |
594 | "net/textproto" |
595 | + "sort" |
596 | "strconv" |
597 | - "strings" |
598 | -@@ -36,7 +37,7 @@ |
599 | +@@ -37,7 +38,7 @@ |
600 | ContentLength int64 // -1 means unknown, 0 means exactly none |
601 | Close bool |
602 | TransferEncoding []string |
603 | @@ -562,16 +564,16 @@ |
604 | } |
605 | |
606 | func newTransferWriter(r interface{}) (t *transferWriter, err error) { |
607 | -@@ -174,7 +175,7 @@ |
608 | - io.WriteString(w, "Trailer: ") |
609 | - needComma := false |
610 | +@@ -171,7 +172,7 @@ |
611 | + if t.Trailer != nil { |
612 | + keys := make([]string, 0, len(t.Trailer)) |
613 | for k := range t.Trailer { |
614 | - k = CanonicalHeaderKey(k) |
615 | + k = http.CanonicalHeaderKey(k) |
616 | switch k { |
617 | case "Transfer-Encoding", "Trailer", "Content-Length": |
618 | return &badStringError{"invalid Trailer key", k} |
619 | -@@ -237,7 +238,7 @@ |
620 | +@@ -243,7 +244,7 @@ |
621 | |
622 | type transferReader struct { |
623 | // Input |
624 | @@ -580,7 +582,7 @@ |
625 | StatusCode int |
626 | RequestMethod string |
627 | ProtoMajor int |
628 | -@@ -247,7 +248,7 @@ |
629 | +@@ -253,7 +254,7 @@ |
630 | ContentLength int64 |
631 | TransferEncoding []string |
632 | Close bool |
633 | @@ -589,7 +591,7 @@ |
634 | } |
635 | |
636 | // bodyAllowedForStatus reports whether a given response status code |
637 | -@@ -308,7 +309,7 @@ |
638 | +@@ -330,7 +331,7 @@ |
639 | return err |
640 | } |
641 | if isResponse && t.RequestMethod == "HEAD" { |
642 | @@ -598,7 +600,7 @@ |
643 | return err |
644 | } else { |
645 | t.ContentLength = n |
646 | -@@ -386,7 +387,7 @@ |
647 | +@@ -408,7 +409,7 @@ |
648 | func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } |
649 | |
650 | // Sanitize transfer encoding |
651 | @@ -607,7 +609,7 @@ |
652 | raw, present := header["Transfer-Encoding"] |
653 | if !present { |
654 | return nil, nil |
655 | -@@ -429,7 +430,7 @@ |
656 | +@@ -451,7 +452,7 @@ |
657 | // Determine the expected body length, using RFC 2616 Section 4.4. This |
658 | // function is not a method, because ultimately it should be shared by |
659 | // ReadResponse and ReadRequest. |
660 | @@ -616,7 +618,7 @@ |
661 | |
662 | // Logic based on response type or status |
663 | if noBodyExpected(requestMethod) { |
664 | -@@ -449,7 +450,7 @@ |
665 | +@@ -471,7 +472,7 @@ |
666 | } |
667 | |
668 | // Logic based on Content-Length |
669 | @@ -625,7 +627,7 @@ |
670 | if cl != "" { |
671 | n, err := parseContentLength(cl) |
672 | if err != nil { |
673 | -@@ -475,18 +476,18 @@ |
674 | +@@ -497,18 +498,18 @@ |
675 | // Determine whether to hang up after sending a request and body, or |
676 | // receiving a response and body |
677 | // 'header' is the request headers |
678 | @@ -647,7 +649,7 @@ |
679 | header.Del("Connection") |
680 | return true |
681 | } |
682 | -@@ -495,17 +496,17 @@ |
683 | +@@ -517,17 +518,17 @@ |
684 | } |
685 | |
686 | // Parse the trailer header |
687 | @@ -669,22 +671,28 @@ |
688 | switch key { |
689 | case "Transfer-Encoding", "Trailer", "Content-Length": |
690 | return nil, &badStringError{"bad trailer key", key} |
691 | -@@ -642,9 +643,9 @@ |
692 | +@@ -664,14 +665,14 @@ |
693 | } |
694 | switch rr := b.hdr.(type) { |
695 | case *Request: |
696 | -- rr.Trailer = Header(hdr) |
697 | -+ rr.Trailer = http.Header(hdr) |
698 | +- mergeSetHeader(&rr.Trailer, Header(hdr)) |
699 | ++ mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
700 | case *Response: |
701 | -- rr.Trailer = Header(hdr) |
702 | -+ rr.Trailer = http.Header(hdr) |
703 | +- mergeSetHeader(&rr.Trailer, Header(hdr)) |
704 | ++ mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
705 | } |
706 | return nil |
707 | } |
708 | + |
709 | +-func mergeSetHeader(dst *Header, src Header) { |
710 | ++func mergeSetHeader(dst *http.Header, src http.Header) { |
711 | + if *dst == nil { |
712 | + *dst = src |
713 | + return |
714 | |
715 | === modified file 'http13client/transport.go' |
716 | ---- http13client/transport.go 2014-03-19 20:20:19 +0000 |
717 | -+++ http13client/transport.go 2014-03-19 22:27:37 +0000 |
718 | +--- http13client/transport.go 2014-06-20 11:00:47 +0000 |
719 | ++++ http13client/transport.go 2014-06-20 12:05:53 +0000 |
720 | @@ -18,6 +18,7 @@ |
721 | "io" |
722 | "log" |
723 | @@ -693,7 +701,7 @@ |
724 | "net/url" |
725 | "os" |
726 | "strings" |
727 | -@@ -144,12 +145,12 @@ |
728 | +@@ -147,12 +148,12 @@ |
729 | // optional extra headers to write. |
730 | type transportRequest struct { |
731 | *Request // original request, not to be mutated |
732 | @@ -709,7 +717,7 @@ |
733 | } |
734 | return tr.extra |
735 | } |
736 | -@@ -512,7 +513,7 @@ |
737 | +@@ -519,7 +520,7 @@ |
738 | case cm.targetScheme == "http": |
739 | pconn.isProxy = true |
740 | if pa != "" { |
741 | @@ -718,7 +726,7 @@ |
742 | h.Set("Proxy-Authorization", pa) |
743 | } |
744 | } |
745 | -@@ -521,7 +522,7 @@ |
746 | +@@ -528,7 +529,7 @@ |
747 | Method: "CONNECT", |
748 | URL: &url.URL{Opaque: cm.targetAddr}, |
749 | Host: cm.targetAddr, |
750 | @@ -727,7 +735,7 @@ |
751 | } |
752 | if pa != "" { |
753 | connectReq.Header.Set("Proxy-Authorization", pa) |
754 | -@@ -735,7 +736,7 @@ |
755 | +@@ -748,7 +749,7 @@ |
756 | // mutateHeaderFunc is an optional func to modify extra |
757 | // headers on each outbound request before it's written. (the |
758 | // original Request given to RoundTrip is not modified) |
759 | @@ -735,5 +743,5 @@ |
760 | + mutateHeaderFunc func(http.Header) |
761 | } |
762 | |
763 | - func (pc *persistConn) isBroken() bool { |
764 | + // isBroken reports whether this connection is in a known broken state. |
765 | |
766 | |
767 | === modified file 'http13client/_patches/fix_status.patch' |
768 | --- http13client/_patches/fix_status.patch 2014-03-19 23:43:25 +0000 |
769 | +++ http13client/_patches/fix_status.patch 2014-06-20 13:24:39 +0000 |
770 | @@ -1,7 +1,7 @@ |
771 | === modified file 'http13client/client.go' |
772 | ---- http13client/client.go 2014-03-19 23:13:58 +0000 |
773 | -+++ http13client/client.go 2014-03-19 23:38:11 +0000 |
774 | -@@ -210,7 +210,7 @@ |
775 | +--- http13client/client.go 2014-06-20 12:46:25 +0000 |
776 | ++++ http13client/client.go 2014-06-20 12:46:45 +0000 |
777 | +@@ -217,7 +217,7 @@ |
778 | // automatically redirect. |
779 | func shouldRedirectGet(statusCode int) bool { |
780 | switch statusCode { |
781 | @@ -10,7 +10,7 @@ |
782 | return true |
783 | } |
784 | return false |
785 | -@@ -220,7 +220,7 @@ |
786 | +@@ -227,7 +227,7 @@ |
787 | // automatically redirect. |
788 | func shouldRedirectPost(statusCode int) bool { |
789 | switch statusCode { |
790 | @@ -21,9 +21,9 @@ |
791 | return false |
792 | |
793 | === modified file 'http13client/client_test.go' |
794 | ---- http13client/client_test.go 2014-03-19 23:13:58 +0000 |
795 | -+++ http13client/client_test.go 2014-03-19 23:39:48 +0000 |
796 | -@@ -202,7 +202,7 @@ |
797 | +--- http13client/client_test.go 2014-06-20 12:46:25 +0000 |
798 | ++++ http13client/client_test.go 2014-06-20 12:46:45 +0000 |
799 | +@@ -204,7 +204,7 @@ |
800 | } |
801 | } |
802 | if n < 15 { |
803 | @@ -32,7 +32,7 @@ |
804 | return |
805 | } |
806 | fmt.Fprintf(w, "n=%d", n) |
807 | -@@ -324,7 +324,7 @@ |
808 | +@@ -326,7 +326,7 @@ |
809 | } |
810 | if r.URL.Path == "/" { |
811 | http.SetCookie(w, expectedCookies[1]) |
812 | @@ -41,7 +41,7 @@ |
813 | } else { |
814 | http.SetCookie(w, expectedCookies[2]) |
815 | w.Write([]byte("hello")) |
816 | -@@ -783,7 +783,7 @@ |
817 | +@@ -785,7 +785,7 @@ |
818 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
819 | if r.URL.Path == "/" { |
820 | sawRoot <- true |
821 | @@ -50,7 +50,7 @@ |
822 | return |
823 | } |
824 | if r.URL.Path == "/slow" { |
825 | -@@ -844,7 +844,7 @@ |
826 | +@@ -846,7 +846,7 @@ |
827 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
828 | saw <- r.RemoteAddr |
829 | if r.URL.Path == "/" { |
830 | @@ -61,9 +61,9 @@ |
831 | defer ts.Close() |
832 | |
833 | === modified file 'http13client/request_test.go' |
834 | ---- http13client/request_test.go 2014-03-19 23:13:58 +0000 |
835 | -+++ http13client/request_test.go 2014-03-19 23:40:12 +0000 |
836 | -@@ -164,11 +164,11 @@ |
837 | +--- http13client/request_test.go 2014-06-20 12:46:25 +0000 |
838 | ++++ http13client/request_test.go 2014-06-20 12:46:45 +0000 |
839 | +@@ -182,11 +182,11 @@ |
840 | switch r.URL.Path { |
841 | case "/": |
842 | w.Header().Set("Location", "/foo/") |
843 | @@ -79,9 +79,9 @@ |
844 | defer ts.Close() |
845 | |
846 | === modified file 'http13client/response.go' |
847 | ---- http13client/response.go 2014-03-19 23:13:58 +0000 |
848 | -+++ http13client/response.go 2014-03-19 23:38:56 +0000 |
849 | -@@ -204,9 +204,8 @@ |
850 | +--- http13client/response.go 2014-06-20 12:46:25 +0000 |
851 | ++++ http13client/response.go 2014-06-20 12:46:45 +0000 |
852 | +@@ -205,9 +205,8 @@ |
853 | // Status line |
854 | text := r.Status |
855 | if text == "" { |
856 | @@ -94,10 +94,23 @@ |
857 | } |
858 | } |
859 | |
860 | +=== modified file 'http13client/responsewrite_test.go' |
861 | +--- http13client/responsewrite_test.go 2014-06-20 12:46:25 +0000 |
862 | ++++ http13client/responsewrite_test.go 2014-06-20 12:47:05 +0000 |
863 | +@@ -197,7 +197,7 @@ |
864 | + // there were two. |
865 | + { |
866 | + Response{ |
867 | +- StatusCode: StatusOK, |
868 | ++ StatusCode: http.StatusOK, |
869 | + ProtoMajor: 1, |
870 | + ProtoMinor: 1, |
871 | + Request: &Request{Method: "POST"}, |
872 | + |
873 | === modified file 'http13client/transport_test.go' |
874 | ---- http13client/transport_test.go 2014-03-19 23:13:58 +0000 |
875 | -+++ http13client/transport_test.go 2014-03-19 23:40:39 +0000 |
876 | -@@ -968,7 +968,7 @@ |
877 | +--- http13client/transport_test.go 2014-06-20 12:46:25 +0000 |
878 | ++++ http13client/transport_test.go 2014-06-20 12:46:45 +0000 |
879 | +@@ -1004,7 +1004,7 @@ |
880 | defer afterTest(t) |
881 | const deniedMsg = "sorry, denied." |
882 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
883 | @@ -106,7 +119,7 @@ |
884 | })) |
885 | defer ts.Close() |
886 | tr := &Transport{} |
887 | -@@ -992,7 +992,7 @@ |
888 | +@@ -1028,7 +1028,7 @@ |
889 | func TestChunkedNoContent(t *testing.T) { |
890 | defer afterTest(t) |
891 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
892 | |
893 | === modified file 'http13client/_patches/fix_tests.patch' |
894 | --- http13client/_patches/fix_tests.patch 2014-03-19 23:13:58 +0000 |
895 | +++ http13client/_patches/fix_tests.patch 2014-06-20 13:24:39 +0000 |
896 | @@ -1,6 +1,6 @@ |
897 | === modified file 'http13client/client_test.go' |
898 | ---- http13client/client_test.go 2014-03-19 21:38:56 +0000 |
899 | -+++ http13client/client_test.go 2014-03-19 22:27:37 +0000 |
900 | +--- http13client/client_test.go 2014-06-20 11:00:47 +0000 |
901 | ++++ http13client/client_test.go 2014-06-20 12:05:53 +0000 |
902 | @@ -15,8 +15,8 @@ |
903 | "fmt" |
904 | "io" |
905 | @@ -11,7 +11,7 @@ |
906 | . "launchpad.net/ubuntu-push/http13client" |
907 | "net/http/httptest" |
908 | "net/url" |
909 | -@@ -27,7 +27,7 @@ |
910 | +@@ -29,7 +29,7 @@ |
911 | "time" |
912 | ) |
913 | |
914 | @@ -20,7 +20,7 @@ |
915 | w.Header().Set("Last-Modified", "sometime") |
916 | fmt.Fprintf(w, "User-agent: go\nDisallow: /something/") |
917 | }) |
918 | -@@ -193,7 +193,7 @@ |
919 | +@@ -195,7 +195,7 @@ |
920 | func TestClientRedirects(t *testing.T) { |
921 | defer afterTest(t) |
922 | var ts *httptest.Server |
923 | @@ -29,7 +29,7 @@ |
924 | n, _ := strconv.Atoi(r.FormValue("n")) |
925 | // Test Referer header. (7 is arbitrary position to test at) |
926 | if n == 7 { |
927 | -@@ -202,7 +202,7 @@ |
928 | +@@ -204,7 +204,7 @@ |
929 | } |
930 | } |
931 | if n < 15 { |
932 | @@ -38,7 +38,7 @@ |
933 | return |
934 | } |
935 | fmt.Fprintf(w, "n=%d", n) |
936 | -@@ -271,7 +271,7 @@ |
937 | +@@ -273,7 +273,7 @@ |
938 | bytes.Buffer |
939 | } |
940 | var ts *httptest.Server |
941 | @@ -47,7 +47,7 @@ |
942 | log.Lock() |
943 | fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI) |
944 | log.Unlock() |
945 | -@@ -312,21 +312,21 @@ |
946 | +@@ -314,21 +314,21 @@ |
947 | } |
948 | } |
949 | |
950 | @@ -75,7 +75,7 @@ |
951 | w.Write([]byte("hello")) |
952 | } |
953 | }) |
954 | -@@ -334,7 +334,7 @@ |
955 | +@@ -336,7 +336,7 @@ |
956 | func TestClientSendsCookieFromJar(t *testing.T) { |
957 | tr := &recordingTransport{} |
958 | client := &Client{Transport: tr} |
959 | @@ -84,7 +84,7 @@ |
960 | us := "http://dummy.faketld/" |
961 | u, _ := url.Parse(us) |
962 | client.Jar.SetCookies(u, expectedCookies) |
963 | -@@ -364,19 +364,19 @@ |
964 | +@@ -366,19 +366,19 @@ |
965 | // scope of all cookies. |
966 | type TestJar struct { |
967 | m sync.Mutex |
968 | @@ -108,7 +108,7 @@ |
969 | j.m.Lock() |
970 | defer j.m.Unlock() |
971 | return j.perURL[u.Host] |
972 | -@@ -391,7 +391,7 @@ |
973 | +@@ -393,7 +393,7 @@ |
974 | Jar: new(TestJar), |
975 | } |
976 | u, _ := url.Parse(ts.URL) |
977 | @@ -117,7 +117,7 @@ |
978 | resp, err := c.Get(ts.URL) |
979 | if err != nil { |
980 | t.Fatalf("Get: %v", err) |
981 | -@@ -400,7 +400,7 @@ |
982 | +@@ -402,7 +402,7 @@ |
983 | matchReturnedCookies(t, expectedCookies, resp.Cookies()) |
984 | } |
985 | |
986 | @@ -126,7 +126,7 @@ |
987 | if len(given) != len(expected) { |
988 | t.Logf("Received cookies: %v", given) |
989 | t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) |
990 | -@@ -421,14 +421,14 @@ |
991 | +@@ -423,14 +423,14 @@ |
992 | |
993 | func TestJarCalls(t *testing.T) { |
994 | defer afterTest(t) |
995 | @@ -144,7 +144,7 @@ |
996 | } |
997 | })) |
998 | defer ts.Close() |
999 | -@@ -468,11 +468,11 @@ |
1000 | +@@ -470,11 +470,11 @@ |
1001 | log bytes.Buffer |
1002 | } |
1003 | |
1004 | @@ -158,7 +158,7 @@ |
1005 | j.logf("Cookies(%q)\n", u) |
1006 | return nil |
1007 | } |
1008 | -@@ -486,11 +486,11 @@ |
1009 | +@@ -488,11 +488,11 @@ |
1010 | func TestStreamingGet(t *testing.T) { |
1011 | defer afterTest(t) |
1012 | say := make(chan string) |
1013 | @@ -173,7 +173,7 @@ |
1014 | } |
1015 | })) |
1016 | defer ts.Close() |
1017 | -@@ -536,7 +536,7 @@ |
1018 | +@@ -538,7 +538,7 @@ |
1019 | // don't send a TCP packet per line of the http request + body. |
1020 | func TestClientWrites(t *testing.T) { |
1021 | defer afterTest(t) |
1022 | @@ -182,7 +182,7 @@ |
1023 | })) |
1024 | defer ts.Close() |
1025 | |
1026 | -@@ -568,46 +568,6 @@ |
1027 | +@@ -570,46 +570,6 @@ |
1028 | } |
1029 | } |
1030 | |
1031 | @@ -229,7 +229,7 @@ |
1032 | func TestClientErrorWithRequestURI(t *testing.T) { |
1033 | defer afterTest(t) |
1034 | req, _ := NewRequest("GET", "http://localhost:1234/", nil) |
1035 | -@@ -639,7 +599,7 @@ |
1036 | +@@ -641,7 +601,7 @@ |
1037 | |
1038 | func TestClientWithCorrectTLSServerName(t *testing.T) { |
1039 | defer afterTest(t) |
1040 | @@ -238,7 +238,7 @@ |
1041 | if r.TLS.ServerName != "127.0.0.1" { |
1042 | t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName) |
1043 | } |
1044 | -@@ -652,33 +612,6 @@ |
1045 | +@@ -654,33 +614,6 @@ |
1046 | } |
1047 | } |
1048 | |
1049 | @@ -272,7 +272,7 @@ |
1050 | // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName |
1051 | // when not empty. |
1052 | // |
1053 | -@@ -690,7 +623,7 @@ |
1054 | +@@ -692,7 +625,7 @@ |
1055 | // The httptest.Server has a cert with "example.com" as its name. |
1056 | func TestTransportUsesTLSConfigServerName(t *testing.T) { |
1057 | defer afterTest(t) |
1058 | @@ -281,7 +281,7 @@ |
1059 | w.Write([]byte("Hello")) |
1060 | })) |
1061 | defer ts.Close() |
1062 | -@@ -711,7 +644,7 @@ |
1063 | +@@ -713,7 +646,7 @@ |
1064 | |
1065 | func TestResponseSetsTLSConnectionState(t *testing.T) { |
1066 | defer afterTest(t) |
1067 | @@ -290,7 +290,7 @@ |
1068 | w.Write([]byte("Hello")) |
1069 | })) |
1070 | defer ts.Close() |
1071 | -@@ -739,7 +672,7 @@ |
1072 | +@@ -741,7 +674,7 @@ |
1073 | // Verify Response.ContentLength is populated. http://golang.org/issue/4126 |
1074 | func TestClientHeadContentLength(t *testing.T) { |
1075 | defer afterTest(t) |
1076 | @@ -299,7 +299,7 @@ |
1077 | if v := r.FormValue("cl"); v != "" { |
1078 | w.Header().Set("Content-Length", v) |
1079 | } |
1080 | -@@ -775,7 +708,7 @@ |
1081 | +@@ -777,7 +710,7 @@ |
1082 | func TestEmptyPasswordAuth(t *testing.T) { |
1083 | defer afterTest(t) |
1084 | gopher := "gopher" |
1085 | @@ -308,7 +308,7 @@ |
1086 | auth := r.Header.Get("Authorization") |
1087 | if strings.HasPrefix(auth, "Basic ") { |
1088 | encoded := auth[6:] |
1089 | -@@ -847,15 +780,15 @@ |
1090 | +@@ -849,15 +782,15 @@ |
1091 | defer afterTest(t) |
1092 | sawRoot := make(chan bool, 1) |
1093 | sawSlow := make(chan bool, 1) |
1094 | @@ -327,7 +327,7 @@ |
1095 | sawSlow <- true |
1096 | time.Sleep(2 * time.Second) |
1097 | return |
1098 | -@@ -908,10 +841,10 @@ |
1099 | +@@ -910,10 +843,10 @@ |
1100 | func TestClientRedirectEatsBody(t *testing.T) { |
1101 | defer afterTest(t) |
1102 | saw := make(chan string, 2) |
1103 | @@ -340,233 +340,50 @@ |
1104 | } |
1105 | })) |
1106 | defer ts.Close() |
1107 | - |
1108 | -=== modified file 'http13client/cookie_test.go' |
1109 | ---- http13client/cookie_test.go 2014-03-19 20:20:19 +0000 |
1110 | -+++ http13client/cookie_test.go 2014-03-19 22:27:37 +0000 |
1111 | -@@ -9,6 +9,7 @@ |
1112 | - "encoding/json" |
1113 | - "fmt" |
1114 | - "log" |
1115 | -+ "net/http" |
1116 | - "os" |
1117 | - "reflect" |
1118 | - "strings" |
1119 | -@@ -17,39 +18,39 @@ |
1120 | - ) |
1121 | - |
1122 | - var writeSetCookiesTests = []struct { |
1123 | -- Cookie *Cookie |
1124 | -+ Cookie *http.Cookie |
1125 | - Raw string |
1126 | - }{ |
1127 | - { |
1128 | -- &Cookie{Name: "cookie-1", Value: "v$1"}, |
1129 | -+ &http.Cookie{Name: "cookie-1", Value: "v$1"}, |
1130 | - "cookie-1=v$1", |
1131 | - }, |
1132 | - { |
1133 | -- &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
1134 | -+ &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
1135 | - "cookie-2=two; Max-Age=3600", |
1136 | - }, |
1137 | - { |
1138 | -- &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
1139 | -+ &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
1140 | - "cookie-3=three; Domain=example.com", |
1141 | - }, |
1142 | - { |
1143 | -- &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
1144 | -+ &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
1145 | - "cookie-4=four; Path=/restricted/", |
1146 | - }, |
1147 | - { |
1148 | -- &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
1149 | -+ &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
1150 | - "cookie-5=five", |
1151 | - }, |
1152 | - { |
1153 | -- &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
1154 | -+ &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
1155 | - "cookie-6=six", |
1156 | - }, |
1157 | - { |
1158 | -- &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
1159 | -+ &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
1160 | - "cookie-7=seven; Domain=127.0.0.1", |
1161 | - }, |
1162 | - { |
1163 | -- &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
1164 | -+ &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
1165 | - "cookie-8=eight", |
1166 | - }, |
1167 | - } |
1168 | -@@ -71,10 +72,10 @@ |
1169 | - } |
1170 | - } |
1171 | - |
1172 | --type headerOnlyResponseWriter Header |
1173 | -+type headerOnlyResponseWriter http.Header |
1174 | - |
1175 | --func (ho headerOnlyResponseWriter) Header() Header { |
1176 | -- return Header(ho) |
1177 | -+func (ho headerOnlyResponseWriter) Header() http.Header { |
1178 | -+ return http.Header(ho) |
1179 | - } |
1180 | - |
1181 | - func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { |
1182 | -@@ -86,9 +87,9 @@ |
1183 | - } |
1184 | - |
1185 | - func TestSetCookie(t *testing.T) { |
1186 | -- m := make(Header) |
1187 | -- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
1188 | -- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
1189 | -+ m := make(http.Header) |
1190 | -+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
1191 | -+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
1192 | - if l := len(m["Set-Cookie"]); l != 2 { |
1193 | - t.Fatalf("expected %d cookies, got %d", 2, l) |
1194 | - } |
1195 | -@@ -101,19 +102,19 @@ |
1196 | - } |
1197 | - |
1198 | - var addCookieTests = []struct { |
1199 | -- Cookies []*Cookie |
1200 | -+ Cookies []*http.Cookie |
1201 | - Raw string |
1202 | - }{ |
1203 | - { |
1204 | -- []*Cookie{}, |
1205 | -+ []*http.Cookie{}, |
1206 | - "", |
1207 | - }, |
1208 | - { |
1209 | -- []*Cookie{{Name: "cookie-1", Value: "v$1"}}, |
1210 | -+ []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, |
1211 | - "cookie-1=v$1", |
1212 | - }, |
1213 | - { |
1214 | -- []*Cookie{ |
1215 | -+ []*http.Cookie{ |
1216 | - {Name: "cookie-1", Value: "v$1"}, |
1217 | - {Name: "cookie-2", Value: "v$2"}, |
1218 | - {Name: "cookie-3", Value: "v$3"}, |
1219 | -@@ -136,16 +137,16 @@ |
1220 | - } |
1221 | - |
1222 | - var readSetCookiesTests = []struct { |
1223 | -- Header Header |
1224 | -- Cookies []*Cookie |
1225 | -+ Header http.Header |
1226 | -+ Cookies []*http.Cookie |
1227 | - }{ |
1228 | - { |
1229 | -- Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
1230 | -- []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
1231 | -+ http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
1232 | -+ []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
1233 | - }, |
1234 | - { |
1235 | -- Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
1236 | -- []*Cookie{{ |
1237 | -+ http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
1238 | -+ []*http.Cookie{{ |
1239 | - Name: "NID", |
1240 | - Value: "99=YsDT5i3E-CXax-", |
1241 | - Path: "/", |
1242 | -@@ -157,8 +158,8 @@ |
1243 | - }}, |
1244 | - }, |
1245 | - { |
1246 | -- Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
1247 | -- []*Cookie{{ |
1248 | -+ http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
1249 | -+ []*http.Cookie{{ |
1250 | - Name: ".ASPXAUTH", |
1251 | - Value: "7E3AA", |
1252 | - Path: "/", |
1253 | -@@ -169,8 +170,8 @@ |
1254 | - }}, |
1255 | - }, |
1256 | - { |
1257 | -- Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
1258 | -- []*Cookie{{ |
1259 | -+ http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
1260 | -+ []*http.Cookie{{ |
1261 | - Name: "ASP.NET_SessionId", |
1262 | - Value: "foo", |
1263 | - Path: "/", |
1264 | -@@ -207,37 +208,37 @@ |
1265 | - } |
1266 | - |
1267 | - var readCookiesTests = []struct { |
1268 | -- Header Header |
1269 | -+ Header http.Header |
1270 | - Filter string |
1271 | -- Cookies []*Cookie |
1272 | -+ Cookies []*http.Cookie |
1273 | - }{ |
1274 | - { |
1275 | -- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
1276 | -- "", |
1277 | -- []*Cookie{ |
1278 | -- {Name: "Cookie-1", Value: "v$1"}, |
1279 | -- {Name: "c2", Value: "v2"}, |
1280 | -- }, |
1281 | -- }, |
1282 | -- { |
1283 | -- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
1284 | -- "c2", |
1285 | -- []*Cookie{ |
1286 | -- {Name: "c2", Value: "v2"}, |
1287 | -- }, |
1288 | -- }, |
1289 | -- { |
1290 | -- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
1291 | -- "", |
1292 | -- []*Cookie{ |
1293 | -- {Name: "Cookie-1", Value: "v$1"}, |
1294 | -- {Name: "c2", Value: "v2"}, |
1295 | -- }, |
1296 | -- }, |
1297 | -- { |
1298 | -- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
1299 | -- "c2", |
1300 | -- []*Cookie{ |
1301 | -+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
1302 | -+ "", |
1303 | -+ []*http.Cookie{ |
1304 | -+ {Name: "Cookie-1", Value: "v$1"}, |
1305 | -+ {Name: "c2", Value: "v2"}, |
1306 | -+ }, |
1307 | -+ }, |
1308 | -+ { |
1309 | -+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
1310 | -+ "c2", |
1311 | -+ []*http.Cookie{ |
1312 | -+ {Name: "c2", Value: "v2"}, |
1313 | -+ }, |
1314 | -+ }, |
1315 | -+ { |
1316 | -+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
1317 | -+ "", |
1318 | -+ []*http.Cookie{ |
1319 | -+ {Name: "Cookie-1", Value: "v$1"}, |
1320 | -+ {Name: "c2", Value: "v2"}, |
1321 | -+ }, |
1322 | -+ }, |
1323 | -+ { |
1324 | -+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
1325 | -+ "c2", |
1326 | -+ []*http.Cookie{ |
1327 | - {Name: "c2", Value: "v2"}, |
1328 | - }, |
1329 | - }, |
1330 | +@@ -957,7 +890,7 @@ |
1331 | + |
1332 | + func TestClientTrailers(t *testing.T) { |
1333 | + defer afterTest(t) |
1334 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
1335 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
1336 | + w.Header().Set("Connection", "close") |
1337 | + w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") |
1338 | + w.Header().Add("Trailer", "Server-Trailer-C") |
1339 | +@@ -992,9 +925,9 @@ |
1340 | + // trailers to be sent, if and only if they were |
1341 | + // previously declared with w.Header().Set("Trailer", |
1342 | + // ..keys..) |
1343 | +- w.(Flusher).Flush() |
1344 | +- conn, buf, _ := w.(Hijacker).Hijack() |
1345 | +- t := Header{} |
1346 | ++ w.(http.Flusher).Flush() |
1347 | ++ conn, buf, _ := w.(http.Hijacker).Hijack() |
1348 | ++ t := http.Header{} |
1349 | + t.Set("Server-Trailer-A", "valuea") |
1350 | + t.Set("Server-Trailer-C", "valuec") // skipping B |
1351 | + buf.WriteString("0\r\n") // eof |
1352 | +@@ -1015,7 +948,7 @@ |
1353 | + req.Trailer["Client-Trailer-B"] = []string{"valueb"} |
1354 | + }), |
1355 | + )) |
1356 | +- req.Trailer = Header{ |
1357 | ++ req.Trailer = http.Header{ |
1358 | + "Client-Trailer-A": nil, // to be set later |
1359 | + "Client-Trailer-B": nil, // to be set later |
1360 | + } |
1361 | +@@ -1027,7 +960,7 @@ |
1362 | + if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { |
1363 | + t.Error(err) |
1364 | + } |
1365 | +- want := Header{ |
1366 | ++ want := http.Header{ |
1367 | + "Server-Trailer-A": []string{"valuea"}, |
1368 | + "Server-Trailer-B": nil, |
1369 | + "Server-Trailer-C": []string{"valuec"}, |
1370 | |
1371 | === modified file 'http13client/export_test.go' |
1372 | ---- http13client/export_test.go 2014-03-19 20:20:19 +0000 |
1373 | -+++ http13client/export_test.go 2014-03-19 22:27:37 +0000 |
1374 | +--- http13client/export_test.go 2014-06-20 11:00:47 +0000 |
1375 | ++++ http13client/export_test.go 2014-06-20 12:00:22 +0000 |
1376 | @@ -9,15 +9,12 @@ |
1377 | |
1378 | import ( |
1379 | @@ -599,8 +416,8 @@ |
1380 | noProxyEnv.reset() |
1381 | |
1382 | === modified file 'http13client/header_test.go' |
1383 | ---- http13client/header_test.go 2014-03-19 20:20:19 +0000 |
1384 | -+++ http13client/header_test.go 2014-03-19 22:27:37 +0000 |
1385 | +--- http13client/header_test.go 2014-06-20 11:00:47 +0000 |
1386 | ++++ http13client/header_test.go 2014-06-20 12:00:22 +0000 |
1387 | @@ -6,19 +6,20 @@ |
1388 | |
1389 | import ( |
1390 | @@ -731,8 +548,8 @@ |
1391 | } |
1392 | |
1393 | === modified file 'http13client/npn_test.go' |
1394 | ---- http13client/npn_test.go 2014-03-19 21:38:56 +0000 |
1395 | -+++ http13client/npn_test.go 2014-03-19 22:27:37 +0000 |
1396 | +--- http13client/npn_test.go 2014-06-20 11:00:47 +0000 |
1397 | ++++ http13client/npn_test.go 2014-06-20 12:05:53 +0000 |
1398 | @@ -11,13 +11,14 @@ |
1399 | "io" |
1400 | "io/ioutil" |
1401 | @@ -792,8 +609,8 @@ |
1402 | func (w http09Writer) WriteHeader(int) {} // no headers |
1403 | |
1404 | === modified file 'http13client/readrequest_test.go' |
1405 | ---- http13client/readrequest_test.go 2014-03-19 20:20:19 +0000 |
1406 | -+++ http13client/readrequest_test.go 2014-03-19 22:27:37 +0000 |
1407 | +--- http13client/readrequest_test.go 2014-06-20 11:00:47 +0000 |
1408 | ++++ http13client/readrequest_test.go 2014-06-20 12:00:22 +0000 |
1409 | @@ -9,6 +9,7 @@ |
1410 | "bytes" |
1411 | "fmt" |
1412 | @@ -909,8 +726,8 @@ |
1413 | Close: false, |
1414 | |
1415 | === modified file 'http13client/request_test.go' |
1416 | ---- http13client/request_test.go 2014-03-19 21:38:56 +0000 |
1417 | -+++ http13client/request_test.go 2014-03-19 22:27:37 +0000 |
1418 | +--- http13client/request_test.go 2014-06-20 11:00:47 +0000 |
1419 | ++++ http13client/request_test.go 2014-06-20 12:05:53 +0000 |
1420 | @@ -12,6 +12,7 @@ |
1421 | "io/ioutil" |
1422 | "mime/multipart" |
1423 | @@ -945,8 +762,26 @@ |
1424 | + req.Header = http.Header{"Content-Type": {"text/plain"}} |
1425 | multipart, err = req.MultipartReader() |
1426 | if multipart != nil { |
1427 | - t.Errorf("unexpected multipart for text/plain") |
1428 | -@@ -159,7 +160,7 @@ |
1429 | + t.Error("unexpected multipart for text/plain") |
1430 | +@@ -161,7 +162,7 @@ |
1431 | + func TestParseMultipartForm(t *testing.T) { |
1432 | + req := &Request{ |
1433 | + Method: "POST", |
1434 | +- Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
1435 | ++ Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
1436 | + Body: ioutil.NopCloser(new(bytes.Buffer)), |
1437 | + } |
1438 | + err := req.ParseMultipartForm(25) |
1439 | +@@ -169,7 +170,7 @@ |
1440 | + t.Error("expected multipart EOF, got nil") |
1441 | + } |
1442 | + |
1443 | +- req.Header = Header{"Content-Type": {"text/plain"}} |
1444 | ++ req.Header = http.Header{"Content-Type": {"text/plain"}} |
1445 | + err = req.ParseMultipartForm(25) |
1446 | + if err != ErrNotMultipart { |
1447 | + t.Error("expected ErrNotMultipart for text/plain") |
1448 | +@@ -177,7 +178,7 @@ |
1449 | } |
1450 | |
1451 | func TestRedirect(t *testing.T) { |
1452 | @@ -957,8 +792,8 @@ |
1453 | w.Header().Set("Location", "/foo/") |
1454 | |
1455 | === modified file 'http13client/requestwrite_test.go' |
1456 | ---- http13client/requestwrite_test.go 2014-03-19 20:20:19 +0000 |
1457 | -+++ http13client/requestwrite_test.go 2014-03-19 22:27:37 +0000 |
1458 | +--- http13client/requestwrite_test.go 2014-06-20 11:00:47 +0000 |
1459 | ++++ http13client/requestwrite_test.go 2014-06-20 12:00:22 +0000 |
1460 | @@ -10,6 +10,7 @@ |
1461 | "fmt" |
1462 | "io" |
1463 | @@ -1068,8 +903,8 @@ |
1464 | var braw bytes.Buffer |
1465 | |
1466 | === modified file 'http13client/response_test.go' |
1467 | ---- http13client/response_test.go 2014-03-19 20:20:19 +0000 |
1468 | -+++ http13client/response_test.go 2014-03-19 22:27:37 +0000 |
1469 | +--- http13client/response_test.go 2014-06-20 11:00:47 +0000 |
1470 | ++++ http13client/response_test.go 2014-06-20 12:05:53 +0000 |
1471 | @@ -12,6 +12,7 @@ |
1472 | "fmt" |
1473 | "io" |
1474 | @@ -1078,7 +913,7 @@ |
1475 | "net/url" |
1476 | "reflect" |
1477 | "regexp" |
1478 | -@@ -44,7 +45,7 @@ |
1479 | +@@ -48,7 +49,7 @@ |
1480 | ProtoMajor: 1, |
1481 | ProtoMinor: 0, |
1482 | Request: dummyReq("GET"), |
1483 | @@ -1087,7 +922,7 @@ |
1484 | "Connection": {"close"}, // TODO(rsc): Delete? |
1485 | }, |
1486 | Close: true, |
1487 | -@@ -67,7 +68,7 @@ |
1488 | +@@ -71,7 +72,7 @@ |
1489 | Proto: "HTTP/1.1", |
1490 | ProtoMajor: 1, |
1491 | ProtoMinor: 1, |
1492 | @@ -1096,7 +931,7 @@ |
1493 | Request: dummyReq("GET"), |
1494 | Close: true, |
1495 | ContentLength: -1, |
1496 | -@@ -88,7 +89,7 @@ |
1497 | +@@ -92,7 +93,7 @@ |
1498 | Proto: "HTTP/1.1", |
1499 | ProtoMajor: 1, |
1500 | ProtoMinor: 1, |
1501 | @@ -1105,7 +940,7 @@ |
1502 | Request: dummyReq("GET"), |
1503 | Close: false, |
1504 | ContentLength: 0, |
1505 | -@@ -112,7 +113,7 @@ |
1506 | +@@ -116,7 +117,7 @@ |
1507 | ProtoMajor: 1, |
1508 | ProtoMinor: 0, |
1509 | Request: dummyReq("GET"), |
1510 | @@ -1114,61 +949,61 @@ |
1511 | "Connection": {"close"}, |
1512 | "Content-Length": {"10"}, |
1513 | }, |
1514 | -@@ -142,7 +143,7 @@ |
1515 | - ProtoMajor: 1, |
1516 | - ProtoMinor: 1, |
1517 | - Request: dummyReq("GET"), |
1518 | -- Header: Header{}, |
1519 | -+ Header: http.Header{}, |
1520 | - Close: false, |
1521 | - ContentLength: -1, |
1522 | - TransferEncoding: []string{"chunked"}, |
1523 | -@@ -169,7 +170,7 @@ |
1524 | - ProtoMajor: 1, |
1525 | - ProtoMinor: 1, |
1526 | - Request: dummyReq("GET"), |
1527 | -- Header: Header{}, |
1528 | -+ Header: http.Header{}, |
1529 | - Close: false, |
1530 | - ContentLength: -1, |
1531 | - TransferEncoding: []string{"chunked"}, |
1532 | -@@ -191,7 +192,7 @@ |
1533 | - ProtoMajor: 1, |
1534 | - ProtoMinor: 1, |
1535 | - Request: dummyReq("HEAD"), |
1536 | -- Header: Header{}, |
1537 | -+ Header: http.Header{}, |
1538 | - TransferEncoding: []string{"chunked"}, |
1539 | - Close: false, |
1540 | - ContentLength: -1, |
1541 | -@@ -213,7 +214,7 @@ |
1542 | - ProtoMajor: 1, |
1543 | - ProtoMinor: 0, |
1544 | - Request: dummyReq("HEAD"), |
1545 | -- Header: Header{"Content-Length": {"256"}}, |
1546 | -+ Header: http.Header{"Content-Length": {"256"}}, |
1547 | - TransferEncoding: nil, |
1548 | - Close: true, |
1549 | - ContentLength: 256, |
1550 | -@@ -235,7 +236,7 @@ |
1551 | - ProtoMajor: 1, |
1552 | - ProtoMinor: 1, |
1553 | - Request: dummyReq("HEAD"), |
1554 | -- Header: Header{"Content-Length": {"256"}}, |
1555 | -+ Header: http.Header{"Content-Length": {"256"}}, |
1556 | - TransferEncoding: nil, |
1557 | - Close: false, |
1558 | - ContentLength: 256, |
1559 | -@@ -256,7 +257,7 @@ |
1560 | - ProtoMajor: 1, |
1561 | - ProtoMinor: 0, |
1562 | - Request: dummyReq("HEAD"), |
1563 | -- Header: Header{}, |
1564 | -+ Header: http.Header{}, |
1565 | - TransferEncoding: nil, |
1566 | - Close: true, |
1567 | - ContentLength: -1, |
1568 | -@@ -278,7 +279,7 @@ |
1569 | +@@ -146,7 +147,7 @@ |
1570 | + ProtoMajor: 1, |
1571 | + ProtoMinor: 1, |
1572 | + Request: dummyReq("GET"), |
1573 | +- Header: Header{}, |
1574 | ++ Header: http.Header{}, |
1575 | + Close: false, |
1576 | + ContentLength: -1, |
1577 | + TransferEncoding: []string{"chunked"}, |
1578 | +@@ -173,7 +174,7 @@ |
1579 | + ProtoMajor: 1, |
1580 | + ProtoMinor: 1, |
1581 | + Request: dummyReq("GET"), |
1582 | +- Header: Header{}, |
1583 | ++ Header: http.Header{}, |
1584 | + Close: false, |
1585 | + ContentLength: -1, |
1586 | + TransferEncoding: []string{"chunked"}, |
1587 | +@@ -195,7 +196,7 @@ |
1588 | + ProtoMajor: 1, |
1589 | + ProtoMinor: 1, |
1590 | + Request: dummyReq("HEAD"), |
1591 | +- Header: Header{}, |
1592 | ++ Header: http.Header{}, |
1593 | + TransferEncoding: []string{"chunked"}, |
1594 | + Close: false, |
1595 | + ContentLength: -1, |
1596 | +@@ -217,7 +218,7 @@ |
1597 | + ProtoMajor: 1, |
1598 | + ProtoMinor: 0, |
1599 | + Request: dummyReq("HEAD"), |
1600 | +- Header: Header{"Content-Length": {"256"}}, |
1601 | ++ Header: http.Header{"Content-Length": {"256"}}, |
1602 | + TransferEncoding: nil, |
1603 | + Close: true, |
1604 | + ContentLength: 256, |
1605 | +@@ -239,7 +240,7 @@ |
1606 | + ProtoMajor: 1, |
1607 | + ProtoMinor: 1, |
1608 | + Request: dummyReq("HEAD"), |
1609 | +- Header: Header{"Content-Length": {"256"}}, |
1610 | ++ Header: http.Header{"Content-Length": {"256"}}, |
1611 | + TransferEncoding: nil, |
1612 | + Close: false, |
1613 | + ContentLength: 256, |
1614 | +@@ -260,7 +261,7 @@ |
1615 | + ProtoMajor: 1, |
1616 | + ProtoMinor: 0, |
1617 | + Request: dummyReq("HEAD"), |
1618 | +- Header: Header{}, |
1619 | ++ Header: http.Header{}, |
1620 | + TransferEncoding: nil, |
1621 | + Close: true, |
1622 | + ContentLength: -1, |
1623 | +@@ -282,7 +283,7 @@ |
1624 | ProtoMajor: 1, |
1625 | ProtoMinor: 1, |
1626 | Request: dummyReq("GET"), |
1627 | @@ -1177,25 +1012,25 @@ |
1628 | "Content-Length": {"0"}, |
1629 | }, |
1630 | Close: false, |
1631 | -@@ -299,7 +300,7 @@ |
1632 | - ProtoMajor: 1, |
1633 | - ProtoMinor: 0, |
1634 | - Request: dummyReq("GET"), |
1635 | -- Header: Header{}, |
1636 | -+ Header: http.Header{}, |
1637 | - Close: true, |
1638 | - ContentLength: -1, |
1639 | - }, |
1640 | -@@ -318,7 +319,7 @@ |
1641 | - ProtoMajor: 1, |
1642 | - ProtoMinor: 0, |
1643 | - Request: dummyReq("GET"), |
1644 | -- Header: Header{}, |
1645 | -+ Header: http.Header{}, |
1646 | - Close: true, |
1647 | - ContentLength: -1, |
1648 | - }, |
1649 | -@@ -340,7 +341,7 @@ |
1650 | +@@ -303,7 +304,7 @@ |
1651 | + ProtoMajor: 1, |
1652 | + ProtoMinor: 0, |
1653 | + Request: dummyReq("GET"), |
1654 | +- Header: Header{}, |
1655 | ++ Header: http.Header{}, |
1656 | + Close: true, |
1657 | + ContentLength: -1, |
1658 | + }, |
1659 | +@@ -322,7 +323,7 @@ |
1660 | + ProtoMajor: 1, |
1661 | + ProtoMinor: 0, |
1662 | + Request: dummyReq("GET"), |
1663 | +- Header: Header{}, |
1664 | ++ Header: http.Header{}, |
1665 | + Close: true, |
1666 | + ContentLength: -1, |
1667 | + }, |
1668 | +@@ -344,7 +345,7 @@ |
1669 | ProtoMajor: 1, |
1670 | ProtoMinor: 1, |
1671 | Request: dummyReq("GET"), |
1672 | @@ -1204,7 +1039,7 @@ |
1673 | "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, |
1674 | }, |
1675 | Close: true, |
1676 | -@@ -363,7 +364,7 @@ |
1677 | +@@ -367,7 +368,7 @@ |
1678 | Proto: "HTTP/1.0", |
1679 | ProtoMajor: 1, |
1680 | ProtoMinor: 0, |
1681 | @@ -1213,7 +1048,7 @@ |
1682 | "Connection": {"close"}, // TODO(rsc): Delete? |
1683 | }, |
1684 | Close: true, |
1685 | -@@ -545,7 +546,7 @@ |
1686 | +@@ -549,7 +550,7 @@ |
1687 | func TestLocationResponse(t *testing.T) { |
1688 | for i, tt := range responseLocationTests { |
1689 | res := new(Response) |
1690 | @@ -1222,7 +1057,7 @@ |
1691 | res.Header.Set("Location", tt.location) |
1692 | if tt.requrl != "" { |
1693 | res.Request = &Request{} |
1694 | -@@ -626,16 +627,3 @@ |
1695 | +@@ -630,16 +631,3 @@ |
1696 | t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) |
1697 | } |
1698 | } |
1699 | @@ -1241,8 +1076,8 @@ |
1700 | -} |
1701 | |
1702 | === modified file 'http13client/responsewrite_test.go' |
1703 | ---- http13client/responsewrite_test.go 2014-03-19 20:20:19 +0000 |
1704 | -+++ http13client/responsewrite_test.go 2014-03-19 22:27:37 +0000 |
1705 | +--- http13client/responsewrite_test.go 2014-06-20 11:00:47 +0000 |
1706 | ++++ http13client/responsewrite_test.go 2014-06-20 12:05:53 +0000 |
1707 | @@ -7,6 +7,7 @@ |
1708 | import ( |
1709 | "bytes" |
1710 | @@ -1257,7 +1092,7 @@ |
1711 | Request: dummyReq("GET"), |
1712 | - Header: Header{}, |
1713 | + Header: http.Header{}, |
1714 | - Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), |
1715 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
1716 | ContentLength: 6, |
1717 | }, |
1718 | @@ -41,7 +42,7 @@ |
1719 | @@ -1270,6 +1105,60 @@ |
1720 | ContentLength: -1, |
1721 | }, |
1722 | @@ -56,7 +57,7 @@ |
1723 | + ProtoMajor: 1, |
1724 | + ProtoMinor: 1, |
1725 | + Request: dummyReq("GET"), |
1726 | +- Header: Header{}, |
1727 | ++ Header: http.Header{}, |
1728 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
1729 | + ContentLength: -1, |
1730 | + Close: true, |
1731 | +@@ -73,7 +74,7 @@ |
1732 | + ProtoMajor: 1, |
1733 | + ProtoMinor: 1, |
1734 | + Request: dummyReq11("GET"), |
1735 | +- Header: Header{}, |
1736 | ++ Header: http.Header{}, |
1737 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
1738 | + ContentLength: -1, |
1739 | + Close: false, |
1740 | +@@ -91,7 +92,7 @@ |
1741 | + ProtoMajor: 1, |
1742 | + ProtoMinor: 1, |
1743 | + Request: dummyReq11("GET"), |
1744 | +- Header: Header{}, |
1745 | ++ Header: http.Header{}, |
1746 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
1747 | + ContentLength: -1, |
1748 | + TransferEncoding: []string{"chunked"}, |
1749 | +@@ -108,7 +109,7 @@ |
1750 | + ProtoMajor: 1, |
1751 | + ProtoMinor: 1, |
1752 | + Request: dummyReq11("GET"), |
1753 | +- Header: Header{}, |
1754 | ++ Header: http.Header{}, |
1755 | + Body: nil, |
1756 | + ContentLength: 0, |
1757 | + Close: false, |
1758 | +@@ -124,7 +125,7 @@ |
1759 | + ProtoMajor: 1, |
1760 | + ProtoMinor: 1, |
1761 | + Request: dummyReq11("GET"), |
1762 | +- Header: Header{}, |
1763 | ++ Header: http.Header{}, |
1764 | + Body: ioutil.NopCloser(strings.NewReader("")), |
1765 | + ContentLength: 0, |
1766 | + Close: false, |
1767 | +@@ -140,7 +141,7 @@ |
1768 | + ProtoMajor: 1, |
1769 | + ProtoMinor: 1, |
1770 | + Request: dummyReq11("GET"), |
1771 | +- Header: Header{}, |
1772 | ++ Header: http.Header{}, |
1773 | + Body: ioutil.NopCloser(strings.NewReader("foo")), |
1774 | + ContentLength: 0, |
1775 | + Close: false, |
1776 | +@@ -156,7 +157,7 @@ |
1777 | ProtoMajor: 1, |
1778 | ProtoMinor: 1, |
1779 | Request: dummyReq("GET"), |
1780 | @@ -1278,7 +1167,7 @@ |
1781 | Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
1782 | ContentLength: 6, |
1783 | TransferEncoding: []string{"chunked"}, |
1784 | -@@ -77,7 +78,7 @@ |
1785 | +@@ -177,7 +178,7 @@ |
1786 | ProtoMajor: 1, |
1787 | ProtoMinor: 1, |
1788 | Request: dummyReq("GET"), |
1789 | @@ -1287,11 +1176,20 @@ |
1790 | "Foo": []string{" Bar\nBaz "}, |
1791 | }, |
1792 | Body: nil, |
1793 | +@@ -200,7 +201,7 @@ |
1794 | + ProtoMajor: 1, |
1795 | + ProtoMinor: 1, |
1796 | + Request: &Request{Method: "POST"}, |
1797 | +- Header: Header{}, |
1798 | ++ Header: http.Header{}, |
1799 | + ContentLength: 0, |
1800 | + TransferEncoding: nil, |
1801 | + Body: nil, |
1802 | |
1803 | === modified file 'http13client/transport_test.go' |
1804 | ---- http13client/transport_test.go 2014-03-19 21:38:56 +0000 |
1805 | -+++ http13client/transport_test.go 2014-03-19 22:27:37 +0000 |
1806 | -@@ -17,8 +17,8 @@ |
1807 | +--- http13client/transport_test.go 2014-06-20 11:00:47 +0000 |
1808 | ++++ http13client/transport_test.go 2014-06-20 12:05:53 +0000 |
1809 | +@@ -18,8 +18,8 @@ |
1810 | "io/ioutil" |
1811 | "log" |
1812 | "net" |
1813 | @@ -1301,7 +1199,7 @@ |
1814 | "net/http/httptest" |
1815 | "net/url" |
1816 | "os" |
1817 | -@@ -34,7 +34,7 @@ |
1818 | +@@ -35,7 +35,7 @@ |
1819 | // and then verify that the final 2 responses get errors back. |
1820 | |
1821 | // hostPortHandler writes back the client's "host:port". |
1822 | @@ -1310,7 +1208,7 @@ |
1823 | if r.FormValue("close") == "true" { |
1824 | w.Header().Set("Connection", "close") |
1825 | } |
1826 | -@@ -280,7 +280,7 @@ |
1827 | +@@ -289,7 +289,7 @@ |
1828 | const msg = "foobar" |
1829 | |
1830 | var addrSeen map[string]int |
1831 | @@ -1319,7 +1217,7 @@ |
1832 | addrSeen[r.RemoteAddr]++ |
1833 | if r.URL.Path == "/chunked/" { |
1834 | w.WriteHeader(200) |
1835 | -@@ -299,7 +299,7 @@ |
1836 | +@@ -308,7 +308,7 @@ |
1837 | wantLen := []int{len(msg), -1}[pi] |
1838 | addrSeen = make(map[string]int) |
1839 | for i := 0; i < 3; i++ { |
1840 | @@ -1328,7 +1226,7 @@ |
1841 | if err != nil { |
1842 | t.Errorf("Get %s: %v", path, err) |
1843 | continue |
1844 | -@@ -329,7 +329,7 @@ |
1845 | +@@ -338,7 +338,7 @@ |
1846 | defer afterTest(t) |
1847 | resch := make(chan string) |
1848 | gotReq := make(chan bool) |
1849 | @@ -1337,7 +1235,7 @@ |
1850 | gotReq <- true |
1851 | msg := <-resch |
1852 | _, err := w.Write([]byte(msg)) |
1853 | -@@ -457,12 +457,12 @@ |
1854 | +@@ -466,12 +466,12 @@ |
1855 | if testing.Short() { |
1856 | t.Skip("skipping test in short mode") |
1857 | } |
1858 | @@ -1353,7 +1251,7 @@ |
1859 | buf.Flush() |
1860 | conn.Close() |
1861 | })) |
1862 | -@@ -510,7 +510,7 @@ |
1863 | +@@ -519,7 +519,7 @@ |
1864 | // with no bodies properly |
1865 | func TestTransportHeadResponses(t *testing.T) { |
1866 | defer afterTest(t) |
1867 | @@ -1362,7 +1260,7 @@ |
1868 | if r.Method != "HEAD" { |
1869 | panic("expected HEAD; got " + r.Method) |
1870 | } |
1871 | -@@ -545,7 +545,7 @@ |
1872 | +@@ -554,7 +554,7 @@ |
1873 | // on responses to HEAD requests. |
1874 | func TestTransportHeadChunkedResponse(t *testing.T) { |
1875 | defer afterTest(t) |
1876 | @@ -1371,7 +1269,7 @@ |
1877 | if r.Method != "HEAD" { |
1878 | panic("expected HEAD; got " + r.Method) |
1879 | } |
1880 | -@@ -588,7 +588,7 @@ |
1881 | +@@ -597,7 +597,7 @@ |
1882 | func TestRoundTripGzip(t *testing.T) { |
1883 | defer afterTest(t) |
1884 | const responseBody = "test response body" |
1885 | @@ -1380,7 +1278,7 @@ |
1886 | accept := req.Header.Get("Accept-Encoding") |
1887 | if expect := req.FormValue("expect_accept"); accept != expect { |
1888 | t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q", |
1889 | -@@ -647,7 +647,7 @@ |
1890 | +@@ -656,7 +656,7 @@ |
1891 | defer afterTest(t) |
1892 | const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
1893 | const nRandBytes = 1024 * 1024 |
1894 | @@ -1389,7 +1287,7 @@ |
1895 | if req.Method == "HEAD" { |
1896 | if g := req.Header.Get("Accept-Encoding"); g != "" { |
1897 | t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g) |
1898 | -@@ -742,11 +742,11 @@ |
1899 | +@@ -751,11 +751,11 @@ |
1900 | func TestTransportProxy(t *testing.T) { |
1901 | defer afterTest(t) |
1902 | ch := make(chan string, 1) |
1903 | @@ -1403,7 +1301,7 @@ |
1904 | ch <- "proxy for " + r.URL.String() |
1905 | })) |
1906 | defer proxy.Close() |
1907 | -@@ -770,7 +770,7 @@ |
1908 | +@@ -779,7 +779,7 @@ |
1909 | // Content-Encoding is removed. |
1910 | func TestTransportGzipRecursive(t *testing.T) { |
1911 | defer afterTest(t) |
1912 | @@ -1412,7 +1310,16 @@ |
1913 | w.Header().Set("Content-Encoding", "gzip") |
1914 | w.Write(rgz) |
1915 | })) |
1916 | -@@ -802,7 +802,7 @@ |
1917 | +@@ -807,7 +807,7 @@ |
1918 | + // a short gzip body |
1919 | + func TestTransportGzipShort(t *testing.T) { |
1920 | + defer afterTest(t) |
1921 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
1922 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
1923 | + w.Header().Set("Content-Encoding", "gzip") |
1924 | + w.Write([]byte{0x1f, 0x8b}) |
1925 | + })) |
1926 | +@@ -838,7 +838,7 @@ |
1927 | defer afterTest(t) |
1928 | gotReqCh := make(chan bool) |
1929 | unblockCh := make(chan bool) |
1930 | @@ -1421,7 +1328,7 @@ |
1931 | gotReqCh <- true |
1932 | <-unblockCh |
1933 | w.Header().Set("Content-Length", "0") |
1934 | -@@ -869,7 +869,7 @@ |
1935 | +@@ -905,7 +905,7 @@ |
1936 | t.Skip("skipping test; see http://golang.org/issue/7237") |
1937 | } |
1938 | defer afterTest(t) |
1939 | @@ -1430,7 +1337,7 @@ |
1940 | })) |
1941 | defer ts.Close() |
1942 | |
1943 | -@@ -912,7 +912,7 @@ |
1944 | +@@ -948,7 +948,7 @@ |
1945 | c := &Client{Transport: tr} |
1946 | |
1947 | unblockCh := make(chan bool, 1) |
1948 | @@ -1439,7 +1346,7 @@ |
1949 | <-unblockCh |
1950 | tr.CloseIdleConnections() |
1951 | })) |
1952 | -@@ -939,7 +939,7 @@ |
1953 | +@@ -975,7 +975,7 @@ |
1954 | func TestIssue3644(t *testing.T) { |
1955 | defer afterTest(t) |
1956 | const numFoos = 5000 |
1957 | @@ -1448,7 +1355,7 @@ |
1958 | w.Header().Set("Connection", "close") |
1959 | for i := 0; i < numFoos; i++ { |
1960 | w.Write([]byte("foo ")) |
1961 | -@@ -967,8 +967,8 @@ |
1962 | +@@ -1003,8 +1003,8 @@ |
1963 | func TestIssue3595(t *testing.T) { |
1964 | defer afterTest(t) |
1965 | const deniedMsg = "sorry, denied." |
1966 | @@ -1459,7 +1366,7 @@ |
1967 | })) |
1968 | defer ts.Close() |
1969 | tr := &Transport{} |
1970 | -@@ -991,7 +991,7 @@ |
1971 | +@@ -1027,7 +1027,7 @@ |
1972 | // "client fails to handle requests with no body and chunked encoding" |
1973 | func TestChunkedNoContent(t *testing.T) { |
1974 | defer afterTest(t) |
1975 | @@ -1468,7 +1375,7 @@ |
1976 | w.WriteHeader(StatusNoContent) |
1977 | })) |
1978 | defer ts.Close() |
1979 | -@@ -1019,7 +1019,7 @@ |
1980 | +@@ -1055,7 +1055,7 @@ |
1981 | maxProcs, numReqs = 4, 50 |
1982 | } |
1983 | defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) |
1984 | @@ -1477,7 +1384,7 @@ |
1985 | fmt.Fprintf(w, "%v", r.FormValue("echo")) |
1986 | })) |
1987 | defer ts.Close() |
1988 | -@@ -1080,8 +1080,8 @@ |
1989 | +@@ -1116,8 +1116,8 @@ |
1990 | } |
1991 | defer afterTest(t) |
1992 | const debug = false |
1993 | @@ -1488,7 +1395,7 @@ |
1994 | io.Copy(w, neverEnding('a')) |
1995 | }) |
1996 | ts := httptest.NewServer(mux) |
1997 | -@@ -1144,11 +1144,11 @@ |
1998 | +@@ -1180,11 +1180,11 @@ |
1999 | } |
2000 | defer afterTest(t) |
2001 | const debug = false |
2002 | @@ -1503,20 +1410,22 @@ |
2003 | defer r.Body.Close() |
2004 | io.Copy(ioutil.Discard, r.Body) |
2005 | }) |
2006 | -@@ -1214,9 +1214,9 @@ |
2007 | - if testing.Short() { |
2008 | +@@ -1251,11 +1251,11 @@ |
2009 | t.Skip("skipping timeout test in -short mode") |
2010 | } |
2011 | + inHandler := make(chan bool, 1) |
2012 | - mux := NewServeMux() |
2013 | -- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {}) |
2014 | +- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) { |
2015 | ++ mux := http.NewServeMux() |
2016 | ++ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) { |
2017 | + inHandler <- true |
2018 | + }) |
2019 | - mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) { |
2020 | -+ mux := http.NewServeMux() |
2021 | -+ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {}) |
2022 | + mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { |
2023 | + inHandler <- true |
2024 | time.Sleep(2 * time.Second) |
2025 | }) |
2026 | - ts := httptest.NewServer(mux) |
2027 | -@@ -1276,9 +1276,9 @@ |
2028 | +@@ -1322,9 +1322,9 @@ |
2029 | t.Skip("skipping test in -short mode") |
2030 | } |
2031 | unblockc := make(chan bool) |
2032 | @@ -1528,7 +1437,7 @@ |
2033 | <-unblockc |
2034 | })) |
2035 | defer ts.Close() |
2036 | -@@ -1386,14 +1386,14 @@ |
2037 | +@@ -1431,14 +1431,14 @@ |
2038 | defer afterTest(t) |
2039 | writeErr := make(chan error, 1) |
2040 | msg := []byte("young\n") |
2041 | @@ -1545,7 +1454,7 @@ |
2042 | } |
2043 | })) |
2044 | defer ts.Close() |
2045 | -@@ -1449,7 +1449,7 @@ |
2046 | +@@ -1494,7 +1494,7 @@ |
2047 | res := &Response{ |
2048 | Status: "200 OK", |
2049 | StatusCode: 200, |
2050 | @@ -1554,7 +1463,7 @@ |
2051 | Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())), |
2052 | } |
2053 | return res, nil |
2054 | -@@ -1478,7 +1478,7 @@ |
2055 | +@@ -1523,7 +1523,7 @@ |
2056 | defer afterTest(t) |
2057 | tr := &Transport{} |
2058 | _, err := tr.RoundTrip(&Request{ |
2059 | @@ -1563,7 +1472,7 @@ |
2060 | URL: &url.URL{ |
2061 | Scheme: "http", |
2062 | }, |
2063 | -@@ -1492,14 +1492,14 @@ |
2064 | +@@ -1537,14 +1537,14 @@ |
2065 | func TestTransportSocketLateBinding(t *testing.T) { |
2066 | defer afterTest(t) |
2067 | |
2068 | @@ -1582,7 +1491,7 @@ |
2069 | w.Header().Set("bar-ipport", r.RemoteAddr) |
2070 | }) |
2071 | ts := httptest.NewServer(mux) |
2072 | -@@ -1720,7 +1720,7 @@ |
2073 | +@@ -1767,7 +1767,7 @@ |
2074 | var mu sync.Mutex |
2075 | var n int |
2076 | |
2077 | @@ -1591,7 +1500,7 @@ |
2078 | mu.Lock() |
2079 | n++ |
2080 | mu.Unlock() |
2081 | -@@ -1756,7 +1756,7 @@ |
2082 | +@@ -1803,7 +1803,7 @@ |
2083 | // then closes it. |
2084 | func TestTransportClosesRequestBody(t *testing.T) { |
2085 | defer afterTest(t) |
2086 | @@ -1600,4 +1509,49 @@ |
2087 | io.Copy(ioutil.Discard, r.Body) |
2088 | })) |
2089 | defer ts.Close() |
2090 | +@@ -1890,9 +1890,9 @@ |
2091 | + t.Skip("skipping flaky test on Windows; golang.org/issue/7634") |
2092 | + } |
2093 | + closedc := make(chan bool, 1) |
2094 | +- ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
2095 | ++ ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2096 | + if strings.Contains(r.URL.Path, "/keep-alive-then-die") { |
2097 | +- conn, _, _ := w.(Hijacker).Hijack() |
2098 | ++ conn, _, _ := w.(http.Hijacker).Hijack() |
2099 | + conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo")) |
2100 | + conn.Close() |
2101 | + closedc <- true |
2102 | +@@ -1994,12 +1994,12 @@ |
2103 | + } |
2104 | + defer closeConn() |
2105 | + |
2106 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
2107 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2108 | + if r.Method == "GET" { |
2109 | + io.WriteString(w, "bar") |
2110 | + return |
2111 | + } |
2112 | +- conn, _, _ := w.(Hijacker).Hijack() |
2113 | ++ conn, _, _ := w.(http.Hijacker).Hijack() |
2114 | + sconn.Lock() |
2115 | + sconn.c = conn |
2116 | + sconn.Unlock() |
2117 | +@@ -2056,7 +2056,7 @@ |
2118 | + } |
2119 | + defer afterTest(t) |
2120 | + readBody := make(chan error, 1) |
2121 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
2122 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2123 | + _, err := ioutil.ReadAll(r.Body) |
2124 | + readBody <- err |
2125 | + })) |
2126 | +@@ -2098,7 +2098,7 @@ |
2127 | + } |
2128 | + } |
2129 | + |
2130 | +-func wantBody(res *http.Response, err error, want string) error { |
2131 | ++func wantBody(res *Response, err error, want string) error { |
2132 | + if err != nil { |
2133 | + return err |
2134 | + } |
2135 | |
2136 | |
2137 | === modified file 'http13client/_using.txt' |
2138 | --- http13client/_using.txt 2014-03-20 12:20:01 +0000 |
2139 | +++ http13client/_using.txt 2014-06-20 13:24:39 +0000 |
2140 | @@ -1,5 +1,5 @@ |
2141 | -parent: 19512:32c32aef2a41 tip |
2142 | - test: enable bug385_32 test on amd64p32. |
2143 | -branch: default |
2144 | +parent: 20169:9895f9e36435 go1.3 release |
2145 | + go1.3 |
2146 | +branch: release-branch.go1.3 |
2147 | commit: (clean) |
2148 | update: (current) |
2149 | |
2150 | === modified file 'http13client/client.go' |
2151 | --- http13client/client.go 2014-03-20 09:26:28 +0000 |
2152 | +++ http13client/client.go 2014-06-20 13:24:39 +0000 |
2153 | @@ -92,8 +92,9 @@ |
2154 | // authentication, or cookies. |
2155 | // |
2156 | // RoundTrip should not modify the request, except for |
2157 | - // consuming and closing the Body. The request's URL and |
2158 | - // Header fields are guaranteed to be initialized. |
2159 | + // consuming and closing the Body, including on errors. The |
2160 | + // request's URL and Header fields are guaranteed to be |
2161 | + // initialized. |
2162 | RoundTrip(*Request) (*Response, error) |
2163 | } |
2164 | |
2165 | @@ -141,6 +142,9 @@ |
2166 | // (typically Transport) may not be able to re-use a persistent TCP |
2167 | // connection to the server for a subsequent "keep-alive" request. |
2168 | // |
2169 | +// The request Body, if non-nil, will be closed by the underlying |
2170 | +// Transport, even on errors. |
2171 | +// |
2172 | // Generally Get, Post, or PostForm will be used instead of Do. |
2173 | func (c *Client) Do(req *Request) (resp *Response, err error) { |
2174 | if req.Method == "GET" || req.Method == "HEAD" { |
2175 | @@ -163,14 +167,17 @@ |
2176 | // Caller should close resp.Body when done reading from it. |
2177 | func send(req *Request, t RoundTripper) (resp *Response, err error) { |
2178 | if t == nil { |
2179 | + req.closeBody() |
2180 | return nil, errors.New("http: no Client.Transport or DefaultTransport") |
2181 | } |
2182 | |
2183 | if req.URL == nil { |
2184 | + req.closeBody() |
2185 | return nil, errors.New("http: nil Request.URL") |
2186 | } |
2187 | |
2188 | if req.RequestURI != "" { |
2189 | + req.closeBody() |
2190 | return nil, errors.New("http: Request.RequestURI can't be set in client requests.") |
2191 | } |
2192 | |
2193 | @@ -278,6 +285,7 @@ |
2194 | var via []*Request |
2195 | |
2196 | if ireq.URL == nil { |
2197 | + ireq.closeBody() |
2198 | return nil, errors.New("http: nil Request.URL") |
2199 | } |
2200 | |
2201 | @@ -400,7 +408,7 @@ |
2202 | // Caller should close resp.Body when done reading from it. |
2203 | // |
2204 | // If the provided body is also an io.Closer, it is closed after the |
2205 | -// body is successfully written to the server. |
2206 | +// request. |
2207 | func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { |
2208 | req, err := NewRequest("POST", url, body) |
2209 | if err != nil { |
2210 | |
2211 | === modified file 'http13client/client_test.go' |
2212 | --- http13client/client_test.go 2014-03-20 09:26:28 +0000 |
2213 | +++ http13client/client_test.go 2014-06-20 13:24:39 +0000 |
2214 | @@ -20,6 +20,8 @@ |
2215 | "net/http" |
2216 | "net/http/httptest" |
2217 | "net/url" |
2218 | + "reflect" |
2219 | + "sort" |
2220 | "strconv" |
2221 | "strings" |
2222 | "sync" |
2223 | @@ -877,3 +879,93 @@ |
2224 | t.Fatal("server saw different client ports before & after the redirect") |
2225 | } |
2226 | } |
2227 | + |
2228 | +// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF. |
2229 | +type eofReaderFunc func() |
2230 | + |
2231 | +func (f eofReaderFunc) Read(p []byte) (n int, err error) { |
2232 | + f() |
2233 | + return 0, io.EOF |
2234 | +} |
2235 | + |
2236 | +func TestClientTrailers(t *testing.T) { |
2237 | + defer afterTest(t) |
2238 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2239 | + w.Header().Set("Connection", "close") |
2240 | + w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") |
2241 | + w.Header().Add("Trailer", "Server-Trailer-C") |
2242 | + |
2243 | + var decl []string |
2244 | + for k := range r.Trailer { |
2245 | + decl = append(decl, k) |
2246 | + } |
2247 | + sort.Strings(decl) |
2248 | + |
2249 | + slurp, err := ioutil.ReadAll(r.Body) |
2250 | + if err != nil { |
2251 | + t.Errorf("Server reading request body: %v", err) |
2252 | + } |
2253 | + if string(slurp) != "foo" { |
2254 | + t.Errorf("Server read request body %q; want foo", slurp) |
2255 | + } |
2256 | + if r.Trailer == nil { |
2257 | + io.WriteString(w, "nil Trailer") |
2258 | + } else { |
2259 | + fmt.Fprintf(w, "decl: %v, vals: %s, %s", |
2260 | + decl, |
2261 | + r.Trailer.Get("Client-Trailer-A"), |
2262 | + r.Trailer.Get("Client-Trailer-B")) |
2263 | + } |
2264 | + |
2265 | + // TODO: golang.org/issue/7759: there's no way yet for |
2266 | + // the server to set trailers without hijacking, so do |
2267 | + // that for now, just to test the client. Later, in |
2268 | + // Go 1.4, it should be implicit that any mutations |
2269 | + // to w.Header() after the initial write are the |
2270 | + // trailers to be sent, if and only if they were |
2271 | + // previously declared with w.Header().Set("Trailer", |
2272 | + // ..keys..) |
2273 | + w.(http.Flusher).Flush() |
2274 | + conn, buf, _ := w.(http.Hijacker).Hijack() |
2275 | + t := http.Header{} |
2276 | + t.Set("Server-Trailer-A", "valuea") |
2277 | + t.Set("Server-Trailer-C", "valuec") // skipping B |
2278 | + buf.WriteString("0\r\n") // eof |
2279 | + t.Write(buf) |
2280 | + buf.WriteString("\r\n") // end of trailers |
2281 | + buf.Flush() |
2282 | + conn.Close() |
2283 | + })) |
2284 | + defer ts.Close() |
2285 | + |
2286 | + var req *Request |
2287 | + req, _ = NewRequest("POST", ts.URL, io.MultiReader( |
2288 | + eofReaderFunc(func() { |
2289 | + req.Trailer["Client-Trailer-A"] = []string{"valuea"} |
2290 | + }), |
2291 | + strings.NewReader("foo"), |
2292 | + eofReaderFunc(func() { |
2293 | + req.Trailer["Client-Trailer-B"] = []string{"valueb"} |
2294 | + }), |
2295 | + )) |
2296 | + req.Trailer = http.Header{ |
2297 | + "Client-Trailer-A": nil, // to be set later |
2298 | + "Client-Trailer-B": nil, // to be set later |
2299 | + } |
2300 | + req.ContentLength = -1 |
2301 | + res, err := DefaultClient.Do(req) |
2302 | + if err != nil { |
2303 | + t.Fatal(err) |
2304 | + } |
2305 | + if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { |
2306 | + t.Error(err) |
2307 | + } |
2308 | + want := http.Header{ |
2309 | + "Server-Trailer-A": []string{"valuea"}, |
2310 | + "Server-Trailer-B": nil, |
2311 | + "Server-Trailer-C": []string{"valuec"}, |
2312 | + } |
2313 | + if !reflect.DeepEqual(res.Trailer, want) { |
2314 | + t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want) |
2315 | + } |
2316 | +} |
2317 | |
2318 | === modified file 'http13client/cookie.go' |
2319 | --- http13client/cookie.go 2014-03-19 23:13:58 +0000 |
2320 | +++ http13client/cookie.go 2014-06-20 13:24:39 +0000 |
2321 | @@ -55,11 +55,7 @@ |
2322 | attr, val = attr[:j], attr[j+1:] |
2323 | } |
2324 | lowerAttr := strings.ToLower(attr) |
2325 | - parseCookieValueFn := parseCookieValue |
2326 | - if lowerAttr == "expires" { |
2327 | - parseCookieValueFn = parseCookieExpiresValue |
2328 | - } |
2329 | - val, success = parseCookieValueFn(val) |
2330 | + val, success = parseCookieValue(val) |
2331 | if !success { |
2332 | c.Unparsed = append(c.Unparsed, parts[i]) |
2333 | continue |
2334 | @@ -230,12 +226,23 @@ |
2335 | // ; US-ASCII characters excluding CTLs, |
2336 | // ; whitespace DQUOTE, comma, semicolon, |
2337 | // ; and backslash |
2338 | +// We loosen this as spaces and commas are common in cookie values |
2339 | +// but we produce a quoted cookie-value in when value starts or ends |
2340 | +// with a comma or space. |
2341 | +// See http://golang.org/issue/7243 for the discussion. |
2342 | func sanitizeCookieValue(v string) string { |
2343 | - return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) |
2344 | + v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) |
2345 | + if len(v) == 0 { |
2346 | + return v |
2347 | + } |
2348 | + if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' { |
2349 | + return `"` + v + `"` |
2350 | + } |
2351 | + return v |
2352 | } |
2353 | |
2354 | func validCookieValueByte(b byte) bool { |
2355 | - return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\' |
2356 | + return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' |
2357 | } |
2358 | |
2359 | // path-av = "Path=" path-value |
2360 | @@ -270,38 +277,13 @@ |
2361 | return string(buf) |
2362 | } |
2363 | |
2364 | -func unquoteCookieValue(v string) string { |
2365 | - if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { |
2366 | - return v[1 : len(v)-1] |
2367 | - } |
2368 | - return v |
2369 | -} |
2370 | - |
2371 | -func isCookieByte(c byte) bool { |
2372 | - switch { |
2373 | - case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, |
2374 | - 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: |
2375 | - return true |
2376 | - } |
2377 | - return false |
2378 | -} |
2379 | - |
2380 | -func isCookieExpiresByte(c byte) (ok bool) { |
2381 | - return isCookieByte(c) || c == ',' || c == ' ' |
2382 | -} |
2383 | - |
2384 | func parseCookieValue(raw string) (string, bool) { |
2385 | - return parseCookieValueUsing(raw, isCookieByte) |
2386 | -} |
2387 | - |
2388 | -func parseCookieExpiresValue(raw string) (string, bool) { |
2389 | - return parseCookieValueUsing(raw, isCookieExpiresByte) |
2390 | -} |
2391 | - |
2392 | -func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { |
2393 | - raw = unquoteCookieValue(raw) |
2394 | + // Strip the quotes, if present. |
2395 | + if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { |
2396 | + raw = raw[1 : len(raw)-1] |
2397 | + } |
2398 | for i := 0; i < len(raw); i++ { |
2399 | - if !validByte(raw[i]) { |
2400 | + if !validCookieValueByte(raw[i]) { |
2401 | return "", false |
2402 | } |
2403 | } |
2404 | |
2405 | === removed file 'http13client/cookie_test.go' |
2406 | --- http13client/cookie_test.go 2014-03-19 23:13:58 +0000 |
2407 | +++ http13client/cookie_test.go 1970-01-01 00:00:00 +0000 |
2408 | @@ -1,304 +0,0 @@ |
2409 | -// Copyright 2010 The Go Authors. All rights reserved. |
2410 | -// Use of this source code is governed by a BSD-style |
2411 | -// license that can be found in the LICENSE file. |
2412 | - |
2413 | -package http |
2414 | - |
2415 | -import ( |
2416 | - "bytes" |
2417 | - "encoding/json" |
2418 | - "fmt" |
2419 | - "log" |
2420 | - "net/http" |
2421 | - "os" |
2422 | - "reflect" |
2423 | - "strings" |
2424 | - "testing" |
2425 | - "time" |
2426 | -) |
2427 | - |
2428 | -var writeSetCookiesTests = []struct { |
2429 | - Cookie *http.Cookie |
2430 | - Raw string |
2431 | -}{ |
2432 | - { |
2433 | - &http.Cookie{Name: "cookie-1", Value: "v$1"}, |
2434 | - "cookie-1=v$1", |
2435 | - }, |
2436 | - { |
2437 | - &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
2438 | - "cookie-2=two; Max-Age=3600", |
2439 | - }, |
2440 | - { |
2441 | - &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
2442 | - "cookie-3=three; Domain=example.com", |
2443 | - }, |
2444 | - { |
2445 | - &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
2446 | - "cookie-4=four; Path=/restricted/", |
2447 | - }, |
2448 | - { |
2449 | - &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
2450 | - "cookie-5=five", |
2451 | - }, |
2452 | - { |
2453 | - &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
2454 | - "cookie-6=six", |
2455 | - }, |
2456 | - { |
2457 | - &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
2458 | - "cookie-7=seven; Domain=127.0.0.1", |
2459 | - }, |
2460 | - { |
2461 | - &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
2462 | - "cookie-8=eight", |
2463 | - }, |
2464 | -} |
2465 | - |
2466 | -func TestWriteSetCookies(t *testing.T) { |
2467 | - defer log.SetOutput(os.Stderr) |
2468 | - var logbuf bytes.Buffer |
2469 | - log.SetOutput(&logbuf) |
2470 | - |
2471 | - for i, tt := range writeSetCookiesTests { |
2472 | - if g, e := tt.Cookie.String(), tt.Raw; g != e { |
2473 | - t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g) |
2474 | - continue |
2475 | - } |
2476 | - } |
2477 | - |
2478 | - if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) { |
2479 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
2480 | - } |
2481 | -} |
2482 | - |
2483 | -type headerOnlyResponseWriter http.Header |
2484 | - |
2485 | -func (ho headerOnlyResponseWriter) Header() http.Header { |
2486 | - return http.Header(ho) |
2487 | -} |
2488 | - |
2489 | -func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { |
2490 | - panic("NOIMPL") |
2491 | -} |
2492 | - |
2493 | -func (ho headerOnlyResponseWriter) WriteHeader(int) { |
2494 | - panic("NOIMPL") |
2495 | -} |
2496 | - |
2497 | -func TestSetCookie(t *testing.T) { |
2498 | - m := make(http.Header) |
2499 | - http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
2500 | - http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
2501 | - if l := len(m["Set-Cookie"]); l != 2 { |
2502 | - t.Fatalf("expected %d cookies, got %d", 2, l) |
2503 | - } |
2504 | - if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e { |
2505 | - t.Errorf("cookie #1: want %q, got %q", e, g) |
2506 | - } |
2507 | - if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e { |
2508 | - t.Errorf("cookie #2: want %q, got %q", e, g) |
2509 | - } |
2510 | -} |
2511 | - |
2512 | -var addCookieTests = []struct { |
2513 | - Cookies []*http.Cookie |
2514 | - Raw string |
2515 | -}{ |
2516 | - { |
2517 | - []*http.Cookie{}, |
2518 | - "", |
2519 | - }, |
2520 | - { |
2521 | - []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, |
2522 | - "cookie-1=v$1", |
2523 | - }, |
2524 | - { |
2525 | - []*http.Cookie{ |
2526 | - {Name: "cookie-1", Value: "v$1"}, |
2527 | - {Name: "cookie-2", Value: "v$2"}, |
2528 | - {Name: "cookie-3", Value: "v$3"}, |
2529 | - }, |
2530 | - "cookie-1=v$1; cookie-2=v$2; cookie-3=v$3", |
2531 | - }, |
2532 | -} |
2533 | - |
2534 | -func TestAddCookie(t *testing.T) { |
2535 | - for i, tt := range addCookieTests { |
2536 | - req, _ := NewRequest("GET", "http://example.com/", nil) |
2537 | - for _, c := range tt.Cookies { |
2538 | - req.AddCookie(c) |
2539 | - } |
2540 | - if g := req.Header.Get("Cookie"); g != tt.Raw { |
2541 | - t.Errorf("Test %d:\nwant: %s\n got: %s\n", i, tt.Raw, g) |
2542 | - continue |
2543 | - } |
2544 | - } |
2545 | -} |
2546 | - |
2547 | -var readSetCookiesTests = []struct { |
2548 | - Header http.Header |
2549 | - Cookies []*http.Cookie |
2550 | -}{ |
2551 | - { |
2552 | - http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
2553 | - []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
2554 | - }, |
2555 | - { |
2556 | - http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
2557 | - []*http.Cookie{{ |
2558 | - Name: "NID", |
2559 | - Value: "99=YsDT5i3E-CXax-", |
2560 | - Path: "/", |
2561 | - Domain: ".google.ch", |
2562 | - HttpOnly: true, |
2563 | - Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC), |
2564 | - RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT", |
2565 | - Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly", |
2566 | - }}, |
2567 | - }, |
2568 | - { |
2569 | - http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
2570 | - []*http.Cookie{{ |
2571 | - Name: ".ASPXAUTH", |
2572 | - Value: "7E3AA", |
2573 | - Path: "/", |
2574 | - Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC), |
2575 | - RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT", |
2576 | - HttpOnly: true, |
2577 | - Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly", |
2578 | - }}, |
2579 | - }, |
2580 | - { |
2581 | - http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
2582 | - []*http.Cookie{{ |
2583 | - Name: "ASP.NET_SessionId", |
2584 | - Value: "foo", |
2585 | - Path: "/", |
2586 | - HttpOnly: true, |
2587 | - Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly", |
2588 | - }}, |
2589 | - }, |
2590 | - |
2591 | - // TODO(bradfitz): users have reported seeing this in the |
2592 | - // wild, but do browsers handle it? RFC 6265 just says "don't |
2593 | - // do that" (section 3) and then never mentions header folding |
2594 | - // again. |
2595 | - // Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
2596 | -} |
2597 | - |
2598 | -func toJSON(v interface{}) string { |
2599 | - b, err := json.Marshal(v) |
2600 | - if err != nil { |
2601 | - return fmt.Sprintf("%#v", v) |
2602 | - } |
2603 | - return string(b) |
2604 | -} |
2605 | - |
2606 | -func TestReadSetCookies(t *testing.T) { |
2607 | - for i, tt := range readSetCookiesTests { |
2608 | - for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input |
2609 | - c := readSetCookies(tt.Header) |
2610 | - if !reflect.DeepEqual(c, tt.Cookies) { |
2611 | - t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies)) |
2612 | - continue |
2613 | - } |
2614 | - } |
2615 | - } |
2616 | -} |
2617 | - |
2618 | -var readCookiesTests = []struct { |
2619 | - Header http.Header |
2620 | - Filter string |
2621 | - Cookies []*http.Cookie |
2622 | -}{ |
2623 | - { |
2624 | - http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2625 | - "", |
2626 | - []*http.Cookie{ |
2627 | - {Name: "Cookie-1", Value: "v$1"}, |
2628 | - {Name: "c2", Value: "v2"}, |
2629 | - }, |
2630 | - }, |
2631 | - { |
2632 | - http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2633 | - "c2", |
2634 | - []*http.Cookie{ |
2635 | - {Name: "c2", Value: "v2"}, |
2636 | - }, |
2637 | - }, |
2638 | - { |
2639 | - http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2640 | - "", |
2641 | - []*http.Cookie{ |
2642 | - {Name: "Cookie-1", Value: "v$1"}, |
2643 | - {Name: "c2", Value: "v2"}, |
2644 | - }, |
2645 | - }, |
2646 | - { |
2647 | - http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2648 | - "c2", |
2649 | - []*http.Cookie{ |
2650 | - {Name: "c2", Value: "v2"}, |
2651 | - }, |
2652 | - }, |
2653 | -} |
2654 | - |
2655 | -func TestReadCookies(t *testing.T) { |
2656 | - for i, tt := range readCookiesTests { |
2657 | - for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input |
2658 | - c := readCookies(tt.Header, tt.Filter) |
2659 | - if !reflect.DeepEqual(c, tt.Cookies) { |
2660 | - t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies)) |
2661 | - continue |
2662 | - } |
2663 | - } |
2664 | - } |
2665 | -} |
2666 | - |
2667 | -func TestCookieSanitizeValue(t *testing.T) { |
2668 | - defer log.SetOutput(os.Stderr) |
2669 | - var logbuf bytes.Buffer |
2670 | - log.SetOutput(&logbuf) |
2671 | - |
2672 | - tests := []struct { |
2673 | - in, want string |
2674 | - }{ |
2675 | - {"foo", "foo"}, |
2676 | - {"foo bar", "foobar"}, |
2677 | - {"\x00\x7e\x7f\x80", "\x7e"}, |
2678 | - {`"withquotes"`, "withquotes"}, |
2679 | - } |
2680 | - for _, tt := range tests { |
2681 | - if got := sanitizeCookieValue(tt.in); got != tt.want { |
2682 | - t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want) |
2683 | - } |
2684 | - } |
2685 | - |
2686 | - if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { |
2687 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
2688 | - } |
2689 | -} |
2690 | - |
2691 | -func TestCookieSanitizePath(t *testing.T) { |
2692 | - defer log.SetOutput(os.Stderr) |
2693 | - var logbuf bytes.Buffer |
2694 | - log.SetOutput(&logbuf) |
2695 | - |
2696 | - tests := []struct { |
2697 | - in, want string |
2698 | - }{ |
2699 | - {"/path", "/path"}, |
2700 | - {"/path with space/", "/path with space/"}, |
2701 | - {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"}, |
2702 | - } |
2703 | - for _, tt := range tests { |
2704 | - if got := sanitizeCookiePath(tt.in); got != tt.want { |
2705 | - t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want) |
2706 | - } |
2707 | - } |
2708 | - |
2709 | - if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { |
2710 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
2711 | - } |
2712 | -} |
2713 | |
2714 | === modified file 'http13client/request.go' |
2715 | --- http13client/request.go 2014-03-19 23:13:58 +0000 |
2716 | +++ http13client/request.go 2014-06-20 13:24:39 +0000 |
2717 | @@ -69,18 +69,31 @@ |
2718 | |
2719 | // A Request represents an HTTP request received by a server |
2720 | // or to be sent by a client. |
2721 | +// |
2722 | +// The field semantics differ slightly between client and server |
2723 | +// usage. In addition to the notes on the fields below, see the |
2724 | +// documentation for Request.Write and RoundTripper. |
2725 | type Request struct { |
2726 | - Method string // GET, POST, PUT, etc. |
2727 | + // Method specifies the HTTP method (GET, POST, PUT, etc.). |
2728 | + // For client requests an empty string means GET. |
2729 | + Method string |
2730 | |
2731 | - // URL is created from the URI supplied on the Request-Line |
2732 | - // as stored in RequestURI. |
2733 | - // |
2734 | - // For most requests, fields other than Path and RawQuery |
2735 | - // will be empty. (See RFC 2616, Section 5.1.2) |
2736 | + // URL specifies either the URI being requested (for server |
2737 | + // requests) or the URL to access (for client requests). |
2738 | + // |
2739 | + // For server requests the URL is parsed from the URI |
2740 | + // supplied on the Request-Line as stored in RequestURI. For |
2741 | + // most requests, fields other than Path and RawQuery will be |
2742 | + // empty. (See RFC 2616, Section 5.1.2) |
2743 | + // |
2744 | + // For client requests, the URL's Host specifies the server to |
2745 | + // connect to, while the Request's Host field optionally |
2746 | + // specifies the Host header value to send in the HTTP |
2747 | + // request. |
2748 | URL *url.URL |
2749 | |
2750 | // The protocol version for incoming requests. |
2751 | - // Outgoing requests always use HTTP/1.1. |
2752 | + // Client requests always use HTTP/1.1. |
2753 | Proto string // "HTTP/1.0" |
2754 | ProtoMajor int // 1 |
2755 | ProtoMinor int // 0 |
2756 | @@ -104,15 +117,20 @@ |
2757 | // The request parser implements this by canonicalizing the |
2758 | // name, making the first character and any characters |
2759 | // following a hyphen uppercase and the rest lowercase. |
2760 | + // |
2761 | + // For client requests certain headers are automatically |
2762 | + // added and may override values in Header. |
2763 | + // |
2764 | + // See the documentation for the Request.Write method. |
2765 | Header http.Header |
2766 | |
2767 | // Body is the request's body. |
2768 | // |
2769 | - // For client requests, a nil body means the request has no |
2770 | + // For client requests a nil body means the request has no |
2771 | // body, such as a GET request. The HTTP Client's Transport |
2772 | // is responsible for calling the Close method. |
2773 | // |
2774 | - // For server requests, the Request Body is always non-nil |
2775 | + // For server requests the Request Body is always non-nil |
2776 | // but will return EOF immediately when no body is present. |
2777 | // The Server will close the request body. The ServeHTTP |
2778 | // Handler does not need to. |
2779 | @@ -122,7 +140,7 @@ |
2780 | // The value -1 indicates that the length is unknown. |
2781 | // Values >= 0 indicate that the given number of bytes may |
2782 | // be read from Body. |
2783 | - // For outgoing requests, a value of 0 means unknown if Body is not nil. |
2784 | + // For client requests, a value of 0 means unknown if Body is not nil. |
2785 | ContentLength int64 |
2786 | |
2787 | // TransferEncoding lists the transfer encodings from outermost to |
2788 | @@ -133,13 +151,18 @@ |
2789 | TransferEncoding []string |
2790 | |
2791 | // Close indicates whether to close the connection after |
2792 | - // replying to this request. |
2793 | + // replying to this request (for servers) or after sending |
2794 | + // the request (for clients). |
2795 | Close bool |
2796 | |
2797 | - // The host on which the URL is sought. |
2798 | - // Per RFC 2616, this is either the value of the Host: header |
2799 | - // or the host name given in the URL itself. |
2800 | + // For server requests Host specifies the host on which the |
2801 | + // URL is sought. Per RFC 2616, this is either the value of |
2802 | + // the "Host" header or the host name given in the URL itself. |
2803 | // It may be of the form "host:port". |
2804 | + // |
2805 | + // For client requests Host optionally overrides the Host |
2806 | + // header to send. If empty, the Request.Write method uses |
2807 | + // the value of URL.Host. |
2808 | Host string |
2809 | |
2810 | // Form contains the parsed form data, including both the URL |
2811 | @@ -159,12 +182,24 @@ |
2812 | // The HTTP client ignores MultipartForm and uses Body instead. |
2813 | MultipartForm *multipart.Form |
2814 | |
2815 | - // Trailer maps trailer keys to values. Like for Header, if the |
2816 | - // response has multiple trailer lines with the same key, they will be |
2817 | - // concatenated, delimited by commas. |
2818 | - // For server requests, Trailer is only populated after Body has been |
2819 | - // closed or fully consumed. |
2820 | - // Trailer support is only partially complete. |
2821 | + // Trailer specifies additional headers that are sent after the request |
2822 | + // body. |
2823 | + // |
2824 | + // For server requests the Trailer map initially contains only the |
2825 | + // trailer keys, with nil values. (The client declares which trailers it |
2826 | + // will later send.) While the handler is reading from Body, it must |
2827 | + // not reference Trailer. After reading from Body returns EOF, Trailer |
2828 | + // can be read again and will contain non-nil values, if they were sent |
2829 | + // by the client. |
2830 | + // |
2831 | + // For client requests Trailer must be initialized to a map containing |
2832 | + // the trailer keys to later send. The values may be nil or their final |
2833 | + // values. The ContentLength must be 0 or -1, to send a chunked request. |
2834 | + // After the HTTP request is sent the map values can be updated while |
2835 | + // the request body is read. Once the body returns EOF, the caller must |
2836 | + // not mutate Trailer. |
2837 | + // |
2838 | + // Few HTTP clients, servers, or proxies support HTTP trailers. |
2839 | Trailer http.Header |
2840 | |
2841 | // RemoteAddr allows HTTP servers and other software to record |
2842 | @@ -382,7 +417,6 @@ |
2843 | return err |
2844 | } |
2845 | |
2846 | - // TODO: split long values? (If so, should share code with Conn.Write) |
2847 | err = req.Header.WriteSubset(w, reqWriteExcludeHeader) |
2848 | if err != nil { |
2849 | return err |
2850 | @@ -589,32 +623,6 @@ |
2851 | |
2852 | fixPragmaCacheControl(req.Header) |
2853 | |
2854 | - // TODO: Parse specific header values: |
2855 | - // Accept |
2856 | - // Accept-Encoding |
2857 | - // Accept-Language |
2858 | - // Authorization |
2859 | - // Cache-Control |
2860 | - // Connection |
2861 | - // Date |
2862 | - // Expect |
2863 | - // From |
2864 | - // If-Match |
2865 | - // If-Modified-Since |
2866 | - // If-None-Match |
2867 | - // If-Range |
2868 | - // If-Unmodified-Since |
2869 | - // Max-Forwards |
2870 | - // Proxy-Authorization |
2871 | - // Referer [sic] |
2872 | - // TE (transfer-codings) |
2873 | - // Trailer |
2874 | - // Transfer-Encoding |
2875 | - // Upgrade |
2876 | - // User-Agent |
2877 | - // Via |
2878 | - // Warning |
2879 | - |
2880 | err = readTransfer(req, b) |
2881 | if err != nil { |
2882 | return nil, err |
2883 | @@ -783,9 +791,7 @@ |
2884 | } |
2885 | |
2886 | mr, err := r.multipartReader() |
2887 | - if err == ErrNotMultipart { |
2888 | - return nil |
2889 | - } else if err != nil { |
2890 | + if err != nil { |
2891 | return err |
2892 | } |
2893 | |
2894 | @@ -863,3 +869,9 @@ |
2895 | func (r *Request) wantsClose() bool { |
2896 | return hasToken(r.Header.Get("Connection"), "close") |
2897 | } |
2898 | + |
2899 | +func (r *Request) closeBody() { |
2900 | + if r.Body != nil { |
2901 | + r.Body.Close() |
2902 | + } |
2903 | +} |
2904 | |
2905 | === modified file 'http13client/request_test.go' |
2906 | --- http13client/request_test.go 2014-03-20 09:26:28 +0000 |
2907 | +++ http13client/request_test.go 2014-06-20 13:24:39 +0000 |
2908 | @@ -155,7 +155,25 @@ |
2909 | req.Header = http.Header{"Content-Type": {"text/plain"}} |
2910 | multipart, err = req.MultipartReader() |
2911 | if multipart != nil { |
2912 | - t.Errorf("unexpected multipart for text/plain") |
2913 | + t.Error("unexpected multipart for text/plain") |
2914 | + } |
2915 | +} |
2916 | + |
2917 | +func TestParseMultipartForm(t *testing.T) { |
2918 | + req := &Request{ |
2919 | + Method: "POST", |
2920 | + Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
2921 | + Body: ioutil.NopCloser(new(bytes.Buffer)), |
2922 | + } |
2923 | + err := req.ParseMultipartForm(25) |
2924 | + if err == nil { |
2925 | + t.Error("expected multipart EOF, got nil") |
2926 | + } |
2927 | + |
2928 | + req.Header = http.Header{"Content-Type": {"text/plain"}} |
2929 | + err = req.ParseMultipartForm(25) |
2930 | + if err != ErrNotMultipart { |
2931 | + t.Error("expected ErrNotMultipart for text/plain") |
2932 | } |
2933 | } |
2934 | |
2935 | @@ -221,16 +239,38 @@ |
2936 | validateTestMultipartContents(t, req, true) |
2937 | } |
2938 | |
2939 | -func TestEmptyMultipartRequest(t *testing.T) { |
2940 | - // Test that FormValue and FormFile automatically invoke |
2941 | - // ParseMultipartForm and return the right values. |
2942 | - req, err := NewRequest("GET", "/", nil) |
2943 | - if err != nil { |
2944 | - t.Errorf("NewRequest err = %q", err) |
2945 | - } |
2946 | +func TestMissingFileMultipartRequest(t *testing.T) { |
2947 | + // Test that FormFile returns an error if |
2948 | + // the named file is missing. |
2949 | + req := newTestMultipartRequest(t) |
2950 | testMissingFile(t, req) |
2951 | } |
2952 | |
2953 | +// Test that FormValue invokes ParseMultipartForm. |
2954 | +func TestFormValueCallsParseMultipartForm(t *testing.T) { |
2955 | + req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post")) |
2956 | + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") |
2957 | + if req.Form != nil { |
2958 | + t.Fatal("Unexpected request Form, want nil") |
2959 | + } |
2960 | + req.FormValue("z") |
2961 | + if req.Form == nil { |
2962 | + t.Fatal("ParseMultipartForm not called by FormValue") |
2963 | + } |
2964 | +} |
2965 | + |
2966 | +// Test that FormFile invokes ParseMultipartForm. |
2967 | +func TestFormFileCallsParseMultipartForm(t *testing.T) { |
2968 | + req := newTestMultipartRequest(t) |
2969 | + if req.Form != nil { |
2970 | + t.Fatal("Unexpected request Form, want nil") |
2971 | + } |
2972 | + req.FormFile("") |
2973 | + if req.Form == nil { |
2974 | + t.Fatal("ParseMultipartForm not called by FormFile") |
2975 | + } |
2976 | +} |
2977 | + |
2978 | // Test that ParseMultipartForm errors if called |
2979 | // after MultipartReader on the same request. |
2980 | func TestParseMultipartFormOrder(t *testing.T) { |
2981 | |
2982 | === modified file 'http13client/response.go' |
2983 | --- http13client/response.go 2014-03-19 23:43:25 +0000 |
2984 | +++ http13client/response.go 2014-06-20 13:24:39 +0000 |
2985 | @@ -8,6 +8,7 @@ |
2986 | |
2987 | import ( |
2988 | "bufio" |
2989 | + "bytes" |
2990 | "crypto/tls" |
2991 | "errors" |
2992 | "io" |
2993 | @@ -47,7 +48,8 @@ |
2994 | // |
2995 | // The http Client and Transport guarantee that Body is always |
2996 | // non-nil, even on responses without a body or responses with |
2997 | - // a zero-lengthed body. |
2998 | + // a zero-length body. It is the caller's responsibility to |
2999 | + // close Body. |
3000 | // |
3001 | // The Body is automatically dechunked if the server replied |
3002 | // with a "chunked" Transfer-Encoding. |
3003 | @@ -200,7 +202,6 @@ |
3004 | // |
3005 | // Body is closed after it is sent. |
3006 | func (r *Response) Write(w io.Writer) error { |
3007 | - |
3008 | // Status line |
3009 | text := r.Status |
3010 | if text == "" { |
3011 | @@ -212,10 +213,45 @@ |
3012 | protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor) |
3013 | statusCode := strconv.Itoa(r.StatusCode) + " " |
3014 | text = strings.TrimPrefix(text, statusCode) |
3015 | - io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n") |
3016 | + if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); err != nil { |
3017 | + return err |
3018 | + } |
3019 | + |
3020 | + // Clone it, so we can modify r1 as needed. |
3021 | + r1 := new(Response) |
3022 | + *r1 = *r |
3023 | + if r1.ContentLength == 0 && r1.Body != nil { |
3024 | + // Is it actually 0 length? Or just unknown? |
3025 | + var buf [1]byte |
3026 | + n, err := r1.Body.Read(buf[:]) |
3027 | + if err != nil && err != io.EOF { |
3028 | + return err |
3029 | + } |
3030 | + if n == 0 { |
3031 | + // Reset it to a known zero reader, in case underlying one |
3032 | + // is unhappy being read repeatedly. |
3033 | + r1.Body = eofReader |
3034 | + } else { |
3035 | + r1.ContentLength = -1 |
3036 | + r1.Body = struct { |
3037 | + io.Reader |
3038 | + io.Closer |
3039 | + }{ |
3040 | + io.MultiReader(bytes.NewReader(buf[:1]), r.Body), |
3041 | + r.Body, |
3042 | + } |
3043 | + } |
3044 | + } |
3045 | + // If we're sending a non-chunked HTTP/1.1 response without a |
3046 | + // content-length, the only way to do that is the old HTTP/1.0 |
3047 | + // way, by noting the EOF with a connection close, so we need |
3048 | + // to set Close. |
3049 | + if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) { |
3050 | + r1.Close = true |
3051 | + } |
3052 | |
3053 | // Process Body,ContentLength,Close,Trailer |
3054 | - tw, err := newTransferWriter(r) |
3055 | + tw, err := newTransferWriter(r1) |
3056 | if err != nil { |
3057 | return err |
3058 | } |
3059 | @@ -230,8 +266,19 @@ |
3060 | return err |
3061 | } |
3062 | |
3063 | + // contentLengthAlreadySent may have been already sent for |
3064 | + // POST/PUT requests, even if zero length. See Issue 8180. |
3065 | + contentLengthAlreadySent := tw.shouldSendContentLength() |
3066 | + if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent { |
3067 | + if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil { |
3068 | + return err |
3069 | + } |
3070 | + } |
3071 | + |
3072 | // End-of-header |
3073 | - io.WriteString(w, "\r\n") |
3074 | + if _, err := io.WriteString(w, "\r\n"); err != nil { |
3075 | + return err |
3076 | + } |
3077 | |
3078 | // Write body and trailer |
3079 | err = tw.WriteBody(w) |
3080 | |
3081 | === modified file 'http13client/response_test.go' |
3082 | --- http13client/response_test.go 2014-03-19 23:13:58 +0000 |
3083 | +++ http13client/response_test.go 2014-06-20 13:24:39 +0000 |
3084 | @@ -30,6 +30,10 @@ |
3085 | return &Request{Method: method} |
3086 | } |
3087 | |
3088 | +func dummyReq11(method string) *Request { |
3089 | + return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} |
3090 | +} |
3091 | + |
3092 | var respTests = []respTest{ |
3093 | // Unchunked response without Content-Length. |
3094 | { |
3095 | |
3096 | === modified file 'http13client/responsewrite_test.go' |
3097 | --- http13client/responsewrite_test.go 2014-03-19 23:13:58 +0000 |
3098 | +++ http13client/responsewrite_test.go 2014-06-20 13:24:39 +0000 |
3099 | @@ -27,7 +27,7 @@ |
3100 | ProtoMinor: 0, |
3101 | Request: dummyReq("GET"), |
3102 | Header: http.Header{}, |
3103 | - Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), |
3104 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3105 | ContentLength: 6, |
3106 | }, |
3107 | |
3108 | @@ -50,6 +50,106 @@ |
3109 | "\r\n" + |
3110 | "abcdef", |
3111 | }, |
3112 | + // HTTP/1.1 response with unknown length and Connection: close |
3113 | + { |
3114 | + Response{ |
3115 | + StatusCode: 200, |
3116 | + ProtoMajor: 1, |
3117 | + ProtoMinor: 1, |
3118 | + Request: dummyReq("GET"), |
3119 | + Header: http.Header{}, |
3120 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3121 | + ContentLength: -1, |
3122 | + Close: true, |
3123 | + }, |
3124 | + "HTTP/1.1 200 OK\r\n" + |
3125 | + "Connection: close\r\n" + |
3126 | + "\r\n" + |
3127 | + "abcdef", |
3128 | + }, |
3129 | + // HTTP/1.1 response with unknown length and not setting connection: close |
3130 | + { |
3131 | + Response{ |
3132 | + StatusCode: 200, |
3133 | + ProtoMajor: 1, |
3134 | + ProtoMinor: 1, |
3135 | + Request: dummyReq11("GET"), |
3136 | + Header: http.Header{}, |
3137 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3138 | + ContentLength: -1, |
3139 | + Close: false, |
3140 | + }, |
3141 | + "HTTP/1.1 200 OK\r\n" + |
3142 | + "Connection: close\r\n" + |
3143 | + "\r\n" + |
3144 | + "abcdef", |
3145 | + }, |
3146 | + // HTTP/1.1 response with unknown length and not setting connection: close, but |
3147 | + // setting chunked. |
3148 | + { |
3149 | + Response{ |
3150 | + StatusCode: 200, |
3151 | + ProtoMajor: 1, |
3152 | + ProtoMinor: 1, |
3153 | + Request: dummyReq11("GET"), |
3154 | + Header: http.Header{}, |
3155 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3156 | + ContentLength: -1, |
3157 | + TransferEncoding: []string{"chunked"}, |
3158 | + Close: false, |
3159 | + }, |
3160 | + "HTTP/1.1 200 OK\r\n" + |
3161 | + "Transfer-Encoding: chunked\r\n\r\n" + |
3162 | + "6\r\nabcdef\r\n0\r\n\r\n", |
3163 | + }, |
3164 | + // HTTP/1.1 response 0 content-length, and nil body |
3165 | + { |
3166 | + Response{ |
3167 | + StatusCode: 200, |
3168 | + ProtoMajor: 1, |
3169 | + ProtoMinor: 1, |
3170 | + Request: dummyReq11("GET"), |
3171 | + Header: http.Header{}, |
3172 | + Body: nil, |
3173 | + ContentLength: 0, |
3174 | + Close: false, |
3175 | + }, |
3176 | + "HTTP/1.1 200 OK\r\n" + |
3177 | + "Content-Length: 0\r\n" + |
3178 | + "\r\n", |
3179 | + }, |
3180 | + // HTTP/1.1 response 0 content-length, and non-nil empty body |
3181 | + { |
3182 | + Response{ |
3183 | + StatusCode: 200, |
3184 | + ProtoMajor: 1, |
3185 | + ProtoMinor: 1, |
3186 | + Request: dummyReq11("GET"), |
3187 | + Header: http.Header{}, |
3188 | + Body: ioutil.NopCloser(strings.NewReader("")), |
3189 | + ContentLength: 0, |
3190 | + Close: false, |
3191 | + }, |
3192 | + "HTTP/1.1 200 OK\r\n" + |
3193 | + "Content-Length: 0\r\n" + |
3194 | + "\r\n", |
3195 | + }, |
3196 | + // HTTP/1.1 response 0 content-length, and non-nil non-empty body |
3197 | + { |
3198 | + Response{ |
3199 | + StatusCode: 200, |
3200 | + ProtoMajor: 1, |
3201 | + ProtoMinor: 1, |
3202 | + Request: dummyReq11("GET"), |
3203 | + Header: http.Header{}, |
3204 | + Body: ioutil.NopCloser(strings.NewReader("foo")), |
3205 | + ContentLength: 0, |
3206 | + Close: false, |
3207 | + }, |
3208 | + "HTTP/1.1 200 OK\r\n" + |
3209 | + "Connection: close\r\n" + |
3210 | + "\r\nfoo", |
3211 | + }, |
3212 | // HTTP/1.1, chunked coding; empty trailer; close |
3213 | { |
3214 | Response{ |
3215 | @@ -92,6 +192,22 @@ |
3216 | "Foo: Bar Baz\r\n" + |
3217 | "\r\n", |
3218 | }, |
3219 | + |
3220 | + // Want a single Content-Length header. Fixing issue 8180 where |
3221 | + // there were two. |
3222 | + { |
3223 | + Response{ |
3224 | + StatusCode: http.StatusOK, |
3225 | + ProtoMajor: 1, |
3226 | + ProtoMinor: 1, |
3227 | + Request: &Request{Method: "POST"}, |
3228 | + Header: http.Header{}, |
3229 | + ContentLength: 0, |
3230 | + TransferEncoding: nil, |
3231 | + Body: nil, |
3232 | + }, |
3233 | + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", |
3234 | + }, |
3235 | } |
3236 | |
3237 | for i := range respWriteTests { |
3238 | |
3239 | === modified file 'http13client/server.go' |
3240 | --- http13client/server.go 2014-03-19 23:13:58 +0000 |
3241 | +++ http13client/server.go 2014-06-20 13:24:39 +0000 |
3242 | @@ -10,21 +10,27 @@ |
3243 | "io/ioutil" |
3244 | "log" |
3245 | "net" |
3246 | - "strings" |
3247 | "sync" |
3248 | ) |
3249 | |
3250 | +type eofReaderWithWriteTo struct{} |
3251 | + |
3252 | +func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil } |
3253 | +func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF } |
3254 | + |
3255 | // eofReader is a non-nil io.ReadCloser that always returns EOF. |
3256 | -// It embeds a *strings.Reader so it still has a WriteTo method |
3257 | -// and io.Copy won't need a buffer. |
3258 | +// It has a WriteTo method so io.Copy won't need a buffer. |
3259 | var eofReader = &struct { |
3260 | - *strings.Reader |
3261 | + eofReaderWithWriteTo |
3262 | io.Closer |
3263 | }{ |
3264 | - strings.NewReader(""), |
3265 | + eofReaderWithWriteTo{}, |
3266 | ioutil.NopCloser(nil), |
3267 | } |
3268 | |
3269 | +// Verify that an io.Copy from an eofReader won't require a buffer. |
3270 | +var _ io.WriterTo = eofReader |
3271 | + |
3272 | // loggingConn is used for debugging. |
3273 | type loggingConn struct { |
3274 | name string |
3275 | |
3276 | === modified file 'http13client/transfer.go' |
3277 | --- http13client/transfer.go 2014-03-19 23:13:58 +0000 |
3278 | +++ http13client/transfer.go 2014-06-20 13:24:39 +0000 |
3279 | @@ -13,6 +13,7 @@ |
3280 | "io/ioutil" |
3281 | "net/http" |
3282 | "net/textproto" |
3283 | + "sort" |
3284 | "strconv" |
3285 | "strings" |
3286 | "sync" |
3287 | @@ -144,11 +145,10 @@ |
3288 | return false |
3289 | } |
3290 | |
3291 | -func (t *transferWriter) WriteHeader(w io.Writer) (err error) { |
3292 | +func (t *transferWriter) WriteHeader(w io.Writer) error { |
3293 | if t.Close { |
3294 | - _, err = io.WriteString(w, "Connection: close\r\n") |
3295 | - if err != nil { |
3296 | - return |
3297 | + if _, err := io.WriteString(w, "Connection: close\r\n"); err != nil { |
3298 | + return err |
3299 | } |
3300 | } |
3301 | |
3302 | @@ -156,43 +156,44 @@ |
3303 | // function of the sanitized field triple (Body, ContentLength, |
3304 | // TransferEncoding) |
3305 | if t.shouldSendContentLength() { |
3306 | - io.WriteString(w, "Content-Length: ") |
3307 | - _, err = io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n") |
3308 | - if err != nil { |
3309 | - return |
3310 | + if _, err := io.WriteString(w, "Content-Length: "); err != nil { |
3311 | + return err |
3312 | + } |
3313 | + if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil { |
3314 | + return err |
3315 | } |
3316 | } else if chunked(t.TransferEncoding) { |
3317 | - _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n") |
3318 | - if err != nil { |
3319 | - return |
3320 | + if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil { |
3321 | + return err |
3322 | } |
3323 | } |
3324 | |
3325 | // Write Trailer header |
3326 | if t.Trailer != nil { |
3327 | - // TODO: At some point, there should be a generic mechanism for |
3328 | - // writing long headers, using HTTP line splitting |
3329 | - io.WriteString(w, "Trailer: ") |
3330 | - needComma := false |
3331 | + keys := make([]string, 0, len(t.Trailer)) |
3332 | for k := range t.Trailer { |
3333 | k = http.CanonicalHeaderKey(k) |
3334 | switch k { |
3335 | case "Transfer-Encoding", "Trailer", "Content-Length": |
3336 | return &badStringError{"invalid Trailer key", k} |
3337 | } |
3338 | - if needComma { |
3339 | - io.WriteString(w, ",") |
3340 | + keys = append(keys, k) |
3341 | + } |
3342 | + if len(keys) > 0 { |
3343 | + sort.Strings(keys) |
3344 | + // TODO: could do better allocation-wise here, but trailers are rare, |
3345 | + // so being lazy for now. |
3346 | + if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil { |
3347 | + return err |
3348 | } |
3349 | - io.WriteString(w, k) |
3350 | - needComma = true |
3351 | } |
3352 | - _, err = io.WriteString(w, "\r\n") |
3353 | } |
3354 | |
3355 | - return |
3356 | + return nil |
3357 | } |
3358 | |
3359 | -func (t *transferWriter) WriteBody(w io.Writer) (err error) { |
3360 | +func (t *transferWriter) WriteBody(w io.Writer) error { |
3361 | + var err error |
3362 | var ncopy int64 |
3363 | |
3364 | // Write body |
3365 | @@ -229,11 +230,16 @@ |
3366 | |
3367 | // TODO(petar): Place trailer writer code here. |
3368 | if chunked(t.TransferEncoding) { |
3369 | + // Write Trailer header |
3370 | + if t.Trailer != nil { |
3371 | + if err := t.Trailer.Write(w); err != nil { |
3372 | + return err |
3373 | + } |
3374 | + } |
3375 | // Last chunk, empty trailer |
3376 | _, err = io.WriteString(w, "\r\n") |
3377 | } |
3378 | - |
3379 | - return |
3380 | + return err |
3381 | } |
3382 | |
3383 | type transferReader struct { |
3384 | @@ -265,6 +271,22 @@ |
3385 | return true |
3386 | } |
3387 | |
3388 | +var ( |
3389 | + suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"} |
3390 | + suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"} |
3391 | +) |
3392 | + |
3393 | +func suppressedHeaders(status int) []string { |
3394 | + switch { |
3395 | + case status == 304: |
3396 | + // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" |
3397 | + return suppressedHeaders304 |
3398 | + case !bodyAllowedForStatus(status): |
3399 | + return suppressedHeadersNoBody |
3400 | + } |
3401 | + return nil |
3402 | +} |
3403 | + |
3404 | // msg is *Request or *Response. |
3405 | func readTransfer(msg interface{}, r *bufio.Reader) (err error) { |
3406 | t := &transferReader{RequestMethod: "GET"} |
3407 | @@ -511,7 +533,7 @@ |
3408 | case "Transfer-Encoding", "Trailer", "Content-Length": |
3409 | return nil, &badStringError{"bad trailer key", key} |
3410 | } |
3411 | - trailer.Del(key) |
3412 | + trailer[key] = nil |
3413 | } |
3414 | if len(trailer) == 0 { |
3415 | return nil, nil |
3416 | @@ -643,13 +665,23 @@ |
3417 | } |
3418 | switch rr := b.hdr.(type) { |
3419 | case *Request: |
3420 | - rr.Trailer = http.Header(hdr) |
3421 | + mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
3422 | case *Response: |
3423 | - rr.Trailer = http.Header(hdr) |
3424 | + mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
3425 | } |
3426 | return nil |
3427 | } |
3428 | |
3429 | +func mergeSetHeader(dst *http.Header, src http.Header) { |
3430 | + if *dst == nil { |
3431 | + *dst = src |
3432 | + return |
3433 | + } |
3434 | + for k, vv := range src { |
3435 | + (*dst)[k] = vv |
3436 | + } |
3437 | +} |
3438 | + |
3439 | func (b *body) Close() error { |
3440 | b.mu.Lock() |
3441 | defer b.mu.Unlock() |
3442 | |
3443 | === modified file 'http13client/transport.go' |
3444 | --- http13client/transport.go 2014-03-20 09:26:28 +0000 |
3445 | +++ http13client/transport.go 2014-06-20 13:24:39 +0000 |
3446 | @@ -110,6 +110,9 @@ |
3447 | // An error is returned if the proxy environment is invalid. |
3448 | // A nil URL and nil error are returned if no proxy is defined in the |
3449 | // environment, or a proxy should not be used for the given request. |
3450 | +// |
3451 | +// As a special case, if req.URL.Host is "localhost" (with or without |
3452 | +// a port number), then a nil URL and nil error will be returned. |
3453 | func ProxyFromEnvironment(req *Request) (*url.URL, error) { |
3454 | proxy := httpProxyEnv.Get() |
3455 | if proxy == "" { |
3456 | @@ -161,9 +164,11 @@ |
3457 | // and redirects), see Get, Post, and the Client type. |
3458 | func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { |
3459 | if req.URL == nil { |
3460 | + req.closeBody() |
3461 | return nil, errors.New("http: nil Request.URL") |
3462 | } |
3463 | if req.Header == nil { |
3464 | + req.closeBody() |
3465 | return nil, errors.New("http: nil Request.Header") |
3466 | } |
3467 | if req.URL.Scheme != "http" && req.URL.Scheme != "https" { |
3468 | @@ -174,16 +179,19 @@ |
3469 | } |
3470 | t.altMu.RUnlock() |
3471 | if rt == nil { |
3472 | + req.closeBody() |
3473 | return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} |
3474 | } |
3475 | return rt.RoundTrip(req) |
3476 | } |
3477 | if req.URL.Host == "" { |
3478 | + req.closeBody() |
3479 | return nil, errors.New("http: no Host in request URL") |
3480 | } |
3481 | treq := &transportRequest{Request: req} |
3482 | cm, err := t.connectMethodForRequest(treq) |
3483 | if err != nil { |
3484 | + req.closeBody() |
3485 | return nil, err |
3486 | } |
3487 | |
3488 | @@ -194,6 +202,7 @@ |
3489 | pconn, err := t.getConn(req, cm) |
3490 | if err != nil { |
3491 | t.setReqCanceler(req, nil) |
3492 | + req.closeBody() |
3493 | return nil, err |
3494 | } |
3495 | |
3496 | @@ -231,9 +240,6 @@ |
3497 | t.idleConn = nil |
3498 | t.idleConnCh = nil |
3499 | t.idleMu.Unlock() |
3500 | - if m == nil { |
3501 | - return |
3502 | - } |
3503 | for _, conns := range m { |
3504 | for _, pconn := range conns { |
3505 | pconn.close() |
3506 | @@ -499,12 +505,13 @@ |
3507 | pa := cm.proxyAuth() |
3508 | |
3509 | pconn := &persistConn{ |
3510 | - t: t, |
3511 | - cacheKey: cm.key(), |
3512 | - conn: conn, |
3513 | - reqch: make(chan requestAndChan, 50), |
3514 | - writech: make(chan writeRequest, 50), |
3515 | - closech: make(chan struct{}), |
3516 | + t: t, |
3517 | + cacheKey: cm.key(), |
3518 | + conn: conn, |
3519 | + reqch: make(chan requestAndChan, 1), |
3520 | + writech: make(chan writeRequest, 1), |
3521 | + closech: make(chan struct{}), |
3522 | + writeErrCh: make(chan error, 1), |
3523 | } |
3524 | |
3525 | switch { |
3526 | @@ -589,7 +596,7 @@ |
3527 | pconn.conn = tlsConn |
3528 | } |
3529 | |
3530 | - pconn.br = bufio.NewReader(pconn.conn) |
3531 | + pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF}) |
3532 | pconn.bw = bufio.NewWriter(pconn.conn) |
3533 | go pconn.readLoop() |
3534 | go pconn.writeLoop() |
3535 | @@ -722,16 +729,22 @@ |
3536 | cacheKey connectMethodKey |
3537 | conn net.Conn |
3538 | tlsState *tls.ConnectionState |
3539 | - closed bool // whether conn has been closed |
3540 | br *bufio.Reader // from conn |
3541 | + sawEOF bool // whether we've seen EOF from conn; owned by readLoop |
3542 | bw *bufio.Writer // to conn |
3543 | reqch chan requestAndChan // written by roundTrip; read by readLoop |
3544 | writech chan writeRequest // written by roundTrip; read by writeLoop |
3545 | - closech chan struct{} // broadcast close when readLoop (TCP connection) closes |
3546 | + closech chan struct{} // closed when conn closed |
3547 | isProxy bool |
3548 | + // writeErrCh passes the request write error (usually nil) |
3549 | + // from the writeLoop goroutine to the readLoop which passes |
3550 | + // it off to the res.Body reader, which then uses it to decide |
3551 | + // whether or not a connection can be reused. Issue 7569. |
3552 | + writeErrCh chan error |
3553 | |
3554 | - lk sync.Mutex // guards following 3 fields |
3555 | + lk sync.Mutex // guards following fields |
3556 | numExpectedResponses int |
3557 | + closed bool // whether conn has been closed |
3558 | broken bool // an error has happened on this connection; marked broken so it's not reused. |
3559 | // mutateHeaderFunc is an optional func to modify extra |
3560 | // headers on each outbound request before it's written. (the |
3561 | @@ -739,6 +752,7 @@ |
3562 | mutateHeaderFunc func(http.Header) |
3563 | } |
3564 | |
3565 | +// isBroken reports whether this connection is in a known broken state. |
3566 | func (pc *persistConn) isBroken() bool { |
3567 | pc.lk.Lock() |
3568 | b := pc.broken |
3569 | @@ -763,7 +777,6 @@ |
3570 | } |
3571 | |
3572 | func (pc *persistConn) readLoop() { |
3573 | - defer close(pc.closech) |
3574 | alive := true |
3575 | |
3576 | for alive { |
3577 | @@ -771,12 +784,14 @@ |
3578 | |
3579 | pc.lk.Lock() |
3580 | if pc.numExpectedResponses == 0 { |
3581 | - pc.closeLocked() |
3582 | + if !pc.closed { |
3583 | + pc.closeLocked() |
3584 | + if len(pb) > 0 { |
3585 | + log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", |
3586 | + string(pb), err) |
3587 | + } |
3588 | + } |
3589 | pc.lk.Unlock() |
3590 | - if len(pb) > 0 { |
3591 | - log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", |
3592 | - string(pb), err) |
3593 | - } |
3594 | return |
3595 | } |
3596 | pc.lk.Unlock() |
3597 | @@ -809,13 +824,7 @@ |
3598 | resp.Header.Del("Content-Encoding") |
3599 | resp.Header.Del("Content-Length") |
3600 | resp.ContentLength = -1 |
3601 | - gzReader, zerr := gzip.NewReader(resp.Body) |
3602 | - if zerr != nil { |
3603 | - pc.close() |
3604 | - err = zerr |
3605 | - } else { |
3606 | - resp.Body = &readerAndCloser{gzReader, resp.Body} |
3607 | - } |
3608 | + resp.Body = &gzipReader{body: resp.Body} |
3609 | } |
3610 | resp.Body = &bodyEOFSignal{body: resp.Body} |
3611 | } |
3612 | @@ -838,24 +847,18 @@ |
3613 | return nil |
3614 | } |
3615 | resp.Body.(*bodyEOFSignal).fn = func(err error) { |
3616 | - alive1 := alive |
3617 | - if err != nil { |
3618 | - alive1 = false |
3619 | - } |
3620 | - if alive1 && !pc.t.putIdleConn(pc) { |
3621 | - alive1 = false |
3622 | - } |
3623 | - if !alive1 || pc.isBroken() { |
3624 | - pc.close() |
3625 | - } |
3626 | - waitForBodyRead <- alive1 |
3627 | + waitForBodyRead <- alive && |
3628 | + err == nil && |
3629 | + !pc.sawEOF && |
3630 | + pc.wroteRequest() && |
3631 | + pc.t.putIdleConn(pc) |
3632 | } |
3633 | } |
3634 | |
3635 | if alive && !hasBody { |
3636 | - if !pc.t.putIdleConn(pc) { |
3637 | - alive = false |
3638 | - } |
3639 | + alive = !pc.sawEOF && |
3640 | + pc.wroteRequest() && |
3641 | + pc.t.putIdleConn(pc) |
3642 | } |
3643 | |
3644 | rc.ch <- responseAndError{resp, err} |
3645 | @@ -863,7 +866,11 @@ |
3646 | // Wait for the just-returned response body to be fully consumed |
3647 | // before we race and peek on the underlying bufio reader. |
3648 | if waitForBodyRead != nil { |
3649 | - alive = <-waitForBodyRead |
3650 | + select { |
3651 | + case alive = <-waitForBodyRead: |
3652 | + case <-pc.closech: |
3653 | + alive = false |
3654 | + } |
3655 | } |
3656 | |
3657 | pc.t.setReqCanceler(rc.req, nil) |
3658 | @@ -888,14 +895,44 @@ |
3659 | } |
3660 | if err != nil { |
3661 | pc.markBroken() |
3662 | + wr.req.Request.closeBody() |
3663 | } |
3664 | - wr.ch <- err |
3665 | + pc.writeErrCh <- err // to the body reader, which might recycle us |
3666 | + wr.ch <- err // to the roundTrip function |
3667 | case <-pc.closech: |
3668 | return |
3669 | } |
3670 | } |
3671 | } |
3672 | |
3673 | +// wroteRequest is a check before recycling a connection that the previous write |
3674 | +// (from writeLoop above) happened and was successful. |
3675 | +func (pc *persistConn) wroteRequest() bool { |
3676 | + select { |
3677 | + case err := <-pc.writeErrCh: |
3678 | + // Common case: the write happened well before the response, so |
3679 | + // avoid creating a timer. |
3680 | + return err == nil |
3681 | + default: |
3682 | + // Rare case: the request was written in writeLoop above but |
3683 | + // before it could send to pc.writeErrCh, the reader read it |
3684 | + // all, processed it, and called us here. In this case, give the |
3685 | + // write goroutine a bit of time to finish its send. |
3686 | + // |
3687 | + // Less rare case: We also get here in the legitimate case of |
3688 | + // Issue 7569, where the writer is still writing (or stalled), |
3689 | + // but the server has already replied. In this case, we don't |
3690 | + // want to wait too long, and we want to return false so this |
3691 | + // connection isn't re-used. |
3692 | + select { |
3693 | + case err := <-pc.writeErrCh: |
3694 | + return err == nil |
3695 | + case <-time.After(50 * time.Millisecond): |
3696 | + return false |
3697 | + } |
3698 | + } |
3699 | +} |
3700 | + |
3701 | type responseAndError struct { |
3702 | res *Response |
3703 | err error |
3704 | @@ -1043,6 +1080,7 @@ |
3705 | if !pc.closed { |
3706 | pc.conn.Close() |
3707 | pc.closed = true |
3708 | + close(pc.closech) |
3709 | } |
3710 | pc.mutateHeaderFunc = nil |
3711 | } |
3712 | @@ -1125,6 +1163,27 @@ |
3713 | es.fn = nil |
3714 | } |
3715 | |
3716 | +// gzipReader wraps a response body so it can lazily |
3717 | +// call gzip.NewReader on the first call to Read |
3718 | +type gzipReader struct { |
3719 | + body io.ReadCloser // underlying Response.Body |
3720 | + zr io.Reader // lazily-initialized gzip reader |
3721 | +} |
3722 | + |
3723 | +func (gz *gzipReader) Read(p []byte) (n int, err error) { |
3724 | + if gz.zr == nil { |
3725 | + gz.zr, err = gzip.NewReader(gz.body) |
3726 | + if err != nil { |
3727 | + return 0, err |
3728 | + } |
3729 | + } |
3730 | + return gz.zr.Read(p) |
3731 | +} |
3732 | + |
3733 | +func (gz *gzipReader) Close() error { |
3734 | + return gz.body.Close() |
3735 | +} |
3736 | + |
3737 | type readerAndCloser struct { |
3738 | io.Reader |
3739 | io.Closer |
3740 | @@ -1135,3 +1194,16 @@ |
3741 | func (tlsHandshakeTimeoutError) Timeout() bool { return true } |
3742 | func (tlsHandshakeTimeoutError) Temporary() bool { return true } |
3743 | func (tlsHandshakeTimeoutError) Error() string { return "net/http: TLS handshake timeout" } |
3744 | + |
3745 | +type noteEOFReader struct { |
3746 | + r io.Reader |
3747 | + sawEOF *bool |
3748 | +} |
3749 | + |
3750 | +func (nr noteEOFReader) Read(p []byte) (n int, err error) { |
3751 | + n, err = nr.r.Read(p) |
3752 | + if err == io.EOF { |
3753 | + *nr.sawEOF = true |
3754 | + } |
3755 | + return |
3756 | +} |
3757 | |
3758 | === modified file 'http13client/transport_test.go' |
3759 | --- http13client/transport_test.go 2014-03-20 09:26:28 +0000 |
3760 | +++ http13client/transport_test.go 2014-06-20 13:24:39 +0000 |
3761 | @@ -11,6 +11,7 @@ |
3762 | "bytes" |
3763 | "compress/gzip" |
3764 | "crypto/rand" |
3765 | + "crypto/tls" |
3766 | "errors" |
3767 | "fmt" |
3768 | "io" |
3769 | @@ -56,21 +57,21 @@ |
3770 | // been closed. |
3771 | type testConnSet struct { |
3772 | t *testing.T |
3773 | + mu sync.Mutex // guards closed and list |
3774 | closed map[net.Conn]bool |
3775 | list []net.Conn // in order created |
3776 | - mutex sync.Mutex |
3777 | } |
3778 | |
3779 | func (tcs *testConnSet) insert(c net.Conn) { |
3780 | - tcs.mutex.Lock() |
3781 | - defer tcs.mutex.Unlock() |
3782 | + tcs.mu.Lock() |
3783 | + defer tcs.mu.Unlock() |
3784 | tcs.closed[c] = false |
3785 | tcs.list = append(tcs.list, c) |
3786 | } |
3787 | |
3788 | func (tcs *testConnSet) remove(c net.Conn) { |
3789 | - tcs.mutex.Lock() |
3790 | - defer tcs.mutex.Unlock() |
3791 | + tcs.mu.Lock() |
3792 | + defer tcs.mu.Unlock() |
3793 | tcs.closed[c] = true |
3794 | } |
3795 | |
3796 | @@ -93,11 +94,19 @@ |
3797 | } |
3798 | |
3799 | func (tcs *testConnSet) check(t *testing.T) { |
3800 | - tcs.mutex.Lock() |
3801 | - defer tcs.mutex.Unlock() |
3802 | - |
3803 | - for i, c := range tcs.list { |
3804 | - if !tcs.closed[c] { |
3805 | + tcs.mu.Lock() |
3806 | + defer tcs.mu.Unlock() |
3807 | + for i := 4; i >= 0; i-- { |
3808 | + for i, c := range tcs.list { |
3809 | + if tcs.closed[c] { |
3810 | + continue |
3811 | + } |
3812 | + if i != 0 { |
3813 | + tcs.mu.Unlock() |
3814 | + time.Sleep(50 * time.Millisecond) |
3815 | + tcs.mu.Lock() |
3816 | + continue |
3817 | + } |
3818 | t.Errorf("TCP connection #%d, %p (of %d total) was not closed", i+1, c, len(tcs.list)) |
3819 | } |
3820 | } |
3821 | @@ -794,6 +803,33 @@ |
3822 | } |
3823 | } |
3824 | |
3825 | +// golang.org/issue/7750: request fails when server replies with |
3826 | +// a short gzip body |
3827 | +func TestTransportGzipShort(t *testing.T) { |
3828 | + defer afterTest(t) |
3829 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3830 | + w.Header().Set("Content-Encoding", "gzip") |
3831 | + w.Write([]byte{0x1f, 0x8b}) |
3832 | + })) |
3833 | + defer ts.Close() |
3834 | + |
3835 | + tr := &Transport{} |
3836 | + defer tr.CloseIdleConnections() |
3837 | + c := &Client{Transport: tr} |
3838 | + res, err := c.Get(ts.URL) |
3839 | + if err != nil { |
3840 | + t.Fatal(err) |
3841 | + } |
3842 | + defer res.Body.Close() |
3843 | + _, err = ioutil.ReadAll(res.Body) |
3844 | + if err == nil { |
3845 | + t.Fatal("Expect an error from reading a body.") |
3846 | + } |
3847 | + if err != io.ErrUnexpectedEOF { |
3848 | + t.Errorf("ReadAll error = %v; want io.ErrUnexpectedEOF", err) |
3849 | + } |
3850 | +} |
3851 | + |
3852 | // tests that persistent goroutine connections shut down when no longer desired. |
3853 | func TestTransportPersistConnLeak(t *testing.T) { |
3854 | if runtime.GOOS == "plan9" { |
3855 | @@ -1214,9 +1250,13 @@ |
3856 | if testing.Short() { |
3857 | t.Skip("skipping timeout test in -short mode") |
3858 | } |
3859 | + inHandler := make(chan bool, 1) |
3860 | mux := http.NewServeMux() |
3861 | - mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {}) |
3862 | + mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) { |
3863 | + inHandler <- true |
3864 | + }) |
3865 | mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { |
3866 | + inHandler <- true |
3867 | time.Sleep(2 * time.Second) |
3868 | }) |
3869 | ts := httptest.NewServer(mux) |
3870 | @@ -1239,6 +1279,12 @@ |
3871 | } |
3872 | for i, tt := range tests { |
3873 | res, err := c.Get(ts.URL + tt.path) |
3874 | + select { |
3875 | + case <-inHandler: |
3876 | + case <-time.After(5 * time.Second): |
3877 | + t.Errorf("never entered handler for test index %d, %s", i, tt.path) |
3878 | + continue |
3879 | + } |
3880 | if err != nil { |
3881 | uerr, ok := err.(*url.Error) |
3882 | if !ok { |
3883 | @@ -1366,7 +1412,6 @@ |
3884 | case <-gotres: |
3885 | case <-time.After(5 * time.Second): |
3886 | panic("hang. events are: " + logbuf.String()) |
3887 | - t.Fatal("timeout; cancel didn't work?") |
3888 | } |
3889 | |
3890 | got := logbuf.String() |
3891 | @@ -1508,8 +1553,10 @@ |
3892 | dialGate := make(chan bool, 1) |
3893 | tr := &Transport{ |
3894 | Dial: func(n, addr string) (net.Conn, error) { |
3895 | - <-dialGate |
3896 | - return net.Dial(n, addr) |
3897 | + if <-dialGate { |
3898 | + return net.Dial(n, addr) |
3899 | + } |
3900 | + return nil, errors.New("manually closed") |
3901 | }, |
3902 | DisableKeepAlives: false, |
3903 | } |
3904 | @@ -1544,7 +1591,7 @@ |
3905 | t.Fatalf("/foo came from conn %q; /bar came from %q instead", fooAddr, barAddr) |
3906 | } |
3907 | barRes.Body.Close() |
3908 | - dialGate <- true |
3909 | + dialGate <- false |
3910 | } |
3911 | |
3912 | // Issue 2184 |
3913 | @@ -1823,10 +1870,10 @@ |
3914 | return |
3915 | } |
3916 | if !ne.Timeout() { |
3917 | - t.Error("expected timeout error; got %v", err) |
3918 | + t.Errorf("expected timeout error; got %v", err) |
3919 | } |
3920 | if !strings.Contains(err.Error(), "handshake timeout") { |
3921 | - t.Error("expected 'handshake timeout' in error; got %v", err) |
3922 | + t.Errorf("expected 'handshake timeout' in error; got %v", err) |
3923 | } |
3924 | }() |
3925 | select { |
3926 | @@ -1836,6 +1883,238 @@ |
3927 | } |
3928 | } |
3929 | |
3930 | +// Trying to repro golang.org/issue/3514 |
3931 | +func TestTLSServerClosesConnection(t *testing.T) { |
3932 | + defer afterTest(t) |
3933 | + if runtime.GOOS == "windows" { |
3934 | + t.Skip("skipping flaky test on Windows; golang.org/issue/7634") |
3935 | + } |
3936 | + closedc := make(chan bool, 1) |
3937 | + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3938 | + if strings.Contains(r.URL.Path, "/keep-alive-then-die") { |
3939 | + conn, _, _ := w.(http.Hijacker).Hijack() |
3940 | + conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo")) |
3941 | + conn.Close() |
3942 | + closedc <- true |
3943 | + return |
3944 | + } |
3945 | + fmt.Fprintf(w, "hello") |
3946 | + })) |
3947 | + defer ts.Close() |
3948 | + tr := &Transport{ |
3949 | + TLSClientConfig: &tls.Config{ |
3950 | + InsecureSkipVerify: true, |
3951 | + }, |
3952 | + } |
3953 | + defer tr.CloseIdleConnections() |
3954 | + client := &Client{Transport: tr} |
3955 | + |
3956 | + var nSuccess = 0 |
3957 | + var errs []error |
3958 | + const trials = 20 |
3959 | + for i := 0; i < trials; i++ { |
3960 | + tr.CloseIdleConnections() |
3961 | + res, err := client.Get(ts.URL + "/keep-alive-then-die") |
3962 | + if err != nil { |
3963 | + t.Fatal(err) |
3964 | + } |
3965 | + <-closedc |
3966 | + slurp, err := ioutil.ReadAll(res.Body) |
3967 | + if err != nil { |
3968 | + t.Fatal(err) |
3969 | + } |
3970 | + if string(slurp) != "foo" { |
3971 | + t.Errorf("Got %q, want foo", slurp) |
3972 | + } |
3973 | + |
3974 | + // Now try again and see if we successfully |
3975 | + // pick a new connection. |
3976 | + res, err = client.Get(ts.URL + "/") |
3977 | + if err != nil { |
3978 | + errs = append(errs, err) |
3979 | + continue |
3980 | + } |
3981 | + slurp, err = ioutil.ReadAll(res.Body) |
3982 | + if err != nil { |
3983 | + errs = append(errs, err) |
3984 | + continue |
3985 | + } |
3986 | + nSuccess++ |
3987 | + } |
3988 | + if nSuccess > 0 { |
3989 | + t.Logf("successes = %d of %d", nSuccess, trials) |
3990 | + } else { |
3991 | + t.Errorf("All runs failed:") |
3992 | + } |
3993 | + for _, err := range errs { |
3994 | + t.Logf(" err: %v", err) |
3995 | + } |
3996 | +} |
3997 | + |
3998 | +// byteFromChanReader is an io.Reader that reads a single byte at a |
3999 | +// time from the channel. When the channel is closed, the reader |
4000 | +// returns io.EOF. |
4001 | +type byteFromChanReader chan byte |
4002 | + |
4003 | +func (c byteFromChanReader) Read(p []byte) (n int, err error) { |
4004 | + if len(p) == 0 { |
4005 | + return |
4006 | + } |
4007 | + b, ok := <-c |
4008 | + if !ok { |
4009 | + return 0, io.EOF |
4010 | + } |
4011 | + p[0] = b |
4012 | + return 1, nil |
4013 | +} |
4014 | + |
4015 | +// Verifies that the Transport doesn't reuse a connection in the case |
4016 | +// where the server replies before the request has been fully |
4017 | +// written. We still honor that reply (see TestIssue3595), but don't |
4018 | +// send future requests on the connection because it's then in a |
4019 | +// questionable state. |
4020 | +// golang.org/issue/7569 |
4021 | +func TestTransportNoReuseAfterEarlyResponse(t *testing.T) { |
4022 | + defer afterTest(t) |
4023 | + var sconn struct { |
4024 | + sync.Mutex |
4025 | + c net.Conn |
4026 | + } |
4027 | + var getOkay bool |
4028 | + closeConn := func() { |
4029 | + sconn.Lock() |
4030 | + defer sconn.Unlock() |
4031 | + if sconn.c != nil { |
4032 | + sconn.c.Close() |
4033 | + sconn.c = nil |
4034 | + if !getOkay { |
4035 | + t.Logf("Closed server connection") |
4036 | + } |
4037 | + } |
4038 | + } |
4039 | + defer closeConn() |
4040 | + |
4041 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
4042 | + if r.Method == "GET" { |
4043 | + io.WriteString(w, "bar") |
4044 | + return |
4045 | + } |
4046 | + conn, _, _ := w.(http.Hijacker).Hijack() |
4047 | + sconn.Lock() |
4048 | + sconn.c = conn |
4049 | + sconn.Unlock() |
4050 | + conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo")) // keep-alive |
4051 | + go io.Copy(ioutil.Discard, conn) |
4052 | + })) |
4053 | + defer ts.Close() |
4054 | + tr := &Transport{} |
4055 | + defer tr.CloseIdleConnections() |
4056 | + client := &Client{Transport: tr} |
4057 | + |
4058 | + const bodySize = 256 << 10 |
4059 | + finalBit := make(byteFromChanReader, 1) |
4060 | + req, _ := NewRequest("POST", ts.URL, io.MultiReader(io.LimitReader(neverEnding('x'), bodySize-1), finalBit)) |
4061 | + req.ContentLength = bodySize |
4062 | + res, err := client.Do(req) |
4063 | + if err := wantBody(res, err, "foo"); err != nil { |
4064 | + t.Errorf("POST response: %v", err) |
4065 | + } |
4066 | + donec := make(chan bool) |
4067 | + go func() { |
4068 | + defer close(donec) |
4069 | + res, err = client.Get(ts.URL) |
4070 | + if err := wantBody(res, err, "bar"); err != nil { |
4071 | + t.Errorf("GET response: %v", err) |
4072 | + return |
4073 | + } |
4074 | + getOkay = true // suppress test noise |
4075 | + }() |
4076 | + time.AfterFunc(5*time.Second, closeConn) |
4077 | + select { |
4078 | + case <-donec: |
4079 | + finalBit <- 'x' // unblock the writeloop of the first Post |
4080 | + close(finalBit) |
4081 | + case <-time.After(7 * time.Second): |
4082 | + t.Fatal("timeout waiting for GET request to finish") |
4083 | + } |
4084 | +} |
4085 | + |
4086 | +type errorReader struct { |
4087 | + err error |
4088 | +} |
4089 | + |
4090 | +func (e errorReader) Read(p []byte) (int, error) { return 0, e.err } |
4091 | + |
4092 | +type closerFunc func() error |
4093 | + |
4094 | +func (f closerFunc) Close() error { return f() } |
4095 | + |
4096 | +// Issue 6981 |
4097 | +func TestTransportClosesBodyOnError(t *testing.T) { |
4098 | + if runtime.GOOS == "plan9" { |
4099 | + t.Skip("skipping test; see http://golang.org/issue/7782") |
4100 | + } |
4101 | + defer afterTest(t) |
4102 | + readBody := make(chan error, 1) |
4103 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
4104 | + _, err := ioutil.ReadAll(r.Body) |
4105 | + readBody <- err |
4106 | + })) |
4107 | + defer ts.Close() |
4108 | + fakeErr := errors.New("fake error") |
4109 | + didClose := make(chan bool, 1) |
4110 | + req, _ := NewRequest("POST", ts.URL, struct { |
4111 | + io.Reader |
4112 | + io.Closer |
4113 | + }{ |
4114 | + io.MultiReader(io.LimitReader(neverEnding('x'), 1<<20), errorReader{fakeErr}), |
4115 | + closerFunc(func() error { |
4116 | + select { |
4117 | + case didClose <- true: |
4118 | + default: |
4119 | + } |
4120 | + return nil |
4121 | + }), |
4122 | + }) |
4123 | + res, err := DefaultClient.Do(req) |
4124 | + if res != nil { |
4125 | + defer res.Body.Close() |
4126 | + } |
4127 | + if err == nil || !strings.Contains(err.Error(), fakeErr.Error()) { |
4128 | + t.Fatalf("Do error = %v; want something containing %q", err, fakeErr.Error()) |
4129 | + } |
4130 | + select { |
4131 | + case err := <-readBody: |
4132 | + if err == nil { |
4133 | + t.Errorf("Unexpected success reading request body from handler; want 'unexpected EOF reading trailer'") |
4134 | + } |
4135 | + case <-time.After(5 * time.Second): |
4136 | + t.Error("timeout waiting for server handler to complete") |
4137 | + } |
4138 | + select { |
4139 | + case <-didClose: |
4140 | + default: |
4141 | + t.Errorf("didn't see Body.Close") |
4142 | + } |
4143 | +} |
4144 | + |
4145 | +func wantBody(res *Response, err error, want string) error { |
4146 | + if err != nil { |
4147 | + return err |
4148 | + } |
4149 | + slurp, err := ioutil.ReadAll(res.Body) |
4150 | + if err != nil { |
4151 | + return fmt.Errorf("error reading body: %v", err) |
4152 | + } |
4153 | + if string(slurp) != want { |
4154 | + return fmt.Errorf("body = %q; want %q", slurp, want) |
4155 | + } |
4156 | + if err := res.Body.Close(); err != nil { |
4157 | + return fmt.Errorf("body Close = %v", err) |
4158 | + } |
4159 | + return nil |
4160 | +} |
4161 | + |
4162 | func newLocalListener(t *testing.T) net.Listener { |
4163 | ln, err := net.Listen("tcp", "127.0.0.1:0") |
4164 | if err != nil { |
LGTM.