github.com/openshift-online/ocm-sdk-go@v0.1.473/retry/transport_wrapper_test.go (about) 1 /* 2 Copyright (c) 2021 Red Hat, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // This file contains tests for request retrying. 18 19 package retry 20 21 import ( 22 "context" 23 "crypto/tls" 24 "io" 25 "net" 26 "net/http" 27 "net/url" 28 "strings" 29 "time" 30 31 "golang.org/x/net/http2" 32 33 . "github.com/onsi/ginkgo/v2/dsl/core" // nolint 34 . "github.com/onsi/gomega" // nolint 35 . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 36 ) 37 38 var _ = Describe("Creation", func() { 39 var ctx context.Context 40 41 BeforeEach(func() { 42 ctx = context.Background() 43 }) 44 45 It("Can't be created without a logger", func() { 46 wrapper, err := NewTransportWrapper(). 47 Build(ctx) 48 Expect(err).To(HaveOccurred()) 49 Expect(wrapper).To(BeNil()) 50 message := err.Error() 51 Expect(message).To(ContainSubstring("logger")) 52 Expect(message).To(ContainSubstring("mandatory")) 53 }) 54 55 It("Can be created with positive retry limit", func() { 56 wrapper, err := NewTransportWrapper(). 57 Logger(logger). 58 Limit(10). 59 Build(ctx) 60 Expect(err).ToNot(HaveOccurred()) 61 Expect(wrapper).ToNot(BeNil()) 62 err = wrapper.Close() 63 Expect(err).ToNot(HaveOccurred()) 64 }) 65 66 It("Can be created with zero retry limit", func() { 67 wrapper, err := NewTransportWrapper(). 68 Logger(logger). 69 Limit(0). 70 Build(ctx) 71 Expect(err).ToNot(HaveOccurred()) 72 Expect(wrapper).ToNot(BeNil()) 73 err = wrapper.Close() 74 Expect(err).ToNot(HaveOccurred()) 75 }) 76 77 It("Can't be created with negative retry limit", func() { 78 wrapper, err := NewTransportWrapper(). 79 Logger(logger). 80 Limit(-1). 81 Build(ctx) 82 Expect(err).To(HaveOccurred()) 83 Expect(wrapper).To(BeNil()) 84 message := err.Error() 85 Expect(message).To(ContainSubstring("limit")) 86 Expect(message).To(ContainSubstring("-1")) 87 Expect(message).To(ContainSubstring("greater or equal than zero")) 88 }) 89 90 It("Can be created with positive retry interval", func() { 91 wrapper, err := NewTransportWrapper(). 92 Logger(logger). 93 Interval(5 * time.Second). 94 Build(ctx) 95 Expect(err).ToNot(HaveOccurred()) 96 Expect(wrapper).ToNot(BeNil()) 97 err = wrapper.Close() 98 Expect(err).ToNot(HaveOccurred()) 99 }) 100 101 It("Can't be created with zero retry interval", func() { 102 wrapper, err := NewTransportWrapper(). 103 Logger(logger). 104 Interval(0). 105 Build(ctx) 106 Expect(err).To(HaveOccurred()) 107 Expect(wrapper).To(BeNil()) 108 message := err.Error() 109 Expect(message).To(ContainSubstring("interval")) 110 Expect(message).To(ContainSubstring("0")) 111 Expect(message).To(ContainSubstring("greater than zero")) 112 }) 113 114 It("Can't be created with negative retry interval", func() { 115 wrapper, err := NewTransportWrapper(). 116 Logger(logger). 117 Interval(0). 118 Build(ctx) 119 Expect(err).To(HaveOccurred()) 120 Expect(wrapper).To(BeNil()) 121 message := err.Error() 122 Expect(message).To(ContainSubstring("interval")) 123 Expect(message).To(ContainSubstring("0")) 124 Expect(message).To(ContainSubstring("greater than zero")) 125 }) 126 127 It("Can be created with jitter between zero and one", func() { 128 wrapper, err := NewTransportWrapper(). 129 Logger(logger). 130 Jitter(0.3). 131 Build(ctx) 132 Expect(err).ToNot(HaveOccurred()) 133 Expect(wrapper).ToNot(BeNil()) 134 err = wrapper.Close() 135 Expect(err).ToNot(HaveOccurred()) 136 }) 137 138 It("Can be created with zero jitter", func() { 139 wrapper, err := NewTransportWrapper(). 140 Logger(logger). 141 Jitter(0.0). 142 Build(ctx) 143 Expect(err).ToNot(HaveOccurred()) 144 Expect(wrapper).ToNot(BeNil()) 145 err = wrapper.Close() 146 Expect(err).ToNot(HaveOccurred()) 147 }) 148 149 It("Can't be created with negative jitter", func() { 150 wrapper, err := NewTransportWrapper(). 151 Logger(logger). 152 Jitter(-1). 153 Build(ctx) 154 Expect(err).To(HaveOccurred()) 155 Expect(wrapper).To(BeNil()) 156 message := err.Error() 157 Expect(message).To(ContainSubstring("jitter")) 158 Expect(message).To(ContainSubstring("0")) 159 Expect(message).To(ContainSubstring("between zero and one")) 160 }) 161 162 It("Can't be created with jitter greater than one", func() { 163 wrapper, err := NewTransportWrapper(). 164 Logger(logger). 165 Jitter(2). 166 Build(ctx) 167 Expect(err).To(HaveOccurred()) 168 Expect(wrapper).To(BeNil()) 169 message := err.Error() 170 Expect(message).To(ContainSubstring("jitter")) 171 Expect(message).To(ContainSubstring("2")) 172 Expect(message).To(ContainSubstring("between zero and one")) 173 }) 174 }) 175 176 var _ = Describe("Server error", func() { 177 var ctx context.Context 178 179 BeforeEach(func() { 180 ctx = context.Background() 181 }) 182 183 When("Retry enabled", func() { 184 It("Retries 503 without request body", func() { 185 // Create a transport that returns a 503 error for the first request and 200 186 // for the second: 187 transport := CombineTransports( 188 TextTransport(http.StatusServiceUnavailable, `ko`), 189 JSONTransport(http.StatusOK, `{ "ok": true }`), 190 ) 191 192 // Wrap the transport: 193 wrapper, err := NewTransportWrapper(). 194 Logger(logger). 195 Interval(100 * time.Millisecond). 196 Build(ctx) 197 Expect(err).ToNot(HaveOccurred()) 198 defer func() { 199 err = wrapper.Close() 200 Expect(err).ToNot(HaveOccurred()) 201 }() 202 203 // Create the client: 204 client := &http.Client{ 205 Transport: wrapper.Wrap(transport), 206 Timeout: 10 * time.Second, 207 } 208 209 // Send the request: 210 response, err := client.Get("http://api.example.com/mypath") 211 Expect(err).ToNot(HaveOccurred()) 212 Expect(response).ToNot(BeNil()) 213 Expect(response.StatusCode).To(Equal(http.StatusOK)) 214 body, err := io.ReadAll(response.Body) 215 Expect(err).ToNot(HaveOccurred()) 216 Expect(body).To(MatchJSON(`{ "ok": true }`)) 217 }) 218 219 It("Retries 503 with request body", func() { 220 // Create a transport that returns a 503 error for the first request and 200 221 // for the second: 222 transport := CombineTransports( 223 TextTransport(http.StatusServiceUnavailable, `ko`), 224 JSONTransport(http.StatusOK, `{ "ok": true }`), 225 ) 226 227 // Wrap the transport: 228 wrapper, err := NewTransportWrapper(). 229 Logger(logger). 230 Interval(100 * time.Millisecond). 231 Build(ctx) 232 Expect(err).ToNot(HaveOccurred()) 233 defer func() { 234 err = wrapper.Close() 235 Expect(err).ToNot(HaveOccurred()) 236 }() 237 238 // Create the client: 239 client := &http.Client{ 240 Transport: wrapper.Wrap(transport), 241 Timeout: 10 * time.Second, 242 } 243 244 // Send the request: 245 response, err := client.Post( 246 "http://api.example.com/mypath", 247 "application/json", 248 strings.NewReader(`{}`), 249 ) 250 Expect(err).ToNot(HaveOccurred()) 251 Expect(response).ToNot(BeNil()) 252 Expect(response.StatusCode).To(Equal(http.StatusOK)) 253 body, err := io.ReadAll(response.Body) 254 Expect(err).ToNot(HaveOccurred()) 255 Expect(body).To(MatchJSON(`{ "ok": true }`)) 256 }) 257 258 It("Retries 429 without request body", func() { 259 // Create a transport that returns a 429 error for the first request and 200 260 // for the second: 261 transport := CombineTransports( 262 JSONTransport(http.StatusTooManyRequests, `{ "ok": false }`), 263 JSONTransport(http.StatusOK, `{ "ok": true }`), 264 ) 265 266 // Wrap the transport: 267 wrapper, err := NewTransportWrapper(). 268 Logger(logger). 269 Interval(100 * time.Millisecond). 270 Build(ctx) 271 Expect(err).ToNot(HaveOccurred()) 272 defer func() { 273 err = wrapper.Close() 274 Expect(err).ToNot(HaveOccurred()) 275 }() 276 277 // Create the client: 278 client := &http.Client{ 279 Transport: wrapper.Wrap(transport), 280 Timeout: 10 * time.Second, 281 } 282 283 // Send the request: 284 response, err := client.Get("http://api.example.com/mypath") 285 Expect(err).ToNot(HaveOccurred()) 286 Expect(response).ToNot(BeNil()) 287 Expect(response.StatusCode).To(Equal(http.StatusOK)) 288 body, err := io.ReadAll(response.Body) 289 Expect(err).ToNot(HaveOccurred()) 290 Expect(body).To(MatchJSON(`{ "ok": true }`)) 291 }) 292 293 It("Retries 429 with request body", func() { 294 // Create a transport that returns a 429 error for the first request and 200 295 // for the second: 296 transport := CombineTransports( 297 JSONTransport(http.StatusTooManyRequests, `{ "ok": false }`), 298 JSONTransport(http.StatusOK, `{ "ok": true }`), 299 ) 300 301 // Wrap the transport: 302 wrapper, err := NewTransportWrapper(). 303 Logger(logger). 304 Interval(100 * time.Millisecond). 305 Build(ctx) 306 Expect(err).ToNot(HaveOccurred()) 307 defer func() { 308 err = wrapper.Close() 309 Expect(err).ToNot(HaveOccurred()) 310 }() 311 312 // Create the client: 313 client := &http.Client{ 314 Transport: wrapper.Wrap(transport), 315 Timeout: 10 * time.Second, 316 } 317 318 // Send the request: 319 response, err := client.Post( 320 "http://api.example.com/mypath", 321 "application/json", 322 strings.NewReader(`{}`), 323 ) 324 Expect(err).ToNot(HaveOccurred()) 325 Expect(response).ToNot(BeNil()) 326 Expect(response.StatusCode).To(Equal(http.StatusOK)) 327 body, err := io.ReadAll(response.Body) 328 Expect(err).ToNot(HaveOccurred()) 329 Expect(body).To(MatchJSON(`{ "ok": true }`)) 330 }) 331 }) 332 333 When("Retry disabled", func() { 334 It("Doesn't retry 503", func() { 335 // Create a transport that returns a 503 error for the first request and 200 336 // for the second: 337 transport := CombineTransports( 338 TextTransport(http.StatusServiceUnavailable, `ko`), 339 JSONTransport(http.StatusOK, `{ "ok": true }`), 340 ) 341 342 // Wrap the transport: 343 wrapper, err := NewTransportWrapper(). 344 Logger(logger). 345 Limit(0). 346 Interval(100 * time.Millisecond). 347 Build(ctx) 348 Expect(err).ToNot(HaveOccurred()) 349 defer func() { 350 err = wrapper.Close() 351 Expect(err).ToNot(HaveOccurred()) 352 }() 353 354 // Create the client: 355 client := &http.Client{ 356 Transport: wrapper.Wrap(transport), 357 Timeout: 10 * time.Second, 358 } 359 360 // Send the request: 361 response, err := client.Get("http://api.example.com/mypath") 362 Expect(err).ToNot(HaveOccurred()) 363 Expect(response).ToNot(BeNil()) 364 Expect(response.StatusCode).To(Equal(http.StatusServiceUnavailable)) 365 body, err := io.ReadAll(response.Body) 366 Expect(err).ToNot(HaveOccurred()) 367 Expect(string(body)).To(Equal(`ko`)) 368 }) 369 370 It("Doesn't retry 429", func() { 371 // Create a transport that returns a 429 error for the first request and 200 372 // for the second: 373 transport := CombineTransports( 374 JSONTransport(http.StatusTooManyRequests, `{ "ok": false }`), 375 JSONTransport(http.StatusOK, `{ "ok": true }`), 376 ) 377 378 // Wrap the transport: 379 wrapper, err := NewTransportWrapper(). 380 Logger(logger). 381 Limit(0). 382 Interval(100 * time.Millisecond). 383 Build(ctx) 384 Expect(err).ToNot(HaveOccurred()) 385 defer func() { 386 err = wrapper.Close() 387 Expect(err).ToNot(HaveOccurred()) 388 }() 389 390 // Create the client: 391 client := &http.Client{ 392 Transport: wrapper.Wrap(transport), 393 Timeout: 10 * time.Second, 394 } 395 396 // Send the request: 397 response, err := client.Get("http://api.example.com/mypath") 398 Expect(err).ToNot(HaveOccurred()) 399 Expect(response).ToNot(BeNil()) 400 Expect(response.StatusCode).To(Equal(http.StatusTooManyRequests)) 401 body, err := io.ReadAll(response.Body) 402 Expect(err).ToNot(HaveOccurred()) 403 Expect(body).To(MatchJSON(`{ "ok": false }`)) 404 }) 405 }) 406 }) 407 408 var _ = Describe("Protocol error", func() { 409 var ctx context.Context 410 var listener net.Listener 411 var address string 412 var transport http.RoundTripper 413 414 BeforeEach(func() { 415 // Create a context: 416 ctx = context.Background() 417 418 // Create a listener: 419 listener, address = Listen() 420 421 // Create the basic transport: 422 transport = &http.Transport{ 423 TLSClientConfig: &tls.Config{ 424 InsecureSkipVerify: true, 425 }, 426 ForceAttemptHTTP2: true, 427 } 428 }) 429 430 AfterEach(func() { 431 // Close the listener: 432 err := listener.Close() 433 Expect(err).ToNot(HaveOccurred()) 434 }) 435 436 When("Retry is enabled", func() { 437 It("Tolerates protocol error", func() { 438 var err error 439 440 // Run the HTTP/2 server: 441 go func() { 442 defer GinkgoRecover() 443 444 // Reject the first connection: 445 first := Accept(listener) 446 Reject(first) 447 448 // Accept the second connection: 449 second := Accept(listener) 450 Serve(second, func(w http.ResponseWriter, r *http.Request) { 451 w.Header().Set("Content-Type", "application/json") 452 w.WriteHeader(http.StatusOK) 453 _, err := w.Write([]byte("{}")) 454 Expect(err).ToNot(HaveOccurred()) 455 }) 456 }() 457 458 // Wrap the transport: 459 wrapper, err := NewTransportWrapper(). 460 Logger(logger). 461 Interval(100 * time.Millisecond). 462 Build(ctx) 463 Expect(err).ToNot(HaveOccurred()) 464 defer func() { 465 err = wrapper.Close() 466 Expect(err).ToNot(HaveOccurred()) 467 }() 468 client := &http.Client{ 469 Transport: wrapper.Wrap(transport), 470 Timeout: 10 * time.Second, 471 } 472 473 // Send the request: 474 response, err := client.Get(address) 475 Expect(err).ToNot(HaveOccurred()) 476 Expect(response).ToNot(BeNil()) 477 Expect(response.StatusCode).To(Equal(http.StatusOK)) 478 body, err := io.ReadAll(response.Body) 479 Expect(err).ToNot(HaveOccurred()) 480 Expect(body).To(MatchJSON("{}")) 481 }) 482 483 It("Honours retry limit", func() { 484 // Run the HTTP/2 server. 485 go func() { 486 defer GinkgoRecover() 487 488 // Reject the first four connections: 489 conn := Accept(listener) 490 Reject(conn) 491 conn = Accept(listener) 492 Reject(conn) 493 conn = Accept(listener) 494 Reject(conn) 495 conn = Accept(listener) 496 Reject(conn) 497 }() 498 499 // Wrap the transport: 500 wrapper, err := NewTransportWrapper(). 501 Logger(logger). 502 Limit(3). 503 Interval(100 * time.Millisecond). 504 Jitter(0). 505 Build(ctx) 506 Expect(err).ToNot(HaveOccurred()) 507 defer func() { 508 err = wrapper.Close() 509 Expect(err).ToNot(HaveOccurred()) 510 }() 511 client := &http.Client{ 512 Transport: wrapper.Wrap(transport), 513 Timeout: 10 * time.Second, 514 } 515 516 // Send the request: 517 response, err := client.Get(address) 518 Expect(err).To(HaveOccurred()) 519 Expect(response).To(BeNil()) 520 message := err.Error() 521 Expect(message).To(ContainSubstring("PROTOCOL_ERROR")) 522 }) 523 524 It("Honours retry interval", func() { 525 // Run the HTTP/2 server. 526 go func() { 527 defer GinkgoRecover() 528 529 // We will use this to calculate the time between requests: 530 var start time.Time 531 var elapsed time.Duration 532 533 // Reject the first connection: 534 conn := Accept(listener) 535 Reject(conn) 536 start = time.Now() 537 538 // Reject the second connection an verify that it was sent after 539 // waiting the configured interval: 540 conn = Accept(listener) 541 Reject(conn) 542 elapsed = time.Since(start) 543 start = time.Now() 544 Expect(elapsed).To(BeNumerically( 545 "~", 546 100*time.Millisecond, 547 50*time.Millisecond, 548 )) 549 550 // Reject the third connection and verify that it was sent after 551 // waiting the double of the configured interval: 552 conn = Accept(listener) 553 Reject(conn) 554 elapsed = time.Since(start) 555 start = time.Now() 556 Expect(elapsed).To(BeNumerically( 557 "~", 558 200*time.Millisecond, 559 50*time.Millisecond, 560 )) 561 562 // Reject the fourth connection and verify that it was sent after 563 // waiting the four times the configured interval: 564 conn = Accept(listener) 565 Reject(conn) 566 elapsed = time.Since(start) 567 Expect(elapsed).To(BeNumerically( 568 "~", 569 400*time.Millisecond, 570 50*time.Millisecond, 571 )) 572 }() 573 574 // Wrap the transport setting the jitter to zero so that we can reliably 575 // measure retry times: 576 wrapper, err := NewTransportWrapper(). 577 Logger(logger). 578 Limit(3). 579 Interval(100 * time.Millisecond). 580 Jitter(0). 581 Build(ctx) 582 Expect(err).ToNot(HaveOccurred()) 583 defer func() { 584 err = wrapper.Close() 585 Expect(err).ToNot(HaveOccurred()) 586 }() 587 client := &http.Client{ 588 Transport: wrapper.Wrap(transport), 589 Timeout: 10 * time.Second, 590 } 591 592 // Send the request: 593 response, err := client.Get(address) 594 Expect(err).To(HaveOccurred()) 595 Expect(response).To(BeNil()) 596 message := err.Error() 597 Expect(message).To(ContainSubstring("PROTOCOL_ERROR")) 598 }) 599 }) 600 601 When("Retry is disabled", func() { 602 It("Doesn't tolerate error", func() { 603 // Run the HTTP/2 server: 604 go func() { 605 defer GinkgoRecover() 606 conn := Accept(listener) 607 Reject(conn) 608 }() 609 610 // Wrap the transport: 611 wrapper, err := NewTransportWrapper(). 612 Logger(logger). 613 Limit(0). 614 Interval(100 * time.Millisecond). 615 Jitter(0). 616 Build(ctx) 617 Expect(err).ToNot(HaveOccurred()) 618 defer func() { 619 err = wrapper.Close() 620 Expect(err).ToNot(HaveOccurred()) 621 }() 622 client := &http.Client{ 623 Transport: wrapper.Wrap(transport), 624 Timeout: 10 * time.Second, 625 } 626 627 // Send the request: 628 response, err := client.Get(address) 629 Expect(err).To(HaveOccurred()) 630 Expect(response).To(BeNil()) 631 message := err.Error() 632 Expect(message).To(ContainSubstring("PROTOCOL_ERROR")) 633 }) 634 }) 635 }) 636 637 var _ = It("Tolerates connection reset by peer", func() { 638 var err error 639 640 // Create a context: 641 ctx := context.Background() 642 643 // Create a listener: 644 listener, address := Listen() 645 defer func() { 646 err = listener.Close() 647 Expect(err).ToNot(HaveOccurred()) 648 }() 649 650 // Run the server: 651 go func() { 652 defer GinkgoRecover() 653 654 // Accept the first connection and close it inmediately. This will trigger 655 // the `connection reset by peer` error in the client. 656 first := Accept(listener) 657 Serve(first, func(w http.ResponseWriter, r *http.Request) { 658 w.Header().Set("Content-Type", "application/json") 659 w.WriteHeader(http.StatusOK) 660 _, err := w.Write([]byte("{")) 661 Expect(err).ToNot(HaveOccurred()) 662 err = first.Close() 663 Expect(err).ToNot(HaveOccurred()) 664 }) 665 666 // Accept the second connection and handle it correctly. 667 second := Accept(listener) 668 Serve(second, func(w http.ResponseWriter, r *http.Request) { 669 w.Header().Set("Content-Type", "application/json") 670 w.WriteHeader(http.StatusOK) 671 _, err := w.Write([]byte("{}")) 672 Expect(err).ToNot(HaveOccurred()) 673 }) 674 }() 675 676 // Wrap the transport: 677 wrapper, err := NewTransportWrapper(). 678 Logger(logger). 679 Interval(100 * time.Millisecond). 680 Jitter(0). 681 Build(ctx) 682 Expect(err).ToNot(HaveOccurred()) 683 defer func() { 684 err = wrapper.Close() 685 Expect(err).ToNot(HaveOccurred()) 686 }() 687 client := &http.Client{ 688 Transport: wrapper.Wrap(&http.Transport{ 689 TLSClientConfig: &tls.Config{ 690 InsecureSkipVerify: true, 691 }, 692 ForceAttemptHTTP2: true, 693 }), 694 Timeout: 10 * time.Second, 695 } 696 697 // Send the request: 698 response, err := client.Get(address) 699 Expect(err).ToNot(HaveOccurred()) 700 Expect(response).ToNot(BeNil()) 701 Expect(response.StatusCode).To(Equal(http.StatusOK)) 702 body, err := io.ReadAll(response.Body) 703 Expect(err).ToNot(HaveOccurred()) 704 Expect(body).To(MatchJSON("{}")) 705 }) 706 707 var _ = It("Puts do NOT tolerate connection reset by peer", func() { 708 var err error 709 710 // Create a context: 711 ctx := context.Background() 712 713 // Create a listener: 714 listener, address := Listen() 715 defer func() { 716 err = listener.Close() 717 Expect(err).ToNot(HaveOccurred()) 718 }() 719 720 // Run the server: 721 go func() { 722 defer GinkgoRecover() 723 724 // Accept the first connection and close it inmediately. This will trigger 725 // the `connection reset by peer` error in the client. 726 first := Accept(listener) 727 Serve(first, func(w http.ResponseWriter, r *http.Request) { 728 w.Header().Set("Content-Type", "application/json") 729 w.WriteHeader(http.StatusOK) 730 _, err := w.Write([]byte("{")) 731 Expect(err).ToNot(HaveOccurred()) 732 err = first.Close() 733 Expect(err).ToNot(HaveOccurred()) 734 }) 735 }() 736 737 // Wrap the transport: 738 wrapper, err := NewTransportWrapper(). 739 Logger(logger). 740 Interval(100 * time.Millisecond). 741 Jitter(0). 742 Build(ctx) 743 Expect(err).ToNot(HaveOccurred()) 744 defer func() { 745 err = wrapper.Close() 746 Expect(err).ToNot(HaveOccurred()) 747 }() 748 client := &http.Client{ 749 Transport: wrapper.Wrap(&http.Transport{ 750 TLSClientConfig: &tls.Config{ 751 InsecureSkipVerify: true, 752 }, 753 ForceAttemptHTTP2: true, 754 }), 755 Timeout: 10 * time.Second, 756 } 757 758 // Send the request: 759 response, err := client.Post(address, "", nil) 760 Expect(err).To(HaveOccurred()) 761 Expect(response).To(BeNil()) 762 }) 763 764 var _ = It("Doesn't change request body object", func() { 765 var err error 766 767 // Create a context: 768 ctx := context.Background() 769 770 // Prepare the server: 771 server := MakeTCPServer() 772 defer server.Close() 773 server.AppendHandlers( 774 RespondWithJSON(http.StatusOK, `{}`), 775 ) 776 777 // Wrap the transport: 778 wrapper, err := NewTransportWrapper(). 779 Logger(logger). 780 Jitter(0). 781 Build(ctx) 782 Expect(err).ToNot(HaveOccurred()) 783 defer func() { 784 err = wrapper.Close() 785 Expect(err).ToNot(HaveOccurred()) 786 }() 787 client := &http.Client{ 788 Transport: wrapper.Wrap(&http.Transport{}), 789 Timeout: 10 * time.Second, 790 } 791 792 // Send the request: 793 body := io.NopCloser(strings.NewReader(`{}`)) 794 addr, err := url.Parse(server.URL()) 795 Expect(err).ToNot(HaveOccurred()) 796 request := &http.Request{ 797 Method: http.MethodGet, 798 URL: addr, 799 Body: body, 800 } 801 _, err = client.Do(request) 802 Expect(err).ToNot(HaveOccurred()) 803 Expect(request.Body).To(Equal(body)) 804 }) 805 806 var _ = It("Tolerates unepected EOF", func() { 807 var err error 808 809 // Create a context: 810 ctx := context.Background() 811 812 // Create a listener: 813 listener, address := Listen() 814 defer func() { 815 err = listener.Close() 816 Expect(err).ToNot(HaveOccurred()) 817 }() 818 819 // Run the server: 820 go func() { 821 defer GinkgoRecover() 822 823 // Accept the first connection and handle it with a server that will close 824 // the it after sending only half of the response body. This will trigger 825 // the `EOF` error in the client. 826 first := Accept(listener) 827 Serve(first, func(w http.ResponseWriter, r *http.Request) { 828 w.Header().Set("Content-Type", "application/json") 829 w.WriteHeader(http.StatusOK) 830 _, err := w.Write([]byte("{")) 831 Expect(err).ToNot(HaveOccurred()) 832 err = first.Close() 833 Expect(err).ToNot(HaveOccurred()) 834 }) 835 836 // Accept the second connection and handle it correctly. 837 second := Accept(listener) 838 Serve(second, func(w http.ResponseWriter, r *http.Request) { 839 w.Header().Set("Content-Type", "application/json") 840 w.WriteHeader(http.StatusOK) 841 _, err := w.Write([]byte("{}")) 842 Expect(err).ToNot(HaveOccurred()) 843 }) 844 }() 845 846 // Wrap the transport: 847 wrapper, err := NewTransportWrapper(). 848 Logger(logger). 849 Interval(100 * time.Millisecond). 850 Jitter(0). 851 Build(ctx) 852 Expect(err).ToNot(HaveOccurred()) 853 defer func() { 854 err = wrapper.Close() 855 Expect(err).ToNot(HaveOccurred()) 856 }() 857 client := &http.Client{ 858 Transport: wrapper.Wrap(&http.Transport{ 859 TLSClientConfig: &tls.Config{ 860 InsecureSkipVerify: true, 861 }, 862 ForceAttemptHTTP2: true, 863 }), 864 Timeout: 10 * time.Second, 865 } 866 867 // Send the request: 868 response, err := client.Get(address) 869 Expect(err).ToNot(HaveOccurred()) 870 Expect(response).ToNot(BeNil()) 871 Expect(response.StatusCode).To(Equal(http.StatusOK)) 872 body, err := io.ReadAll(response.Body) 873 Expect(err).ToNot(HaveOccurred()) 874 Expect(body).To(MatchJSON("{}")) 875 }) 876 877 // Listen creates an HTTP/2 listener. 878 func Listen() (listener net.Listener, address string) { 879 // Create a TLS listener that will be used to process incoming requests 880 // simulating an HTTP/2 server: 881 listener, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{ 882 Certificates: []tls.Certificate{ 883 LocalhostCertificate(), 884 }, 885 NextProtos: []string{ 886 http2.NextProtoTLS, 887 }, 888 }) 889 Expect(err).ToNot(HaveOccurred()) 890 891 // Calculate the listener URL: 892 address = "https://" + listener.Addr().String() 893 894 return 895 } 896 897 // Accept accepts an HTTP/2 connection. 898 func Accept(listener net.Listener) net.Conn { 899 // Accept the connection and complete the TLS handshake. 900 conn, err := listener.Accept() 901 Expect(err).ToNot(HaveOccurred()) 902 err = conn.(*tls.Conn).Handshake() 903 Expect(err).ToNot(HaveOccurred()) 904 905 // Return the connection: 906 return conn 907 } 908 909 // Reject sends an HTTP/2 go away frame to the given connection and then closes it. 910 func Reject(conn net.Conn) { 911 // Read the HTTP2 connection preface, otherwise the client won't reach the part of 912 // the code where the protocol error is detected: 913 buffer := make([]byte, len(http2.ClientPreface)) 914 count, err := conn.Read(buffer) 915 Expect(err).ToNot(HaveOccurred()) 916 Expect(count).To(Equal(len(http2.ClientPreface))) 917 Expect(string(buffer)).To(Equal(http2.ClientPreface)) 918 919 // Send the go away frame: 920 framer := http2.NewFramer(conn, conn) 921 err = framer.WriteGoAway(0, http2.ErrCodeStreamClosed, nil) 922 Expect(err).ToNot(HaveOccurred()) 923 924 // Close the connection: 925 err = conn.Close() 926 Expect(err).ToNot(HaveOccurred()) 927 } 928 929 // Serve handles request received from the given connection using a real HTTP/2 server and the given 930 // handler function. 931 func Serve(conn net.Conn, handler http.HandlerFunc) { 932 server := &http2.Server{} 933 server.ServeConn(conn, &http2.ServeConnOpts{ 934 Handler: handler, 935 }) 936 }