netramesh

Форк
0
/
requestwrite_test.go 
882 строки · 21.2 Кб
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.
4

5
package http
6

7
import (
8
	"bufio"
9
	"bytes"
10
	"errors"
11
	"fmt"
12
	"io"
13
	"io/ioutil"
14
	"net"
15
	"net/url"
16
	"strings"
17
	"testing"
18
	"time"
19
)
20

21
type reqWriteTest struct {
22
	Req  Request
23
	Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
24

25
	// Any of these three may be empty to skip that test.
26
	WantWrite string // Request.Write
27
	WantProxy string // Request.WriteProxy
28

29
	WantError error // wanted error from Request.Write
30
}
31

32
var reqWriteTests = []reqWriteTest{
33
	// HTTP/1.1 => chunked coding; no body; no trailer
34
	0: {
35
		Req: Request{
36
			Method: "GET",
37
			URL: &url.URL{
38
				Scheme: "http",
39
				Host:   "www.techcrunch.com",
40
				Path:   "/",
41
			},
42
			Proto:      "HTTP/1.1",
43
			ProtoMajor: 1,
44
			ProtoMinor: 1,
45
			Header: Header{
46
				"Accept":           {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
47
				"Accept-Charset":   {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
48
				"Accept-Encoding":  {"gzip,deflate"},
49
				"Accept-Language":  {"en-us,en;q=0.5"},
50
				"Keep-Alive":       {"300"},
51
				"Proxy-Connection": {"keep-alive"},
52
				"User-Agent":       {"Fake"},
53
			},
54
			Body:  nil,
55
			Close: false,
56
			Host:  "www.techcrunch.com",
57
			Form:  map[string][]string{},
58
		},
59

60
		WantWrite: "GET / HTTP/1.1\r\n" +
61
			"Host: www.techcrunch.com\r\n" +
62
			"User-Agent: Fake\r\n" +
63
			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
64
			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
65
			"Accept-Encoding: gzip,deflate\r\n" +
66
			"Accept-Language: en-us,en;q=0.5\r\n" +
67
			"Keep-Alive: 300\r\n" +
68
			"Proxy-Connection: keep-alive\r\n\r\n",
69

70
		WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
71
			"Host: www.techcrunch.com\r\n" +
72
			"User-Agent: Fake\r\n" +
73
			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
74
			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
75
			"Accept-Encoding: gzip,deflate\r\n" +
76
			"Accept-Language: en-us,en;q=0.5\r\n" +
77
			"Keep-Alive: 300\r\n" +
78
			"Proxy-Connection: keep-alive\r\n\r\n",
79
	},
80
	// HTTP/1.1 => chunked coding; body; empty trailer
81
	1: {
82
		Req: Request{
83
			Method: "GET",
84
			URL: &url.URL{
85
				Scheme: "http",
86
				Host:   "www.google.com",
87
				Path:   "/search",
88
			},
89
			ProtoMajor:       1,
90
			ProtoMinor:       1,
91
			Header:           Header{},
92
			TransferEncoding: []string{"chunked"},
93
		},
94

95
		Body: []byte("abcdef"),
96

97
		WantWrite: "GET /search HTTP/1.1\r\n" +
98
			"Host: www.google.com\r\n" +
99
			"User-Agent: Go-http-client/1.1\r\n" +
100
			"Transfer-Encoding: chunked\r\n\r\n" +
101
			chunk("abcdef") + chunk(""),
102

103
		WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
104
			"Host: www.google.com\r\n" +
105
			"User-Agent: Go-http-client/1.1\r\n" +
106
			"Transfer-Encoding: chunked\r\n\r\n" +
107
			chunk("abcdef") + chunk(""),
108
	},
109
	// HTTP/1.1 POST => chunked coding; body; empty trailer
110
	2: {
111
		Req: Request{
112
			Method: "POST",
113
			URL: &url.URL{
114
				Scheme: "http",
115
				Host:   "www.google.com",
116
				Path:   "/search",
117
			},
118
			ProtoMajor:       1,
119
			ProtoMinor:       1,
120
			Header:           Header{},
121
			Close:            true,
122
			TransferEncoding: []string{"chunked"},
123
		},
124

125
		Body: []byte("abcdef"),
126

127
		WantWrite: "POST /search HTTP/1.1\r\n" +
128
			"Host: www.google.com\r\n" +
129
			"User-Agent: Go-http-client/1.1\r\n" +
130
			"Connection: close\r\n" +
131
			"Transfer-Encoding: chunked\r\n\r\n" +
132
			chunk("abcdef") + chunk(""),
133

134
		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
135
			"Host: www.google.com\r\n" +
136
			"User-Agent: Go-http-client/1.1\r\n" +
137
			"Connection: close\r\n" +
138
			"Transfer-Encoding: chunked\r\n\r\n" +
139
			chunk("abcdef") + chunk(""),
140
	},
141

142
	// HTTP/1.1 POST with Content-Length, no chunking
143
	3: {
144
		Req: Request{
145
			Method: "POST",
146
			URL: &url.URL{
147
				Scheme: "http",
148
				Host:   "www.google.com",
149
				Path:   "/search",
150
			},
151
			ProtoMajor:    1,
152
			ProtoMinor:    1,
153
			Header:        Header{},
154
			Close:         true,
155
			ContentLength: 6,
156
		},
157

158
		Body: []byte("abcdef"),
159

160
		WantWrite: "POST /search HTTP/1.1\r\n" +
161
			"Host: www.google.com\r\n" +
162
			"User-Agent: Go-http-client/1.1\r\n" +
163
			"Connection: close\r\n" +
164
			"Content-Length: 6\r\n" +
165
			"\r\n" +
166
			"abcdef",
167

168
		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
169
			"Host: www.google.com\r\n" +
170
			"User-Agent: Go-http-client/1.1\r\n" +
171
			"Connection: close\r\n" +
172
			"Content-Length: 6\r\n" +
173
			"\r\n" +
174
			"abcdef",
175
	},
176

177
	// HTTP/1.1 POST with Content-Length in headers
178
	4: {
179
		Req: Request{
180
			Method: "POST",
181
			URL:    mustParseURL("http://example.com/"),
182
			Host:   "example.com",
183
			Header: Header{
184
				"Content-Length": []string{"10"}, // ignored
185
			},
186
			ContentLength: 6,
187
		},
188

189
		Body: []byte("abcdef"),
190

191
		WantWrite: "POST / HTTP/1.1\r\n" +
192
			"Host: example.com\r\n" +
193
			"User-Agent: Go-http-client/1.1\r\n" +
194
			"Content-Length: 6\r\n" +
195
			"\r\n" +
196
			"abcdef",
197

198
		WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
199
			"Host: example.com\r\n" +
200
			"User-Agent: Go-http-client/1.1\r\n" +
201
			"Content-Length: 6\r\n" +
202
			"\r\n" +
203
			"abcdef",
204
	},
205

206
	// default to HTTP/1.1
207
	5: {
208
		Req: Request{
209
			Method: "GET",
210
			URL:    mustParseURL("/search"),
211
			Host:   "www.google.com",
212
		},
213

214
		WantWrite: "GET /search HTTP/1.1\r\n" +
215
			"Host: www.google.com\r\n" +
216
			"User-Agent: Go-http-client/1.1\r\n" +
217
			"\r\n",
218
	},
219

220
	// Request with a 0 ContentLength and a 0 byte body.
221
	6: {
222
		Req: Request{
223
			Method:        "POST",
224
			URL:           mustParseURL("/"),
225
			Host:          "example.com",
226
			ProtoMajor:    1,
227
			ProtoMinor:    1,
228
			ContentLength: 0, // as if unset by user
229
		},
230

231
		Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
232

233
		WantWrite: "POST / HTTP/1.1\r\n" +
234
			"Host: example.com\r\n" +
235
			"User-Agent: Go-http-client/1.1\r\n" +
236
			"Transfer-Encoding: chunked\r\n" +
237
			"\r\n0\r\n\r\n",
238

239
		WantProxy: "POST / HTTP/1.1\r\n" +
240
			"Host: example.com\r\n" +
241
			"User-Agent: Go-http-client/1.1\r\n" +
242
			"Transfer-Encoding: chunked\r\n" +
243
			"\r\n0\r\n\r\n",
244
	},
245

246
	// Request with a 0 ContentLength and a nil body.
247
	7: {
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
255
		},
256

257
		Body: func() io.ReadCloser { return nil },
258

259
		WantWrite: "POST / HTTP/1.1\r\n" +
260
			"Host: example.com\r\n" +
261
			"User-Agent: Go-http-client/1.1\r\n" +
262
			"Content-Length: 0\r\n" +
263
			"\r\n",
264

265
		WantProxy: "POST / HTTP/1.1\r\n" +
266
			"Host: example.com\r\n" +
267
			"User-Agent: Go-http-client/1.1\r\n" +
268
			"Content-Length: 0\r\n" +
269
			"\r\n",
270
	},
271

272
	// Request with a 0 ContentLength and a 1 byte body.
273
	8: {
274
		Req: Request{
275
			Method:        "POST",
276
			URL:           mustParseURL("/"),
277
			Host:          "example.com",
278
			ProtoMajor:    1,
279
			ProtoMinor:    1,
280
			ContentLength: 0, // as if unset by user
281
		},
282

283
		Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
284

285
		WantWrite: "POST / HTTP/1.1\r\n" +
286
			"Host: example.com\r\n" +
287
			"User-Agent: Go-http-client/1.1\r\n" +
288
			"Transfer-Encoding: chunked\r\n\r\n" +
289
			chunk("x") + chunk(""),
290

291
		WantProxy: "POST / HTTP/1.1\r\n" +
292
			"Host: example.com\r\n" +
293
			"User-Agent: Go-http-client/1.1\r\n" +
294
			"Transfer-Encoding: chunked\r\n\r\n" +
295
			chunk("x") + chunk(""),
296
	},
297

298
	// Request with a ContentLength of 10 but a 5 byte body.
299
	9: {
300
		Req: Request{
301
			Method:        "POST",
302
			URL:           mustParseURL("/"),
303
			Host:          "example.com",
304
			ProtoMajor:    1,
305
			ProtoMinor:    1,
306
			ContentLength: 10, // but we're going to send only 5 bytes
307
		},
308
		Body:      []byte("12345"),
309
		WantError: errors.New("http: ContentLength=10 with Body length 5"),
310
	},
311

312
	// Request with a ContentLength of 4 but an 8 byte body.
313
	10: {
314
		Req: Request{
315
			Method:        "POST",
316
			URL:           mustParseURL("/"),
317
			Host:          "example.com",
318
			ProtoMajor:    1,
319
			ProtoMinor:    1,
320
			ContentLength: 4, // but we're going to try to send 8 bytes
321
		},
322
		Body:      []byte("12345678"),
323
		WantError: errors.New("http: ContentLength=4 with Body length 8"),
324
	},
325

326
	// Request with a 5 ContentLength and nil body.
327
	11: {
328
		Req: Request{
329
			Method:        "POST",
330
			URL:           mustParseURL("/"),
331
			Host:          "example.com",
332
			ProtoMajor:    1,
333
			ProtoMinor:    1,
334
			ContentLength: 5, // but we'll omit the body
335
		},
336
		WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
337
	},
338

339
	// Request with a 0 ContentLength and a body with 1 byte content and an error.
340
	12: {
341
		Req: Request{
342
			Method:        "POST",
343
			URL:           mustParseURL("/"),
344
			Host:          "example.com",
345
			ProtoMajor:    1,
346
			ProtoMinor:    1,
347
			ContentLength: 0, // as if unset by user
348
		},
349

350
		Body: func() io.ReadCloser {
351
			err := errors.New("Custom reader error")
352
			errReader := &errorReader{err}
353
			return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
354
		},
355

356
		WantError: errors.New("Custom reader error"),
357
	},
358

359
	// Request with a 0 ContentLength and a body without content and an error.
360
	13: {
361
		Req: Request{
362
			Method:        "POST",
363
			URL:           mustParseURL("/"),
364
			Host:          "example.com",
365
			ProtoMajor:    1,
366
			ProtoMinor:    1,
367
			ContentLength: 0, // as if unset by user
368
		},
369

370
		Body: func() io.ReadCloser {
371
			err := errors.New("Custom reader error")
372
			errReader := &errorReader{err}
373
			return ioutil.NopCloser(errReader)
374
		},
375

376
		WantError: errors.New("Custom reader error"),
377
	},
378

379
	// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
380
	// and doesn't add a User-Agent.
381
	14: {
382
		Req: Request{
383
			Method:     "GET",
384
			URL:        mustParseURL("/foo"),
385
			ProtoMajor: 1,
386
			ProtoMinor: 0,
387
			Header: Header{
388
				"X-Foo": []string{"X-Bar"},
389
			},
390
		},
391

392
		WantWrite: "GET /foo HTTP/1.1\r\n" +
393
			"Host: \r\n" +
394
			"User-Agent: Go-http-client/1.1\r\n" +
395
			"X-Foo: X-Bar\r\n\r\n",
396
	},
397

398
	// If no Request.Host and no Request.URL.Host, we send
399
	// an empty Host header, and don't use
400
	// Request.Header["Host"]. This is just testing that
401
	// we don't change Go 1.0 behavior.
402
	15: {
403
		Req: Request{
404
			Method: "GET",
405
			Host:   "",
406
			URL: &url.URL{
407
				Scheme: "http",
408
				Host:   "",
409
				Path:   "/search",
410
			},
411
			ProtoMajor: 1,
412
			ProtoMinor: 1,
413
			Header: Header{
414
				"Host": []string{"bad.example.com"},
415
			},
416
		},
417

418
		WantWrite: "GET /search HTTP/1.1\r\n" +
419
			"Host: \r\n" +
420
			"User-Agent: Go-http-client/1.1\r\n\r\n",
421
	},
422

423
	// Opaque test #1 from golang.org/issue/4860
424
	16: {
425
		Req: Request{
426
			Method: "GET",
427
			URL: &url.URL{
428
				Scheme: "http",
429
				Host:   "www.google.com",
430
				Opaque: "/%2F/%2F/",
431
			},
432
			ProtoMajor: 1,
433
			ProtoMinor: 1,
434
			Header:     Header{},
435
		},
436

437
		WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
438
			"Host: www.google.com\r\n" +
439
			"User-Agent: Go-http-client/1.1\r\n\r\n",
440
	},
441

442
	// Opaque test #2 from golang.org/issue/4860
443
	17: {
444
		Req: Request{
445
			Method: "GET",
446
			URL: &url.URL{
447
				Scheme: "http",
448
				Host:   "x.google.com",
449
				Opaque: "//y.google.com/%2F/%2F/",
450
			},
451
			ProtoMajor: 1,
452
			ProtoMinor: 1,
453
			Header:     Header{},
454
		},
455

456
		WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
457
			"Host: x.google.com\r\n" +
458
			"User-Agent: Go-http-client/1.1\r\n\r\n",
459
	},
460

461
	// Testing custom case in header keys. Issue 5022.
462
	18: {
463
		Req: Request{
464
			Method: "GET",
465
			URL: &url.URL{
466
				Scheme: "http",
467
				Host:   "www.google.com",
468
				Path:   "/",
469
			},
470
			Proto:      "HTTP/1.1",
471
			ProtoMajor: 1,
472
			ProtoMinor: 1,
473
			Header: Header{
474
				"ALL-CAPS": {"x"},
475
			},
476
		},
477

478
		WantWrite: "GET / HTTP/1.1\r\n" +
479
			"Host: www.google.com\r\n" +
480
			"User-Agent: Go-http-client/1.1\r\n" +
481
			"ALL-CAPS: x\r\n" +
482
			"\r\n",
483
	},
484

485
	// Request with host header field; IPv6 address with zone identifier
486
	19: {
487
		Req: Request{
488
			Method: "GET",
489
			URL: &url.URL{
490
				Host: "[fe80::1%en0]",
491
			},
492
		},
493

494
		WantWrite: "GET / HTTP/1.1\r\n" +
495
			"Host: [fe80::1]\r\n" +
496
			"User-Agent: Go-http-client/1.1\r\n" +
497
			"\r\n",
498
	},
499

500
	// Request with optional host header field; IPv6 address with zone identifier
501
	20: {
502
		Req: Request{
503
			Method: "GET",
504
			URL: &url.URL{
505
				Host: "www.example.com",
506
			},
507
			Host: "[fe80::1%en0]:8080",
508
		},
509

510
		WantWrite: "GET / HTTP/1.1\r\n" +
511
			"Host: [fe80::1]:8080\r\n" +
512
			"User-Agent: Go-http-client/1.1\r\n" +
513
			"\r\n",
514
	},
515
}
516

517
func TestRequestWrite(t *testing.T) {
518
	for i := range reqWriteTests {
519
		tt := &reqWriteTests[i]
520

521
		setBody := func() {
522
			if tt.Body == nil {
523
				return
524
			}
525
			switch b := tt.Body.(type) {
526
			case []byte:
527
				tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
528
			case func() io.ReadCloser:
529
				tt.Req.Body = b()
530
			}
531
		}
532
		setBody()
533
		if tt.Req.Header == nil {
534
			tt.Req.Header = make(Header)
535
		}
536

537
		var braw bytes.Buffer
538
		err := tt.Req.Write(&braw)
539
		if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
540
			t.Errorf("writing #%d, err = %q, want %q", i, g, e)
541
			continue
542
		}
543
		if err != nil {
544
			continue
545
		}
546

547
		if tt.WantWrite != "" {
548
			sraw := braw.String()
549
			if sraw != tt.WantWrite {
550
				t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
551
				continue
552
			}
553
		}
554

555
		if tt.WantProxy != "" {
556
			setBody()
557
			var praw bytes.Buffer
558
			err = tt.Req.WriteProxy(&praw)
559
			if err != nil {
560
				t.Errorf("WriteProxy #%d: %s", i, err)
561
				continue
562
			}
563
			sraw := praw.String()
564
			if sraw != tt.WantProxy {
565
				t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
566
				continue
567
			}
568
		}
569
	}
570
}
571

572
func TestRequestWriteTransport(t *testing.T) {
573
	t.Parallel()
574

575
	matchSubstr := func(substr string) func(string) error {
576
		return func(written string) error {
577
			if !strings.Contains(written, substr) {
578
				return fmt.Errorf("expected substring %q in request: %s", substr, written)
579
			}
580
			return nil
581
		}
582
	}
583

584
	noContentLengthOrTransferEncoding := func(req string) error {
585
		if strings.Contains(req, "Content-Length: ") {
586
			return fmt.Errorf("unexpected Content-Length in request: %s", req)
587
		}
588
		if strings.Contains(req, "Transfer-Encoding: ") {
589
			return fmt.Errorf("unexpected Transfer-Encoding in request: %s", req)
590
		}
591
		return nil
592
	}
593

594
	all := func(checks ...func(string) error) func(string) error {
595
		return func(req string) error {
596
			for _, c := range checks {
597
				if err := c(req); err != nil {
598
					return err
599
				}
600
			}
601
			return nil
602
		}
603
	}
604

605
	type testCase struct {
606
		method string
607
		clen   int64 // ContentLength
608
		body   io.ReadCloser
609
		want   func(string) error
610

611
		// optional:
612
		init         func(*testCase)
613
		afterReqRead func()
614
	}
615

616
	tests := []testCase{
617
		{
618
			method: "GET",
619
			want:   noContentLengthOrTransferEncoding,
620
		},
621
		{
622
			method: "GET",
623
			body:   ioutil.NopCloser(strings.NewReader("")),
624
			want:   noContentLengthOrTransferEncoding,
625
		},
626
		{
627
			method: "GET",
628
			clen:   -1,
629
			body:   ioutil.NopCloser(strings.NewReader("")),
630
			want:   noContentLengthOrTransferEncoding,
631
		},
632
		// A GET with a body, with explicit content length:
633
		{
634
			method: "GET",
635
			clen:   7,
636
			body:   ioutil.NopCloser(strings.NewReader("foobody")),
637
			want: all(matchSubstr("Content-Length: 7"),
638
				matchSubstr("foobody")),
639
		},
640
		// A GET with a body, sniffing the leading "f" from "foobody".
641
		{
642
			method: "GET",
643
			clen:   -1,
644
			body:   ioutil.NopCloser(strings.NewReader("foobody")),
645
			want: all(matchSubstr("Transfer-Encoding: chunked"),
646
				matchSubstr("\r\n1\r\nf\r\n"),
647
				matchSubstr("oobody")),
648
		},
649
		// But a POST request is expected to have a body, so
650
		// no sniffing happens:
651
		{
652
			method: "POST",
653
			clen:   -1,
654
			body:   ioutil.NopCloser(strings.NewReader("foobody")),
655
			want: all(matchSubstr("Transfer-Encoding: chunked"),
656
				matchSubstr("foobody")),
657
		},
658
		{
659
			method: "POST",
660
			clen:   -1,
661
			body:   ioutil.NopCloser(strings.NewReader("")),
662
			want:   all(matchSubstr("Transfer-Encoding: chunked")),
663
		},
664
		// Verify that a blocking Request.Body doesn't block forever.
665
		{
666
			method: "GET",
667
			clen:   -1,
668
			init: func(tt *testCase) {
669
				pr, pw := io.Pipe()
670
				tt.afterReqRead = func() {
671
					pw.Close()
672
				}
673
				tt.body = ioutil.NopCloser(pr)
674
			},
675
			want: matchSubstr("Transfer-Encoding: chunked"),
676
		},
677
	}
678

679
	for i, tt := range tests {
680
		if tt.init != nil {
681
			tt.init(&tt)
682
		}
683
		req := &Request{
684
			Method: tt.method,
685
			URL: &url.URL{
686
				Scheme: "http",
687
				Host:   "example.com",
688
			},
689
			Header:        make(Header),
690
			ContentLength: tt.clen,
691
			Body:          tt.body,
692
		}
693
		got, err := dumpRequestOut(req, tt.afterReqRead)
694
		if err != nil {
695
			t.Errorf("test[%d]: %v", i, err)
696
			continue
697
		}
698
		if err := tt.want(string(got)); err != nil {
699
			t.Errorf("test[%d]: %v", i, err)
700
		}
701
	}
702
}
703

704
type closeChecker struct {
705
	io.Reader
706
	closed bool
707
}
708

709
func (rc *closeChecker) Close() error {
710
	rc.closed = true
711
	return nil
712
}
713

714
// TestRequestWriteClosesBody tests that Request.Write closes its request.Body.
715
// It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
716
// inside a NopCloser, and that it serializes it correctly.
717
func TestRequestWriteClosesBody(t *testing.T) {
718
	rc := &closeChecker{Reader: strings.NewReader("my body")}
719
	req, err := NewRequest("POST", "http://foo.com/", rc)
720
	if err != nil {
721
		t.Fatal(err)
722
	}
723
	buf := new(bytes.Buffer)
724
	if err := req.Write(buf); err != nil {
725
		t.Error(err)
726
	}
727
	if !rc.closed {
728
		t.Error("body not closed after write")
729
	}
730
	expected := "POST / HTTP/1.1\r\n" +
731
		"Host: foo.com\r\n" +
732
		"User-Agent: Go-http-client/1.1\r\n" +
733
		"Transfer-Encoding: chunked\r\n\r\n" +
734
		chunk("my body") +
735
		chunk("")
736
	if buf.String() != expected {
737
		t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
738
	}
739
}
740

741
func chunk(s string) string {
742
	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
743
}
744

745
func mustParseURL(s string) *url.URL {
746
	u, err := url.Parse(s)
747
	if err != nil {
748
		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
749
	}
750
	return u
751
}
752

753
type writerFunc func([]byte) (int, error)
754

755
func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
756

757
// TestRequestWriteError tests the Write err != nil checks in (*Request).write.
758
func TestRequestWriteError(t *testing.T) {
759
	failAfter, writeCount := 0, 0
760
	errFail := errors.New("fake write failure")
761

762
	// w is the buffered io.Writer to write the request to. It
763
	// fails exactly once on its Nth Write call, as controlled by
764
	// failAfter. It also tracks the number of calls in
765
	// writeCount.
766
	w := struct {
767
		io.ByteWriter // to avoid being wrapped by a bufio.Writer
768
		io.Writer
769
	}{
770
		nil,
771
		writerFunc(func(p []byte) (n int, err error) {
772
			writeCount++
773
			if failAfter == 0 {
774
				err = errFail
775
			}
776
			failAfter--
777
			return len(p), err
778
		}),
779
	}
780

781
	req, _ := NewRequest("GET", "http://example.com/", nil)
782
	const writeCalls = 4 // number of Write calls in current implementation
783
	sawGood := false
784
	for n := 0; n <= writeCalls+2; n++ {
785
		failAfter = n
786
		writeCount = 0
787
		err := req.Write(w)
788
		var wantErr error
789
		if n < writeCalls {
790
			wantErr = errFail
791
		}
792
		if err != wantErr {
793
			t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr)
794
			continue
795
		}
796
		if err == nil {
797
			sawGood = true
798
			if writeCount != writeCalls {
799
				t.Fatalf("writeCalls constant is outdated in test")
800
			}
801
		}
802
		if writeCount > writeCalls || writeCount > n+1 {
803
			t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount)
804
		}
805
	}
806
	if !sawGood {
807
		t.Fatalf("writeCalls constant is outdated in test")
808
	}
809
}
810

811
// dumpRequestOut is a modified copy of net/http/httputil.DumpRequestOut.
812
// Unlike the original, this version doesn't mutate the req.Body and
813
// try to restore it. It always dumps the whole body.
814
// And it doesn't support https.
815
func dumpRequestOut(req *Request, onReadHeaders func()) ([]byte, error) {
816

817
	// Use the actual Transport code to record what we would send
818
	// on the wire, but not using TCP.  Use a Transport with a
819
	// custom dialer that returns a fake net.Conn that waits
820
	// for the full input (and recording it), and then responds
821
	// with a dummy response.
822
	var buf bytes.Buffer // records the output
823
	pr, pw := io.Pipe()
824
	defer pr.Close()
825
	defer pw.Close()
826
	dr := &delegateReader{c: make(chan io.Reader)}
827

828
	t := &Transport{
829
		Dial: func(net, addr string) (net.Conn, error) {
830
			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
831
		},
832
	}
833
	defer t.CloseIdleConnections()
834

835
	// Wait for the request before replying with a dummy response:
836
	go func() {
837
		req, err := ReadRequest(bufio.NewReader(pr))
838
		if err == nil {
839
			if onReadHeaders != nil {
840
				onReadHeaders()
841
			}
842
			// Ensure all the body is read; otherwise
843
			// we'll get a partial dump.
844
			io.Copy(ioutil.Discard, req.Body)
845
			req.Body.Close()
846
		}
847
		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
848
	}()
849

850
	_, err := t.RoundTrip(req)
851
	if err != nil {
852
		return nil, err
853
	}
854
	return buf.Bytes(), nil
855
}
856

857
// delegateReader is a reader that delegates to another reader,
858
// once it arrives on a channel.
859
type delegateReader struct {
860
	c chan io.Reader
861
	r io.Reader // nil until received from c
862
}
863

864
func (r *delegateReader) Read(p []byte) (int, error) {
865
	if r.r == nil {
866
		r.r = <-r.c
867
	}
868
	return r.r.Read(p)
869
}
870

871
// dumpConn is a net.Conn that writes to Writer and reads from Reader.
872
type dumpConn struct {
873
	io.Writer
874
	io.Reader
875
}
876

877
func (c *dumpConn) Close() error                       { return nil }
878
func (c *dumpConn) LocalAddr() net.Addr                { return nil }
879
func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
880
func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
881
func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
882
func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
883

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.