Source file
src/net/http/requestwrite_test.go
1
2
3
4
5 package http
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "net/url"
15 "strings"
16 "testing"
17 "testing/iotest"
18 "time"
19 )
20
21 type reqWriteTest struct {
22 Req Request
23 Body any
24
25
26 WantWrite string
27 WantProxy string
28
29 WantError error
30 }
31
32 var reqWriteTests = []reqWriteTest{
33
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
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
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
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
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"},
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
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
221 6: {
222 Req: Request{
223 Method: "POST",
224 URL: mustParseURL("/"),
225 Host: "example.com",
226 ProtoMajor: 1,
227 ProtoMinor: 1,
228 ContentLength: 0,
229 },
230
231 Body: func() io.ReadCloser { return io.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
247 7: {
248 Req: Request{
249 Method: "POST",
250 URL: mustParseURL("/"),
251 Host: "example.com",
252 ProtoMajor: 1,
253 ProtoMinor: 1,
254 ContentLength: 0,
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
273 8: {
274 Req: Request{
275 Method: "POST",
276 URL: mustParseURL("/"),
277 Host: "example.com",
278 ProtoMajor: 1,
279 ProtoMinor: 1,
280 ContentLength: 0,
281 },
282
283 Body: func() io.ReadCloser { return io.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
299 9: {
300 Req: Request{
301 Method: "POST",
302 URL: mustParseURL("/"),
303 Host: "example.com",
304 ProtoMajor: 1,
305 ProtoMinor: 1,
306 ContentLength: 10,
307 },
308 Body: []byte("12345"),
309 WantError: errors.New("http: ContentLength=10 with Body length 5"),
310 },
311
312
313 10: {
314 Req: Request{
315 Method: "POST",
316 URL: mustParseURL("/"),
317 Host: "example.com",
318 ProtoMajor: 1,
319 ProtoMinor: 1,
320 ContentLength: 4,
321 },
322 Body: []byte("12345678"),
323 WantError: errors.New("http: ContentLength=4 with Body length 8"),
324 },
325
326
327 11: {
328 Req: Request{
329 Method: "POST",
330 URL: mustParseURL("/"),
331 Host: "example.com",
332 ProtoMajor: 1,
333 ProtoMinor: 1,
334 ContentLength: 5,
335 },
336 WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
337 },
338
339
340 12: {
341 Req: Request{
342 Method: "POST",
343 URL: mustParseURL("/"),
344 Host: "example.com",
345 ProtoMajor: 1,
346 ProtoMinor: 1,
347 ContentLength: 0,
348 },
349
350 Body: func() io.ReadCloser {
351 err := errors.New("Custom reader error")
352 errReader := iotest.ErrReader(err)
353 return io.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
354 },
355
356 WantError: errors.New("Custom reader error"),
357 },
358
359
360 13: {
361 Req: Request{
362 Method: "POST",
363 URL: mustParseURL("/"),
364 Host: "example.com",
365 ProtoMajor: 1,
366 ProtoMinor: 1,
367 ContentLength: 0,
368 },
369
370 Body: func() io.ReadCloser {
371 err := errors.New("Custom reader error")
372 errReader := iotest.ErrReader(err)
373 return io.NopCloser(errReader)
374 },
375
376 WantError: errors.New("Custom reader error"),
377 },
378
379
380
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
399
400
401
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
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
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
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
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
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 21: {
518 Req: Request{
519 Method: "CONNECT",
520 URL: &url.URL{
521 Scheme: "https",
522 Host: "proxy.com",
523 },
524 },
525
526 WantWrite: "CONNECT proxy.com HTTP/1.1\r\n" +
527 "Host: proxy.com\r\n" +
528 "User-Agent: Go-http-client/1.1\r\n" +
529 "\r\n",
530 },
531
532
533 22: {
534 Req: Request{
535 Method: "CONNECT",
536 URL: &url.URL{
537 Scheme: "https",
538 Host: "proxy.com",
539 Opaque: "backend:443",
540 },
541 },
542 WantWrite: "CONNECT backend:443 HTTP/1.1\r\n" +
543 "Host: proxy.com\r\n" +
544 "User-Agent: Go-http-client/1.1\r\n" +
545 "\r\n",
546 },
547
548
549 23: {
550 Req: Request{
551 Method: "GET",
552 URL: mustParseURL("/foo"),
553 Header: Header{
554 "X-Foo": []string{"X-Bar"},
555 "X-Idempotency-Key": nil,
556 },
557 },
558
559 WantWrite: "GET /foo HTTP/1.1\r\n" +
560 "Host: \r\n" +
561 "User-Agent: Go-http-client/1.1\r\n" +
562 "X-Foo: X-Bar\r\n\r\n",
563 },
564 24: {
565 Req: Request{
566 Method: "GET",
567 URL: mustParseURL("/foo"),
568 Header: Header{
569 "X-Foo": []string{"X-Bar"},
570 "X-Idempotency-Key": []string{},
571 },
572 },
573
574 WantWrite: "GET /foo HTTP/1.1\r\n" +
575 "Host: \r\n" +
576 "User-Agent: Go-http-client/1.1\r\n" +
577 "X-Foo: X-Bar\r\n\r\n",
578 },
579
580 25: {
581 Req: Request{
582 Method: "GET",
583 URL: &url.URL{
584 Host: "www.example.com",
585 RawQuery: "new\nline",
586 },
587 },
588 WantError: errors.New("net/http: can't write control character in Request.URL"),
589 },
590
591 26: {
592 Req: Request{
593 Method: "PATCH",
594 URL: mustParseURL("/"),
595 Host: "example.com",
596 ProtoMajor: 1,
597 ProtoMinor: 1,
598 ContentLength: 0,
599 },
600 Body: nil,
601 WantWrite: "PATCH / HTTP/1.1\r\n" +
602 "Host: example.com\r\n" +
603 "User-Agent: Go-http-client/1.1\r\n" +
604 "Content-Length: 0\r\n\r\n",
605 WantProxy: "PATCH / HTTP/1.1\r\n" +
606 "Host: example.com\r\n" +
607 "User-Agent: Go-http-client/1.1\r\n" +
608 "Content-Length: 0\r\n\r\n",
609 },
610 }
611
612 func TestRequestWrite(t *testing.T) {
613 for i := range reqWriteTests {
614 tt := &reqWriteTests[i]
615
616 setBody := func() {
617 if tt.Body == nil {
618 return
619 }
620 switch b := tt.Body.(type) {
621 case []byte:
622 tt.Req.Body = io.NopCloser(bytes.NewReader(b))
623 case func() io.ReadCloser:
624 tt.Req.Body = b()
625 }
626 }
627 setBody()
628 if tt.Req.Header == nil {
629 tt.Req.Header = make(Header)
630 }
631
632 var braw strings.Builder
633 err := tt.Req.Write(&braw)
634 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
635 t.Errorf("writing #%d, err = %q, want %q", i, g, e)
636 continue
637 }
638 if err != nil {
639 continue
640 }
641
642 if tt.WantWrite != "" {
643 sraw := braw.String()
644 if sraw != tt.WantWrite {
645 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
646 continue
647 }
648 }
649
650 if tt.WantProxy != "" {
651 setBody()
652 var praw strings.Builder
653 err = tt.Req.WriteProxy(&praw)
654 if err != nil {
655 t.Errorf("WriteProxy #%d: %s", i, err)
656 continue
657 }
658 sraw := praw.String()
659 if sraw != tt.WantProxy {
660 t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
661 continue
662 }
663 }
664 }
665 }
666
667 func TestRequestWriteTransport(t *testing.T) {
668 t.Parallel()
669
670 matchSubstr := func(substr string) func(string) error {
671 return func(written string) error {
672 if !strings.Contains(written, substr) {
673 return fmt.Errorf("expected substring %q in request: %s", substr, written)
674 }
675 return nil
676 }
677 }
678
679 noContentLengthOrTransferEncoding := func(req string) error {
680 if strings.Contains(req, "Content-Length: ") {
681 return fmt.Errorf("unexpected Content-Length in request: %s", req)
682 }
683 if strings.Contains(req, "Transfer-Encoding: ") {
684 return fmt.Errorf("unexpected Transfer-Encoding in request: %s", req)
685 }
686 return nil
687 }
688
689 all := func(checks ...func(string) error) func(string) error {
690 return func(req string) error {
691 for _, c := range checks {
692 if err := c(req); err != nil {
693 return err
694 }
695 }
696 return nil
697 }
698 }
699
700 type testCase struct {
701 method string
702 clen int64
703 body io.ReadCloser
704 want func(string) error
705
706
707 init func(*testCase)
708 afterReqRead func()
709 }
710
711 tests := []testCase{
712 {
713 method: "GET",
714 want: noContentLengthOrTransferEncoding,
715 },
716 {
717 method: "GET",
718 body: io.NopCloser(strings.NewReader("")),
719 want: noContentLengthOrTransferEncoding,
720 },
721 {
722 method: "GET",
723 clen: -1,
724 body: io.NopCloser(strings.NewReader("")),
725 want: noContentLengthOrTransferEncoding,
726 },
727
728 {
729 method: "GET",
730 clen: 7,
731 body: io.NopCloser(strings.NewReader("foobody")),
732 want: all(matchSubstr("Content-Length: 7"),
733 matchSubstr("foobody")),
734 },
735
736 {
737 method: "GET",
738 clen: -1,
739 body: io.NopCloser(strings.NewReader("foobody")),
740 want: all(matchSubstr("Transfer-Encoding: chunked"),
741 matchSubstr("\r\n1\r\nf\r\n"),
742 matchSubstr("oobody")),
743 },
744
745
746 {
747 method: "POST",
748 clen: -1,
749 body: io.NopCloser(strings.NewReader("foobody")),
750 want: all(matchSubstr("Transfer-Encoding: chunked"),
751 matchSubstr("foobody")),
752 },
753 {
754 method: "POST",
755 clen: -1,
756 body: io.NopCloser(strings.NewReader("")),
757 want: all(matchSubstr("Transfer-Encoding: chunked")),
758 },
759
760 {
761 method: "GET",
762 clen: -1,
763 init: func(tt *testCase) {
764 pr, pw := io.Pipe()
765 tt.afterReqRead = func() {
766 pw.Close()
767 }
768 tt.body = io.NopCloser(pr)
769 },
770 want: matchSubstr("Transfer-Encoding: chunked"),
771 },
772 }
773
774 for i, tt := range tests {
775 if tt.init != nil {
776 tt.init(&tt)
777 }
778 req := &Request{
779 Method: tt.method,
780 URL: &url.URL{
781 Scheme: "http",
782 Host: "example.com",
783 },
784 Header: make(Header),
785 ContentLength: tt.clen,
786 Body: tt.body,
787 }
788 got, err := dumpRequestOut(req, tt.afterReqRead)
789 if err != nil {
790 t.Errorf("test[%d]: %v", i, err)
791 continue
792 }
793 if err := tt.want(string(got)); err != nil {
794 t.Errorf("test[%d]: %v", i, err)
795 }
796 }
797 }
798
799 type closeChecker struct {
800 io.Reader
801 closed bool
802 }
803
804 func (rc *closeChecker) Close() error {
805 rc.closed = true
806 return nil
807 }
808
809
810
811
812 func TestRequestWriteClosesBody(t *testing.T) {
813 rc := &closeChecker{Reader: strings.NewReader("my body")}
814 req, err := NewRequest("POST", "http://foo.com/", rc)
815 if err != nil {
816 t.Fatal(err)
817 }
818 buf := new(strings.Builder)
819 if err := req.Write(buf); err != nil {
820 t.Error(err)
821 }
822 if !rc.closed {
823 t.Error("body not closed after write")
824 }
825 expected := "POST / HTTP/1.1\r\n" +
826 "Host: foo.com\r\n" +
827 "User-Agent: Go-http-client/1.1\r\n" +
828 "Transfer-Encoding: chunked\r\n\r\n" +
829 chunk("my body") +
830 chunk("")
831 if buf.String() != expected {
832 t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
833 }
834 }
835
836 func chunk(s string) string {
837 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
838 }
839
840 func mustParseURL(s string) *url.URL {
841 u, err := url.Parse(s)
842 if err != nil {
843 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
844 }
845 return u
846 }
847
848 type writerFunc func([]byte) (int, error)
849
850 func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
851
852
853 func TestRequestWriteError(t *testing.T) {
854 failAfter, writeCount := 0, 0
855 errFail := errors.New("fake write failure")
856
857
858
859
860
861 w := struct {
862 io.ByteWriter
863 io.Writer
864 }{
865 nil,
866 writerFunc(func(p []byte) (n int, err error) {
867 writeCount++
868 if failAfter == 0 {
869 err = errFail
870 }
871 failAfter--
872 return len(p), err
873 }),
874 }
875
876 req, _ := NewRequest("GET", "http://example.com/", nil)
877 const writeCalls = 4
878 sawGood := false
879 for n := 0; n <= writeCalls+2; n++ {
880 failAfter = n
881 writeCount = 0
882 err := req.Write(w)
883 var wantErr error
884 if n < writeCalls {
885 wantErr = errFail
886 }
887 if err != wantErr {
888 t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr)
889 continue
890 }
891 if err == nil {
892 sawGood = true
893 if writeCount != writeCalls {
894 t.Fatalf("writeCalls constant is outdated in test")
895 }
896 }
897 if writeCount > writeCalls || writeCount > n+1 {
898 t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount)
899 }
900 }
901 if !sawGood {
902 t.Fatalf("writeCalls constant is outdated in test")
903 }
904 }
905
906
907
908
909
910 func dumpRequestOut(req *Request, onReadHeaders func()) ([]byte, error) {
911
912
913
914
915
916
917 var buf bytes.Buffer
918 pr, pw := io.Pipe()
919 defer pr.Close()
920 defer pw.Close()
921 dr := &delegateReader{c: make(chan io.Reader)}
922
923 t := &Transport{
924 Dial: func(net, addr string) (net.Conn, error) {
925 return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
926 },
927 }
928 defer t.CloseIdleConnections()
929
930
931 go func() {
932 req, err := ReadRequest(bufio.NewReader(pr))
933 if err == nil {
934 if onReadHeaders != nil {
935 onReadHeaders()
936 }
937
938
939 io.Copy(io.Discard, req.Body)
940 req.Body.Close()
941 }
942 dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
943 }()
944
945 _, err := t.RoundTrip(req)
946 if err != nil {
947 return nil, err
948 }
949 return buf.Bytes(), nil
950 }
951
952
953
954 type delegateReader struct {
955 c chan io.Reader
956 r io.Reader
957 }
958
959 func (r *delegateReader) Read(p []byte) (int, error) {
960 if r.r == nil {
961 r.r = <-r.c
962 }
963 return r.r.Read(p)
964 }
965
966
967 type dumpConn struct {
968 io.Writer
969 io.Reader
970 }
971
972 func (c *dumpConn) Close() error { return nil }
973 func (c *dumpConn) LocalAddr() net.Addr { return nil }
974 func (c *dumpConn) RemoteAddr() net.Addr { return nil }
975 func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
976 func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
977 func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
978
View as plain text