Rebase.
[official-gcc.git] / libgo / go / net / http / requestwrite_test.go
blobdc0e204cac98c7c2d86686ff76c438604513e8dc
1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 package http
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "net/url"
14 "strings"
15 "testing"
18 type reqWriteTest struct {
19 Req Request
20 Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
22 // Any of these three may be empty to skip that test.
23 WantWrite string // Request.Write
24 WantProxy string // Request.WriteProxy
26 WantError error // wanted error from Request.Write
29 var reqWriteTests = []reqWriteTest{
30 // HTTP/1.1 => chunked coding; no body; no trailer
32 Req: Request{
33 Method: "GET",
34 URL: &url.URL{
35 Scheme: "http",
36 Host: "www.techcrunch.com",
37 Path: "/",
39 Proto: "HTTP/1.1",
40 ProtoMajor: 1,
41 ProtoMinor: 1,
42 Header: Header{
43 "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
44 "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
45 "Accept-Encoding": {"gzip,deflate"},
46 "Accept-Language": {"en-us,en;q=0.5"},
47 "Keep-Alive": {"300"},
48 "Proxy-Connection": {"keep-alive"},
49 "User-Agent": {"Fake"},
51 Body: nil,
52 Close: false,
53 Host: "www.techcrunch.com",
54 Form: map[string][]string{},
57 WantWrite: "GET / HTTP/1.1\r\n" +
58 "Host: www.techcrunch.com\r\n" +
59 "User-Agent: Fake\r\n" +
60 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
61 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
62 "Accept-Encoding: gzip,deflate\r\n" +
63 "Accept-Language: en-us,en;q=0.5\r\n" +
64 "Keep-Alive: 300\r\n" +
65 "Proxy-Connection: keep-alive\r\n\r\n",
67 WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
68 "Host: www.techcrunch.com\r\n" +
69 "User-Agent: Fake\r\n" +
70 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
71 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
72 "Accept-Encoding: gzip,deflate\r\n" +
73 "Accept-Language: en-us,en;q=0.5\r\n" +
74 "Keep-Alive: 300\r\n" +
75 "Proxy-Connection: keep-alive\r\n\r\n",
77 // HTTP/1.1 => chunked coding; body; empty trailer
79 Req: Request{
80 Method: "GET",
81 URL: &url.URL{
82 Scheme: "http",
83 Host: "www.google.com",
84 Path: "/search",
86 ProtoMajor: 1,
87 ProtoMinor: 1,
88 Header: Header{},
89 TransferEncoding: []string{"chunked"},
92 Body: []byte("abcdef"),
94 WantWrite: "GET /search HTTP/1.1\r\n" +
95 "Host: www.google.com\r\n" +
96 "User-Agent: Go 1.1 package http\r\n" +
97 "Transfer-Encoding: chunked\r\n\r\n" +
98 chunk("abcdef") + chunk(""),
100 WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
101 "Host: www.google.com\r\n" +
102 "User-Agent: Go 1.1 package http\r\n" +
103 "Transfer-Encoding: chunked\r\n\r\n" +
104 chunk("abcdef") + chunk(""),
106 // HTTP/1.1 POST => chunked coding; body; empty trailer
108 Req: Request{
109 Method: "POST",
110 URL: &url.URL{
111 Scheme: "http",
112 Host: "www.google.com",
113 Path: "/search",
115 ProtoMajor: 1,
116 ProtoMinor: 1,
117 Header: Header{},
118 Close: true,
119 TransferEncoding: []string{"chunked"},
122 Body: []byte("abcdef"),
124 WantWrite: "POST /search HTTP/1.1\r\n" +
125 "Host: www.google.com\r\n" +
126 "User-Agent: Go 1.1 package http\r\n" +
127 "Connection: close\r\n" +
128 "Transfer-Encoding: chunked\r\n\r\n" +
129 chunk("abcdef") + chunk(""),
131 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
132 "Host: www.google.com\r\n" +
133 "User-Agent: Go 1.1 package http\r\n" +
134 "Connection: close\r\n" +
135 "Transfer-Encoding: chunked\r\n\r\n" +
136 chunk("abcdef") + chunk(""),
139 // HTTP/1.1 POST with Content-Length, no chunking
141 Req: Request{
142 Method: "POST",
143 URL: &url.URL{
144 Scheme: "http",
145 Host: "www.google.com",
146 Path: "/search",
148 ProtoMajor: 1,
149 ProtoMinor: 1,
150 Header: Header{},
151 Close: true,
152 ContentLength: 6,
155 Body: []byte("abcdef"),
157 WantWrite: "POST /search HTTP/1.1\r\n" +
158 "Host: www.google.com\r\n" +
159 "User-Agent: Go 1.1 package http\r\n" +
160 "Connection: close\r\n" +
161 "Content-Length: 6\r\n" +
162 "\r\n" +
163 "abcdef",
165 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
166 "Host: www.google.com\r\n" +
167 "User-Agent: Go 1.1 package http\r\n" +
168 "Connection: close\r\n" +
169 "Content-Length: 6\r\n" +
170 "\r\n" +
171 "abcdef",
174 // HTTP/1.1 POST with Content-Length in headers
176 Req: Request{
177 Method: "POST",
178 URL: mustParseURL("http://example.com/"),
179 Host: "example.com",
180 Header: Header{
181 "Content-Length": []string{"10"}, // ignored
183 ContentLength: 6,
186 Body: []byte("abcdef"),
188 WantWrite: "POST / HTTP/1.1\r\n" +
189 "Host: example.com\r\n" +
190 "User-Agent: Go 1.1 package http\r\n" +
191 "Content-Length: 6\r\n" +
192 "\r\n" +
193 "abcdef",
195 WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
196 "Host: example.com\r\n" +
197 "User-Agent: Go 1.1 package http\r\n" +
198 "Content-Length: 6\r\n" +
199 "\r\n" +
200 "abcdef",
203 // default to HTTP/1.1
205 Req: Request{
206 Method: "GET",
207 URL: mustParseURL("/search"),
208 Host: "www.google.com",
211 WantWrite: "GET /search HTTP/1.1\r\n" +
212 "Host: www.google.com\r\n" +
213 "User-Agent: Go 1.1 package http\r\n" +
214 "\r\n",
217 // Request with a 0 ContentLength and a 0 byte body.
219 Req: Request{
220 Method: "POST",
221 URL: mustParseURL("/"),
222 Host: "example.com",
223 ProtoMajor: 1,
224 ProtoMinor: 1,
225 ContentLength: 0, // as if unset by user
228 Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
230 // RFC 2616 Section 14.13 says Content-Length should be specified
231 // unless body is prohibited by the request method.
232 // Also, nginx expects it for POST and PUT.
233 WantWrite: "POST / HTTP/1.1\r\n" +
234 "Host: example.com\r\n" +
235 "User-Agent: Go 1.1 package http\r\n" +
236 "Content-Length: 0\r\n" +
237 "\r\n",
239 WantProxy: "POST / HTTP/1.1\r\n" +
240 "Host: example.com\r\n" +
241 "User-Agent: Go 1.1 package http\r\n" +
242 "Content-Length: 0\r\n" +
243 "\r\n",
246 // Request with a 0 ContentLength and a 1 byte body.
248 Req: Request{
249 Method: "POST",
250 URL: mustParseURL("/"),
251 Host: "example.com",
252 ProtoMajor: 1,
253 ProtoMinor: 1,
254 ContentLength: 0, // as if unset by user
257 Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
259 WantWrite: "POST / HTTP/1.1\r\n" +
260 "Host: example.com\r\n" +
261 "User-Agent: Go 1.1 package http\r\n" +
262 "Transfer-Encoding: chunked\r\n\r\n" +
263 chunk("x") + chunk(""),
265 WantProxy: "POST / HTTP/1.1\r\n" +
266 "Host: example.com\r\n" +
267 "User-Agent: Go 1.1 package http\r\n" +
268 "Transfer-Encoding: chunked\r\n\r\n" +
269 chunk("x") + chunk(""),
272 // Request with a ContentLength of 10 but a 5 byte body.
274 Req: Request{
275 Method: "POST",
276 URL: mustParseURL("/"),
277 Host: "example.com",
278 ProtoMajor: 1,
279 ProtoMinor: 1,
280 ContentLength: 10, // but we're going to send only 5 bytes
282 Body: []byte("12345"),
283 WantError: errors.New("http: Request.ContentLength=10 with Body length 5"),
286 // Request with a ContentLength of 4 but an 8 byte body.
288 Req: Request{
289 Method: "POST",
290 URL: mustParseURL("/"),
291 Host: "example.com",
292 ProtoMajor: 1,
293 ProtoMinor: 1,
294 ContentLength: 4, // but we're going to try to send 8 bytes
296 Body: []byte("12345678"),
297 WantError: errors.New("http: Request.ContentLength=4 with Body length 8"),
300 // Request with a 5 ContentLength and nil body.
302 Req: Request{
303 Method: "POST",
304 URL: mustParseURL("/"),
305 Host: "example.com",
306 ProtoMajor: 1,
307 ProtoMinor: 1,
308 ContentLength: 5, // but we'll omit the body
310 WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
313 // Request with a 0 ContentLength and a body with 1 byte content and an error.
315 Req: Request{
316 Method: "POST",
317 URL: mustParseURL("/"),
318 Host: "example.com",
319 ProtoMajor: 1,
320 ProtoMinor: 1,
321 ContentLength: 0, // as if unset by user
324 Body: func() io.ReadCloser {
325 err := errors.New("Custom reader error")
326 errReader := &errorReader{err}
327 return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
330 WantError: errors.New("Custom reader error"),
333 // Request with a 0 ContentLength and a body without content and an error.
335 Req: Request{
336 Method: "POST",
337 URL: mustParseURL("/"),
338 Host: "example.com",
339 ProtoMajor: 1,
340 ProtoMinor: 1,
341 ContentLength: 0, // as if unset by user
344 Body: func() io.ReadCloser {
345 err := errors.New("Custom reader error")
346 errReader := &errorReader{err}
347 return ioutil.NopCloser(errReader)
350 WantError: errors.New("Custom reader error"),
353 // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
354 // and doesn't add a User-Agent.
356 Req: Request{
357 Method: "GET",
358 URL: mustParseURL("/foo"),
359 ProtoMajor: 1,
360 ProtoMinor: 0,
361 Header: Header{
362 "X-Foo": []string{"X-Bar"},
366 WantWrite: "GET /foo HTTP/1.1\r\n" +
367 "Host: \r\n" +
368 "User-Agent: Go 1.1 package http\r\n" +
369 "X-Foo: X-Bar\r\n\r\n",
372 // If no Request.Host and no Request.URL.Host, we send
373 // an empty Host header, and don't use
374 // Request.Header["Host"]. This is just testing that
375 // we don't change Go 1.0 behavior.
377 Req: Request{
378 Method: "GET",
379 Host: "",
380 URL: &url.URL{
381 Scheme: "http",
382 Host: "",
383 Path: "/search",
385 ProtoMajor: 1,
386 ProtoMinor: 1,
387 Header: Header{
388 "Host": []string{"bad.example.com"},
392 WantWrite: "GET /search HTTP/1.1\r\n" +
393 "Host: \r\n" +
394 "User-Agent: Go 1.1 package http\r\n\r\n",
397 // Opaque test #1 from golang.org/issue/4860
399 Req: Request{
400 Method: "GET",
401 URL: &url.URL{
402 Scheme: "http",
403 Host: "www.google.com",
404 Opaque: "/%2F/%2F/",
406 ProtoMajor: 1,
407 ProtoMinor: 1,
408 Header: Header{},
411 WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
412 "Host: www.google.com\r\n" +
413 "User-Agent: Go 1.1 package http\r\n\r\n",
416 // Opaque test #2 from golang.org/issue/4860
418 Req: Request{
419 Method: "GET",
420 URL: &url.URL{
421 Scheme: "http",
422 Host: "x.google.com",
423 Opaque: "//y.google.com/%2F/%2F/",
425 ProtoMajor: 1,
426 ProtoMinor: 1,
427 Header: Header{},
430 WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
431 "Host: x.google.com\r\n" +
432 "User-Agent: Go 1.1 package http\r\n\r\n",
435 // Testing custom case in header keys. Issue 5022.
437 Req: Request{
438 Method: "GET",
439 URL: &url.URL{
440 Scheme: "http",
441 Host: "www.google.com",
442 Path: "/",
444 Proto: "HTTP/1.1",
445 ProtoMajor: 1,
446 ProtoMinor: 1,
447 Header: Header{
448 "ALL-CAPS": {"x"},
452 WantWrite: "GET / HTTP/1.1\r\n" +
453 "Host: www.google.com\r\n" +
454 "User-Agent: Go 1.1 package http\r\n" +
455 "ALL-CAPS: x\r\n" +
456 "\r\n",
460 func TestRequestWrite(t *testing.T) {
461 for i := range reqWriteTests {
462 tt := &reqWriteTests[i]
464 setBody := func() {
465 if tt.Body == nil {
466 return
468 switch b := tt.Body.(type) {
469 case []byte:
470 tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
471 case func() io.ReadCloser:
472 tt.Req.Body = b()
475 setBody()
476 if tt.Req.Header == nil {
477 tt.Req.Header = make(Header)
480 var braw bytes.Buffer
481 err := tt.Req.Write(&braw)
482 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
483 t.Errorf("writing #%d, err = %q, want %q", i, g, e)
484 continue
486 if err != nil {
487 continue
490 if tt.WantWrite != "" {
491 sraw := braw.String()
492 if sraw != tt.WantWrite {
493 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
494 continue
498 if tt.WantProxy != "" {
499 setBody()
500 var praw bytes.Buffer
501 err = tt.Req.WriteProxy(&praw)
502 if err != nil {
503 t.Errorf("WriteProxy #%d: %s", i, err)
504 continue
506 sraw := praw.String()
507 if sraw != tt.WantProxy {
508 t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
509 continue
515 type closeChecker struct {
516 io.Reader
517 closed bool
520 func (rc *closeChecker) Close() error {
521 rc.closed = true
522 return nil
525 // TestRequestWriteClosesBody tests that Request.Write does close its request.Body.
526 // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
527 // inside a NopCloser, and that it serializes it correctly.
528 func TestRequestWriteClosesBody(t *testing.T) {
529 rc := &closeChecker{Reader: strings.NewReader("my body")}
530 req, _ := NewRequest("POST", "http://foo.com/", rc)
531 if req.ContentLength != 0 {
532 t.Errorf("got req.ContentLength %d, want 0", req.ContentLength)
534 buf := new(bytes.Buffer)
535 req.Write(buf)
536 if !rc.closed {
537 t.Error("body not closed after write")
539 expected := "POST / HTTP/1.1\r\n" +
540 "Host: foo.com\r\n" +
541 "User-Agent: Go 1.1 package http\r\n" +
542 "Transfer-Encoding: chunked\r\n\r\n" +
543 // TODO: currently we don't buffer before chunking, so we get a
544 // single "m" chunk before the other chunks, as this was the 1-byte
545 // read from our MultiReader where we stiched the Body back together
546 // after sniffing whether the Body was 0 bytes or not.
547 chunk("m") +
548 chunk("y body") +
549 chunk("")
550 if buf.String() != expected {
551 t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
555 func chunk(s string) string {
556 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
559 func mustParseURL(s string) *url.URL {
560 u, err := url.Parse(s)
561 if err != nil {
562 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
564 return u