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 "testing/synctest"
19 "time"
20 )
21
22 type reqWriteTest struct {
23 Req Request
24 Body any
25
26
27 WantWrite string
28 WantProxy string
29
30 WantError error
31 }
32
33 var reqWriteTests = []reqWriteTest{
34
35 0: {
36 Req: Request{
37 Method: "GET",
38 URL: &url.URL{
39 Scheme: "http",
40 Host: "www.techcrunch.com",
41 Path: "/",
42 },
43 Proto: "HTTP/1.1",
44 ProtoMajor: 1,
45 ProtoMinor: 1,
46 Header: Header{
47 "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
48 "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
49 "Accept-Encoding": {"gzip,deflate"},
50 "Accept-Language": {"en-us,en;q=0.5"},
51 "Keep-Alive": {"300"},
52 "Proxy-Connection": {"keep-alive"},
53 "User-Agent": {"Fake"},
54 },
55 Body: nil,
56 Close: false,
57 Host: "www.techcrunch.com",
58 Form: map[string][]string{},
59 },
60
61 WantWrite: "GET / HTTP/1.1\r\n" +
62 "Host: www.techcrunch.com\r\n" +
63 "User-Agent: Fake\r\n" +
64 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
65 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
66 "Accept-Encoding: gzip,deflate\r\n" +
67 "Accept-Language: en-us,en;q=0.5\r\n" +
68 "Keep-Alive: 300\r\n" +
69 "Proxy-Connection: keep-alive\r\n\r\n",
70
71 WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
72 "Host: www.techcrunch.com\r\n" +
73 "User-Agent: Fake\r\n" +
74 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
75 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
76 "Accept-Encoding: gzip,deflate\r\n" +
77 "Accept-Language: en-us,en;q=0.5\r\n" +
78 "Keep-Alive: 300\r\n" +
79 "Proxy-Connection: keep-alive\r\n\r\n",
80 },
81
82 1: {
83 Req: Request{
84 Method: "GET",
85 URL: &url.URL{
86 Scheme: "http",
87 Host: "www.google.com",
88 Path: "/search",
89 },
90 ProtoMajor: 1,
91 ProtoMinor: 1,
92 Header: Header{},
93 TransferEncoding: []string{"chunked"},
94 },
95
96 Body: []byte("abcdef"),
97
98 WantWrite: "GET /search HTTP/1.1\r\n" +
99 "Host: www.google.com\r\n" +
100 "User-Agent: Go-http-client/1.1\r\n" +
101 "Transfer-Encoding: chunked\r\n\r\n" +
102 chunk("abcdef") + chunk(""),
103
104 WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
105 "Host: www.google.com\r\n" +
106 "User-Agent: Go-http-client/1.1\r\n" +
107 "Transfer-Encoding: chunked\r\n\r\n" +
108 chunk("abcdef") + chunk(""),
109 },
110
111 2: {
112 Req: Request{
113 Method: "POST",
114 URL: &url.URL{
115 Scheme: "http",
116 Host: "www.google.com",
117 Path: "/search",
118 },
119 ProtoMajor: 1,
120 ProtoMinor: 1,
121 Header: Header{},
122 Close: true,
123 TransferEncoding: []string{"chunked"},
124 },
125
126 Body: []byte("abcdef"),
127
128 WantWrite: "POST /search HTTP/1.1\r\n" +
129 "Host: www.google.com\r\n" +
130 "User-Agent: Go-http-client/1.1\r\n" +
131 "Connection: close\r\n" +
132 "Transfer-Encoding: chunked\r\n\r\n" +
133 chunk("abcdef") + chunk(""),
134
135 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
136 "Host: www.google.com\r\n" +
137 "User-Agent: Go-http-client/1.1\r\n" +
138 "Connection: close\r\n" +
139 "Transfer-Encoding: chunked\r\n\r\n" +
140 chunk("abcdef") + chunk(""),
141 },
142
143
144 3: {
145 Req: Request{
146 Method: "POST",
147 URL: &url.URL{
148 Scheme: "http",
149 Host: "www.google.com",
150 Path: "/search",
151 },
152 ProtoMajor: 1,
153 ProtoMinor: 1,
154 Header: Header{},
155 Close: true,
156 ContentLength: 6,
157 },
158
159 Body: []byte("abcdef"),
160
161 WantWrite: "POST /search HTTP/1.1\r\n" +
162 "Host: www.google.com\r\n" +
163 "User-Agent: Go-http-client/1.1\r\n" +
164 "Connection: close\r\n" +
165 "Content-Length: 6\r\n" +
166 "\r\n" +
167 "abcdef",
168
169 WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
170 "Host: www.google.com\r\n" +
171 "User-Agent: Go-http-client/1.1\r\n" +
172 "Connection: close\r\n" +
173 "Content-Length: 6\r\n" +
174 "\r\n" +
175 "abcdef",
176 },
177
178
179 4: {
180 Req: Request{
181 Method: "POST",
182 URL: mustParseURL("http://example.com/"),
183 Host: "example.com",
184 Header: Header{
185 "Content-Length": []string{"10"},
186 },
187 ContentLength: 6,
188 },
189
190 Body: []byte("abcdef"),
191
192 WantWrite: "POST / HTTP/1.1\r\n" +
193 "Host: example.com\r\n" +
194 "User-Agent: Go-http-client/1.1\r\n" +
195 "Content-Length: 6\r\n" +
196 "\r\n" +
197 "abcdef",
198
199 WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
200 "Host: example.com\r\n" +
201 "User-Agent: Go-http-client/1.1\r\n" +
202 "Content-Length: 6\r\n" +
203 "\r\n" +
204 "abcdef",
205 },
206
207
208 5: {
209 Req: Request{
210 Method: "GET",
211 URL: mustParseURL("/search"),
212 Host: "www.google.com",
213 },
214
215 WantWrite: "GET /search HTTP/1.1\r\n" +
216 "Host: www.google.com\r\n" +
217 "User-Agent: Go-http-client/1.1\r\n" +
218 "\r\n",
219 },
220
221
222 6: {
223 Req: Request{
224 Method: "POST",
225 URL: mustParseURL("/"),
226 Host: "example.com",
227 ProtoMajor: 1,
228 ProtoMinor: 1,
229 ContentLength: 0,
230 },
231
232 Body: func() io.ReadCloser { return io.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
233
234 WantWrite: "POST / HTTP/1.1\r\n" +
235 "Host: example.com\r\n" +
236 "User-Agent: Go-http-client/1.1\r\n" +
237 "Transfer-Encoding: chunked\r\n" +
238 "\r\n0\r\n\r\n",
239
240 WantProxy: "POST / HTTP/1.1\r\n" +
241 "Host: example.com\r\n" +
242 "User-Agent: Go-http-client/1.1\r\n" +
243 "Transfer-Encoding: chunked\r\n" +
244 "\r\n0\r\n\r\n",
245 },
246
247
248 7: {
249 Req: Request{
250 Method: "POST",
251 URL: mustParseURL("/"),
252 Host: "example.com",
253 ProtoMajor: 1,
254 ProtoMinor: 1,
255 ContentLength: 0,
256 },
257
258 Body: func() io.ReadCloser { return nil },
259
260 WantWrite: "POST / HTTP/1.1\r\n" +
261 "Host: example.com\r\n" +
262 "User-Agent: Go-http-client/1.1\r\n" +
263 "Content-Length: 0\r\n" +
264 "\r\n",
265
266 WantProxy: "POST / HTTP/1.1\r\n" +
267 "Host: example.com\r\n" +
268 "User-Agent: Go-http-client/1.1\r\n" +
269 "Content-Length: 0\r\n" +
270 "\r\n",
271 },
272
273
274 8: {
275 Req: Request{
276 Method: "POST",
277 URL: mustParseURL("/"),
278 Host: "example.com",
279 ProtoMajor: 1,
280 ProtoMinor: 1,
281 ContentLength: 0,
282 },
283
284 Body: func() io.ReadCloser { return io.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
285
286 WantWrite: "POST / HTTP/1.1\r\n" +
287 "Host: example.com\r\n" +
288 "User-Agent: Go-http-client/1.1\r\n" +
289 "Transfer-Encoding: chunked\r\n\r\n" +
290 chunk("x") + chunk(""),
291
292 WantProxy: "POST / HTTP/1.1\r\n" +
293 "Host: example.com\r\n" +
294 "User-Agent: Go-http-client/1.1\r\n" +
295 "Transfer-Encoding: chunked\r\n\r\n" +
296 chunk("x") + chunk(""),
297 },
298
299
300 9: {
301 Req: Request{
302 Method: "POST",
303 URL: mustParseURL("/"),
304 Host: "example.com",
305 ProtoMajor: 1,
306 ProtoMinor: 1,
307 ContentLength: 10,
308 },
309 Body: []byte("12345"),
310 WantError: errors.New("http: ContentLength=10 with Body length 5"),
311 },
312
313
314 10: {
315 Req: Request{
316 Method: "POST",
317 URL: mustParseURL("/"),
318 Host: "example.com",
319 ProtoMajor: 1,
320 ProtoMinor: 1,
321 ContentLength: 4,
322 },
323 Body: []byte("12345678"),
324 WantError: errors.New("http: ContentLength=4 with Body length 8"),
325 },
326
327
328 11: {
329 Req: Request{
330 Method: "POST",
331 URL: mustParseURL("/"),
332 Host: "example.com",
333 ProtoMajor: 1,
334 ProtoMinor: 1,
335 ContentLength: 5,
336 },
337 WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
338 },
339
340
341 12: {
342 Req: Request{
343 Method: "POST",
344 URL: mustParseURL("/"),
345 Host: "example.com",
346 ProtoMajor: 1,
347 ProtoMinor: 1,
348 ContentLength: 0,
349 },
350
351 Body: func() io.ReadCloser {
352 err := errors.New("Custom reader error")
353 errReader := iotest.ErrReader(err)
354 return io.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
355 },
356
357 WantError: errors.New("Custom reader error"),
358 },
359
360
361 13: {
362 Req: Request{
363 Method: "POST",
364 URL: mustParseURL("/"),
365 Host: "example.com",
366 ProtoMajor: 1,
367 ProtoMinor: 1,
368 ContentLength: 0,
369 },
370
371 Body: func() io.ReadCloser {
372 err := errors.New("Custom reader error")
373 errReader := iotest.ErrReader(err)
374 return io.NopCloser(errReader)
375 },
376
377 WantError: errors.New("Custom reader error"),
378 },
379
380
381
382 14: {
383 Req: Request{
384 Method: "GET",
385 URL: mustParseURL("/foo"),
386 ProtoMajor: 1,
387 ProtoMinor: 0,
388 Header: Header{
389 "X-Foo": []string{"X-Bar"},
390 },
391 },
392
393 WantWrite: "GET /foo HTTP/1.1\r\n" +
394 "Host: \r\n" +
395 "User-Agent: Go-http-client/1.1\r\n" +
396 "X-Foo: X-Bar\r\n\r\n",
397 },
398
399
400
401
402
403 15: {
404 Req: Request{
405 Method: "GET",
406 Host: "",
407 URL: &url.URL{
408 Scheme: "http",
409 Host: "",
410 Path: "/search",
411 },
412 ProtoMajor: 1,
413 ProtoMinor: 1,
414 Header: Header{
415 "Host": []string{"bad.example.com"},
416 },
417 },
418
419 WantWrite: "GET /search HTTP/1.1\r\n" +
420 "Host: \r\n" +
421 "User-Agent: Go-http-client/1.1\r\n\r\n",
422 },
423
424
425 16: {
426 Req: Request{
427 Method: "GET",
428 URL: &url.URL{
429 Scheme: "http",
430 Host: "www.google.com",
431 Opaque: "/%2F/%2F/",
432 },
433 ProtoMajor: 1,
434 ProtoMinor: 1,
435 Header: Header{},
436 },
437
438 WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
439 "Host: www.google.com\r\n" +
440 "User-Agent: Go-http-client/1.1\r\n\r\n",
441 },
442
443
444 17: {
445 Req: Request{
446 Method: "GET",
447 URL: &url.URL{
448 Scheme: "http",
449 Host: "x.google.com",
450 Opaque: "//y.google.com/%2F/%2F/",
451 },
452 ProtoMajor: 1,
453 ProtoMinor: 1,
454 Header: Header{},
455 },
456
457 WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
458 "Host: x.google.com\r\n" +
459 "User-Agent: Go-http-client/1.1\r\n\r\n",
460 },
461
462
463 18: {
464 Req: Request{
465 Method: "GET",
466 URL: &url.URL{
467 Scheme: "http",
468 Host: "www.google.com",
469 Path: "/",
470 },
471 Proto: "HTTP/1.1",
472 ProtoMajor: 1,
473 ProtoMinor: 1,
474 Header: Header{
475 "ALL-CAPS": {"x"},
476 },
477 },
478
479 WantWrite: "GET / HTTP/1.1\r\n" +
480 "Host: www.google.com\r\n" +
481 "User-Agent: Go-http-client/1.1\r\n" +
482 "ALL-CAPS: x\r\n" +
483 "\r\n",
484 },
485
486
487 19: {
488 Req: Request{
489 Method: "GET",
490 URL: &url.URL{
491 Host: "[fe80::1%en0]",
492 },
493 },
494
495 WantWrite: "GET / HTTP/1.1\r\n" +
496 "Host: [fe80::1]\r\n" +
497 "User-Agent: Go-http-client/1.1\r\n" +
498 "\r\n",
499 },
500
501
502 20: {
503 Req: Request{
504 Method: "GET",
505 URL: &url.URL{
506 Host: "www.example.com",
507 },
508 Host: "[fe80::1%en0]:8080",
509 },
510
511 WantWrite: "GET / HTTP/1.1\r\n" +
512 "Host: [fe80::1]:8080\r\n" +
513 "User-Agent: Go-http-client/1.1\r\n" +
514 "\r\n",
515 },
516
517
518 21: {
519 Req: Request{
520 Method: "CONNECT",
521 URL: &url.URL{
522 Scheme: "https",
523 Host: "proxy.com",
524 },
525 },
526
527 WantWrite: "CONNECT proxy.com HTTP/1.1\r\n" +
528 "Host: proxy.com\r\n" +
529 "User-Agent: Go-http-client/1.1\r\n" +
530 "\r\n",
531 },
532
533
534 22: {
535 Req: Request{
536 Method: "CONNECT",
537 URL: &url.URL{
538 Scheme: "https",
539 Host: "proxy.com",
540 Opaque: "backend:443",
541 },
542 },
543 WantWrite: "CONNECT backend:443 HTTP/1.1\r\n" +
544 "Host: proxy.com\r\n" +
545 "User-Agent: Go-http-client/1.1\r\n" +
546 "\r\n",
547 },
548
549
550 23: {
551 Req: Request{
552 Method: "GET",
553 URL: mustParseURL("/foo"),
554 Header: Header{
555 "X-Foo": []string{"X-Bar"},
556 "X-Idempotency-Key": nil,
557 },
558 },
559
560 WantWrite: "GET /foo HTTP/1.1\r\n" +
561 "Host: \r\n" +
562 "User-Agent: Go-http-client/1.1\r\n" +
563 "X-Foo: X-Bar\r\n\r\n",
564 },
565 24: {
566 Req: Request{
567 Method: "GET",
568 URL: mustParseURL("/foo"),
569 Header: Header{
570 "X-Foo": []string{"X-Bar"},
571 "X-Idempotency-Key": []string{},
572 },
573 },
574
575 WantWrite: "GET /foo HTTP/1.1\r\n" +
576 "Host: \r\n" +
577 "User-Agent: Go-http-client/1.1\r\n" +
578 "X-Foo: X-Bar\r\n\r\n",
579 },
580
581 25: {
582 Req: Request{
583 Method: "GET",
584 URL: &url.URL{
585 Host: "www.example.com",
586 RawQuery: "new\nline",
587 },
588 },
589 WantError: errors.New("net/http: can't write control character in Request.URL"),
590 },
591
592 26: {
593 Req: Request{
594 Method: "PATCH",
595 URL: mustParseURL("/"),
596 Host: "example.com",
597 ProtoMajor: 1,
598 ProtoMinor: 1,
599 ContentLength: 0,
600 },
601 Body: nil,
602 WantWrite: "PATCH / HTTP/1.1\r\n" +
603 "Host: example.com\r\n" +
604 "User-Agent: Go-http-client/1.1\r\n" +
605 "Content-Length: 0\r\n\r\n",
606 WantProxy: "PATCH / HTTP/1.1\r\n" +
607 "Host: example.com\r\n" +
608 "User-Agent: Go-http-client/1.1\r\n" +
609 "Content-Length: 0\r\n\r\n",
610 },
611 }
612
613 func TestRequestWrite(t *testing.T) {
614 for i := range reqWriteTests {
615 tt := &reqWriteTests[i]
616
617 setBody := func() {
618 if tt.Body == nil {
619 return
620 }
621 switch b := tt.Body.(type) {
622 case []byte:
623 tt.Req.Body = io.NopCloser(bytes.NewReader(b))
624 case func() io.ReadCloser:
625 tt.Req.Body = b()
626 }
627 }
628 setBody()
629 if tt.Req.Header == nil {
630 tt.Req.Header = make(Header)
631 }
632
633 var braw strings.Builder
634 err := tt.Req.Write(&braw)
635 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
636 t.Errorf("writing #%d, err = %q, want %q", i, g, e)
637 continue
638 }
639 if err != nil {
640 continue
641 }
642
643 if tt.WantWrite != "" {
644 sraw := braw.String()
645 if sraw != tt.WantWrite {
646 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
647 continue
648 }
649 }
650
651 if tt.WantProxy != "" {
652 setBody()
653 var praw strings.Builder
654 err = tt.Req.WriteProxy(&praw)
655 if err != nil {
656 t.Errorf("WriteProxy #%d: %s", i, err)
657 continue
658 }
659 sraw := praw.String()
660 if sraw != tt.WantProxy {
661 t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
662 continue
663 }
664 }
665 }
666 }
667
668 func TestRequestWriteTransport(t *testing.T) {
669 t.Parallel()
670
671
672
673
674
675 synctest.Test(t, testRequestWriteTransport)
676 }
677 func testRequestWriteTransport(t *testing.T) {
678 matchSubstr := func(substr string) func(string) error {
679 return func(written string) error {
680 if !strings.Contains(written, substr) {
681 return fmt.Errorf("expected substring %q in request: %s", substr, written)
682 }
683 return nil
684 }
685 }
686
687 noContentLengthOrTransferEncoding := func(req string) error {
688 if strings.Contains(req, "Content-Length: ") {
689 return fmt.Errorf("unexpected Content-Length in request: %s", req)
690 }
691 if strings.Contains(req, "Transfer-Encoding: ") {
692 return fmt.Errorf("unexpected Transfer-Encoding in request: %s", req)
693 }
694 return nil
695 }
696
697 all := func(checks ...func(string) error) func(string) error {
698 return func(req string) error {
699 for _, c := range checks {
700 if err := c(req); err != nil {
701 return err
702 }
703 }
704 return nil
705 }
706 }
707
708 type testCase struct {
709 method string
710 clen int64
711 body io.ReadCloser
712 want func(string) error
713
714
715 init func(*testCase)
716 afterReqRead func()
717 }
718
719 tests := []testCase{
720 {
721 method: "GET",
722 want: noContentLengthOrTransferEncoding,
723 },
724 {
725 method: "GET",
726 body: io.NopCloser(strings.NewReader("")),
727 want: noContentLengthOrTransferEncoding,
728 },
729 {
730 method: "GET",
731 clen: -1,
732 body: io.NopCloser(strings.NewReader("")),
733 want: noContentLengthOrTransferEncoding,
734 },
735
736 {
737 method: "GET",
738 clen: 7,
739 body: io.NopCloser(strings.NewReader("foobody")),
740 want: all(matchSubstr("Content-Length: 7"),
741 matchSubstr("foobody")),
742 },
743
744 {
745 method: "GET",
746 clen: -1,
747 body: io.NopCloser(strings.NewReader("foobody")),
748 want: all(matchSubstr("Transfer-Encoding: chunked"),
749 matchSubstr("\r\n1\r\nf\r\n"),
750 matchSubstr("oobody")),
751 },
752
753
754 {
755 method: "POST",
756 clen: -1,
757 body: io.NopCloser(strings.NewReader("foobody")),
758 want: all(matchSubstr("Transfer-Encoding: chunked"),
759 matchSubstr("foobody")),
760 },
761 {
762 method: "POST",
763 clen: -1,
764 body: io.NopCloser(strings.NewReader("")),
765 want: all(matchSubstr("Transfer-Encoding: chunked")),
766 },
767
768 {
769 method: "GET",
770 clen: -1,
771 init: func(tt *testCase) {
772 pr, pw := io.Pipe()
773 tt.afterReqRead = func() {
774 pw.Close()
775 }
776 tt.body = io.NopCloser(pr)
777 },
778 want: matchSubstr("Transfer-Encoding: chunked"),
779 },
780 }
781
782 for i, tt := range tests {
783 if tt.init != nil {
784 tt.init(&tt)
785 }
786 req := &Request{
787 Method: tt.method,
788 URL: &url.URL{
789 Scheme: "http",
790 Host: "example.com",
791 },
792 Header: make(Header),
793 ContentLength: tt.clen,
794 Body: tt.body,
795 }
796 got, err := dumpRequestOut(req, tt.afterReqRead)
797 if err != nil {
798 t.Errorf("test[%d]: %v", i, err)
799 continue
800 }
801 if err := tt.want(string(got)); err != nil {
802 t.Errorf("test[%d]: %v", i, err)
803 }
804 }
805 }
806
807 type closeChecker struct {
808 io.Reader
809 closed bool
810 }
811
812 func (rc *closeChecker) Close() error {
813 rc.closed = true
814 return nil
815 }
816
817
818
819
820 func TestRequestWriteClosesBody(t *testing.T) {
821 rc := &closeChecker{Reader: strings.NewReader("my body")}
822 req, err := NewRequest("POST", "http://foo.com/", rc)
823 if err != nil {
824 t.Fatal(err)
825 }
826 buf := new(strings.Builder)
827 if err := req.Write(buf); err != nil {
828 t.Error(err)
829 }
830 if !rc.closed {
831 t.Error("body not closed after write")
832 }
833 expected := "POST / HTTP/1.1\r\n" +
834 "Host: foo.com\r\n" +
835 "User-Agent: Go-http-client/1.1\r\n" +
836 "Transfer-Encoding: chunked\r\n\r\n" +
837 chunk("my body") +
838 chunk("")
839 if buf.String() != expected {
840 t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
841 }
842 }
843
844 func chunk(s string) string {
845 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
846 }
847
848 func mustParseURL(s string) *url.URL {
849 u, err := url.Parse(s)
850 if err != nil {
851 panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
852 }
853 return u
854 }
855
856 type writerFunc func([]byte) (int, error)
857
858 func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
859
860
861 func TestRequestWriteError(t *testing.T) {
862 failAfter, writeCount := 0, 0
863 errFail := errors.New("fake write failure")
864
865
866
867
868
869 w := struct {
870 io.ByteWriter
871 io.Writer
872 }{
873 nil,
874 writerFunc(func(p []byte) (n int, err error) {
875 writeCount++
876 if failAfter == 0 {
877 err = errFail
878 }
879 failAfter--
880 return len(p), err
881 }),
882 }
883
884 req, _ := NewRequest("GET", "http://example.com/", nil)
885 const writeCalls = 4
886 sawGood := false
887 for n := 0; n <= writeCalls+2; n++ {
888 failAfter = n
889 writeCount = 0
890 err := req.Write(w)
891 var wantErr error
892 if n < writeCalls {
893 wantErr = errFail
894 }
895 if err != wantErr {
896 t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr)
897 continue
898 }
899 if err == nil {
900 sawGood = true
901 if writeCount != writeCalls {
902 t.Fatalf("writeCalls constant is outdated in test")
903 }
904 }
905 if writeCount > writeCalls || writeCount > n+1 {
906 t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount)
907 }
908 }
909 if !sawGood {
910 t.Fatalf("writeCalls constant is outdated in test")
911 }
912 }
913
914
915
916
917
918 func dumpRequestOut(req *Request, onReadHeaders func()) ([]byte, error) {
919
920
921
922
923
924
925 var buf bytes.Buffer
926 pr, pw := io.Pipe()
927 defer pr.Close()
928 defer pw.Close()
929 dr := &delegateReader{c: make(chan io.Reader)}
930
931 t := &Transport{
932 Dial: func(net, addr string) (net.Conn, error) {
933 return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
934 },
935 }
936 defer t.CloseIdleConnections()
937
938
939 go func() {
940 req, err := ReadRequest(bufio.NewReader(pr))
941 if err == nil {
942 if onReadHeaders != nil {
943 onReadHeaders()
944 }
945
946
947 io.Copy(io.Discard, req.Body)
948 req.Body.Close()
949 }
950 dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
951 }()
952
953 _, err := t.RoundTrip(req)
954 if err != nil {
955 return nil, err
956 }
957 return buf.Bytes(), nil
958 }
959
960
961
962 type delegateReader struct {
963 c chan io.Reader
964 r io.Reader
965 }
966
967 func (r *delegateReader) Read(p []byte) (int, error) {
968 if r.r == nil {
969 r.r = <-r.c
970 }
971 return r.r.Read(p)
972 }
973
974
975 type dumpConn struct {
976 io.Writer
977 io.Reader
978 }
979
980 func (c *dumpConn) Close() error { return nil }
981 func (c *dumpConn) LocalAddr() net.Addr { return nil }
982 func (c *dumpConn) RemoteAddr() net.Addr { return nil }
983 func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
984 func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
985 func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
986
View as plain text