github.com/Jeffail/benthos/v3@v3.65.0/lib/input/http_client_test.go (about) 1 package input 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "mime/multipart" 8 "net/http" 9 "net/http/httptest" 10 "net/textproto" 11 "sync" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/Jeffail/benthos/v3/lib/log" 17 "github.com/Jeffail/benthos/v3/lib/metrics" 18 "github.com/Jeffail/benthos/v3/lib/response" 19 "github.com/Jeffail/benthos/v3/lib/types" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestHTTPClientGET(t *testing.T) { 25 inputs := []string{ 26 "foo1", 27 "foo2", 28 "foo3", 29 "foo4", 30 "foo5", 31 } 32 33 var reqCount uint32 34 index := 0 35 36 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 if exp, act := "GET", r.Method; exp != act { 38 t.Errorf("Wrong method: %v != %v", act, exp) 39 } 40 atomic.AddUint32(&reqCount, 1) 41 w.Write([]byte(inputs[index%len(inputs)])) 42 index++ 43 })) 44 defer ts.Close() 45 46 conf := NewConfig() 47 conf.HTTPClient.URL = ts.URL + "/testpost" 48 conf.HTTPClient.Retry = "1ms" 49 50 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 51 if err != nil { 52 t.Error(err) 53 return 54 } 55 56 var tr types.Transaction 57 var open bool 58 59 for _, expPart := range inputs { 60 select { 61 case tr, open = <-h.TransactionChan(): 62 if !open { 63 t.Fatal("Chan not open") 64 } 65 if exp, act := 1, tr.Payload.Len(); exp != act { 66 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 67 } 68 if exp, act := expPart, string(tr.Payload.Get(0).Get()); exp != act { 69 t.Errorf("Wrong part: %v != %v", act, exp) 70 } 71 case <-time.After(time.Second): 72 t.Errorf("Action timed out") 73 } 74 75 select { 76 case tr.ResponseChan <- response.NewAck(): 77 case <-time.After(time.Second): 78 t.Errorf("Action timed out") 79 } 80 } 81 82 h.CloseAsync() 83 if err := h.WaitForClose(time.Second); err != nil { 84 t.Error(err) 85 } 86 87 if exp, act := uint32(len(inputs)), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act { 88 t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp) 89 } 90 } 91 92 func TestHTTPClientPagination(t *testing.T) { 93 var paths []string 94 var pathsLock sync.Mutex 95 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 96 fmt.Fprintf(w, "hello%v", len(paths)) 97 pathsLock.Lock() 98 paths = append(paths, r.URL.Path) 99 pathsLock.Unlock() 100 })) 101 defer ts.Close() 102 103 conf := NewConfig() 104 conf.HTTPClient.URL = ts.URL + "/${!content()}" 105 conf.HTTPClient.Retry = "1ms" 106 107 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 108 require.NoError(t, err) 109 110 var tr types.Transaction 111 var open bool 112 113 for i := 0; i < 10; i++ { 114 exp := fmt.Sprintf("hello%v", i) 115 select { 116 case tr, open = <-h.TransactionChan(): 117 require.True(t, open) 118 require.Equal(t, 1, tr.Payload.Len()) 119 assert.Equal(t, exp, string(tr.Payload.Get(0).Get())) 120 case <-time.After(time.Second): 121 t.Fatal("Action timed out") 122 } 123 select { 124 case tr.ResponseChan <- response.NewAck(): 125 case <-time.After(time.Second): 126 t.Fatal("Action timed out") 127 } 128 } 129 130 h.CloseAsync() 131 assert.NoError(t, h.WaitForClose(time.Second)) 132 133 pathsLock.Lock() 134 defer pathsLock.Unlock() 135 for i, url := range paths { 136 expURL := "/" 137 if i > 0 { 138 expURL = fmt.Sprintf("/hello%v", i-1) 139 } 140 assert.Equal(t, expURL, url) 141 } 142 } 143 144 func TestHTTPClientGETError(t *testing.T) { 145 t.Parallel() 146 147 requestChan := make(chan struct{}) 148 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 149 http.Error(w, "nah", http.StatusBadGateway) 150 select { 151 case requestChan <- struct{}{}: 152 default: 153 } 154 })) 155 defer ts.Close() 156 157 conf := NewConfig() 158 conf.HTTPClient.URL = ts.URL + "/testpost" 159 conf.HTTPClient.Retry = "1ms" 160 161 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 162 if err != nil { 163 t.Error(err) 164 return 165 } 166 167 for i := 0; i < 3; i++ { 168 select { 169 case <-requestChan: 170 case <-time.After(time.Second): 171 t.Error("Timed out") 172 } 173 } 174 175 h.CloseAsync() 176 if err := h.WaitForClose(time.Second); err != nil { 177 t.Error(err) 178 } 179 } 180 181 func TestHTTPClientGETNotExist(t *testing.T) { 182 t.Parallel() 183 184 conf := NewConfig() 185 conf.HTTPClient.URL = "jgljksdfhjgkldfjglkf" 186 conf.HTTPClient.Retry = "1ms" 187 188 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 189 if err != nil { 190 t.Error(err) 191 return 192 } 193 194 <-time.After(time.Millisecond * 500) 195 196 h.CloseAsync() 197 if err := h.WaitForClose(time.Second); err != nil { 198 t.Error(err) 199 } 200 } 201 202 func TestHTTPClientGETStreamNotExist(t *testing.T) { 203 t.Parallel() 204 205 conf := NewConfig() 206 conf.HTTPClient.URL = "jgljksdfhjgkldfjglkf" 207 conf.HTTPClient.Retry = "1ms" 208 conf.HTTPClient.Stream.Enabled = true 209 210 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 211 if err != nil { 212 t.Error(err) 213 return 214 } 215 216 <-time.After(time.Millisecond * 500) 217 218 h.CloseAsync() 219 if err := h.WaitForClose(time.Second * 5); err != nil { 220 t.Error(err) 221 } 222 } 223 224 func TestHTTPClientGETStreamError(t *testing.T) { 225 t.Parallel() 226 227 requestChan := make(chan struct{}) 228 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 229 http.Error(w, "nah", http.StatusBadGateway) 230 select { 231 case requestChan <- struct{}{}: 232 default: 233 } 234 })) 235 defer ts.Close() 236 237 conf := NewConfig() 238 conf.HTTPClient.URL = ts.URL + "/testpost" 239 conf.HTTPClient.Retry = "1ms" 240 conf.HTTPClient.Stream.Enabled = true 241 242 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 243 if err != nil { 244 t.Error(err) 245 return 246 } 247 248 select { 249 case <-requestChan: 250 case <-time.After(time.Second): 251 t.Error("Timed out") 252 } 253 254 h.CloseAsync() 255 if err := h.WaitForClose(time.Second * 2); err != nil { 256 t.Error(err) 257 } 258 } 259 260 func TestHTTPClientPOST(t *testing.T) { 261 var reqCount uint32 262 inputs := []string{ 263 "foo1", 264 "foo2", 265 "foo3", 266 "foo4", 267 "foo5", 268 } 269 270 index := 0 271 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 272 if exp, act := "POST", r.Method; exp != act { 273 t.Errorf("Wrong method: %v != %v", act, exp) 274 } 275 defer r.Body.Close() 276 277 bodyBytes, err := io.ReadAll(r.Body) 278 if err != nil { 279 t.Error(err) 280 } 281 282 if exp, act := "foobar", string(bodyBytes); exp != act { 283 t.Errorf("Wrong post body: %v != %v", act, exp) 284 } 285 286 atomic.AddUint32(&reqCount, 1) 287 w.Write([]byte(inputs[index%len(inputs)])) 288 index++ 289 })) 290 defer ts.Close() 291 292 conf := NewConfig() 293 conf.HTTPClient.URL = ts.URL + "/testpost" 294 conf.HTTPClient.Verb = "POST" 295 conf.HTTPClient.Payload = "foobar" 296 conf.HTTPClient.Retry = "1ms" 297 298 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 299 if err != nil { 300 t.Error(err) 301 return 302 } 303 304 for _, expPart := range inputs { 305 var ts types.Transaction 306 var open bool 307 308 select { 309 case ts, open = <-h.TransactionChan(): 310 if !open { 311 t.Fatal("Chan not open") 312 } 313 if exp, act := 1, ts.Payload.Len(); exp != act { 314 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 315 } 316 if exp, act := expPart, string(ts.Payload.Get(0).Get()); exp != act { 317 t.Errorf("Wrong part: %v != %v", act, exp) 318 } 319 case <-time.After(time.Second): 320 t.Errorf("Action timed out") 321 } 322 323 select { 324 case ts.ResponseChan <- response.NewAck(): 325 case <-time.After(time.Second): 326 t.Errorf("Action timed out") 327 } 328 } 329 330 h.CloseAsync() 331 if err := h.WaitForClose(time.Second); err != nil { 332 t.Error(err) 333 } 334 335 if exp, act := uint32(len(inputs)), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act { 336 t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp) 337 } 338 } 339 340 func TestHTTPClientGETMultipart(t *testing.T) { 341 var reqCount uint32 342 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 343 if exp, act := "GET", r.Method; exp != act { 344 t.Errorf("Wrong method: %v != %v", act, exp) 345 } 346 atomic.AddUint32(&reqCount, 1) 347 348 body := &bytes.Buffer{} 349 writer := multipart.NewWriter(body) 350 351 parts := []string{ 352 "hello", "http", "world", 353 } 354 for _, p := range parts { 355 var err error 356 var part io.Writer 357 if part, err = writer.CreatePart(textproto.MIMEHeader{ 358 "Content-Type": []string{"application/octet-stream"}, 359 }); err == nil { 360 _, err = io.Copy(part, bytes.NewReader([]byte(p))) 361 } 362 if err != nil { 363 t.Fatal(err) 364 } 365 } 366 367 writer.Close() 368 w.Header().Add("Content-Type", writer.FormDataContentType()) 369 w.Write(body.Bytes()) 370 })) 371 defer ts.Close() 372 373 conf := NewConfig() 374 conf.HTTPClient.URL = ts.URL + "/testpost" 375 conf.HTTPClient.Retry = "1ms" 376 377 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 378 if err != nil { 379 t.Error(err) 380 return 381 } 382 383 var tr types.Transaction 384 var open bool 385 386 select { 387 case tr, open = <-h.TransactionChan(): 388 if !open { 389 t.Fatal("Chan not open") 390 } 391 if exp, act := 3, tr.Payload.Len(); exp != act { 392 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 393 } 394 if exp, act := "hello", string(tr.Payload.Get(0).Get()); exp != act { 395 t.Errorf("Wrong part: %v != %v", act, exp) 396 } 397 if exp, act := "http", string(tr.Payload.Get(1).Get()); exp != act { 398 t.Errorf("Wrong part: %v != %v", act, exp) 399 } 400 if exp, act := "world", string(tr.Payload.Get(2).Get()); exp != act { 401 t.Errorf("Wrong part: %v != %v", act, exp) 402 } 403 case <-time.After(time.Second): 404 t.Errorf("Action timed out") 405 } 406 407 select { 408 case tr.ResponseChan <- response.NewAck(): 409 case <-time.After(time.Second): 410 t.Errorf("Action timed out") 411 } 412 h.CloseAsync() 413 414 if err := h.WaitForClose(time.Second); err != nil { 415 t.Error(err) 416 } 417 418 if exp, act := uint32(1), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act { 419 t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp) 420 } 421 } 422 423 func TestHTTPClientGETMultipartLoop(t *testing.T) { 424 tests := [][]string{ 425 { 426 "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 427 "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", 428 "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 429 }, 430 { 431 "Tristique et egestas quis ipsum suspendisse ultrices. Quis enim lobortis scelerisque fermentum dui faucibus.", 432 }, 433 { 434 "Lorem donec massa sapien faucibus et molestie ac. Lectus proin nibh nisl condimentum id venenatis a.", 435 "Ultricies mi eget mauris pharetra et ultrices neque ornare aenean.", 436 }, 437 { 438 "Amet tellus cras adipiscing enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Venenatis cras sed felis eget velit aliquet sagittis.", 439 "Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.", 440 "Odio ut sem nulla pharetra diam sit. Neque vitae tempus quam pellentesque nec nam aliquam sem.", 441 "Scelerisque eu ultrices vitae auctor eu augue. Ut eu sem integer vitae justo eget. Purus in massa tempor nec feugiat nisl pretium fusce id.", 442 }, 443 } 444 445 var reqMut sync.Mutex 446 447 var index int 448 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 449 reqMut.Lock() 450 defer reqMut.Unlock() 451 452 if exp, act := "GET", r.Method; exp != act { 453 t.Errorf("Wrong method: %v != %v", act, exp) 454 } 455 456 body := &bytes.Buffer{} 457 writer := multipart.NewWriter(body) 458 459 parts := tests[index%len(tests)] 460 for _, p := range parts { 461 var err error 462 var part io.Writer 463 if part, err = writer.CreatePart(textproto.MIMEHeader{ 464 "Content-Type": []string{"application/octet-stream"}, 465 }); err == nil { 466 _, err = io.Copy(part, bytes.NewReader([]byte(p))) 467 } 468 if err != nil { 469 t.Fatal(err) 470 } 471 } 472 index++ 473 474 writer.Close() 475 w.Header().Add("Content-Type", writer.FormDataContentType()) 476 w.Write(body.Bytes()) 477 })) 478 defer tserve.Close() 479 480 conf := NewConfig() 481 conf.HTTPClient.URL = tserve.URL + "/testpost" 482 conf.HTTPClient.Retry = "1ms" 483 484 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 485 if err != nil { 486 t.Error(err) 487 return 488 } 489 490 reqMut.Lock() 491 for _, test := range tests { 492 var ts types.Transaction 493 var open bool 494 495 reqMut.Unlock() 496 select { 497 case ts, open = <-h.TransactionChan(): 498 if !open { 499 t.Fatal("Chan not open") 500 } 501 if exp, act := len(test), ts.Payload.Len(); exp != act { 502 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 503 } 504 for i, part := range test { 505 if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act { 506 t.Errorf("Wrong part: %v != %v", act, exp) 507 } 508 } 509 case <-time.After(time.Second): 510 t.Errorf("Action timed out") 511 } 512 513 reqMut.Lock() 514 select { 515 case ts.ResponseChan <- response.NewAck(): 516 case <-time.After(time.Second): 517 t.Errorf("Action timed out") 518 } 519 } 520 521 h.CloseAsync() 522 reqMut.Unlock() 523 524 select { 525 case <-h.TransactionChan(): 526 case <-time.After(time.Second): 527 t.Errorf("Action timed out") 528 } 529 530 if err := h.WaitForClose(time.Second); err != nil { 531 t.Error(err) 532 } 533 } 534 535 func TestHTTPClientStreamGETMultipartLoop(t *testing.T) { 536 tests := [][]string{ 537 { 538 "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 539 "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", 540 "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 541 }, 542 { 543 "Tristique et egestas quis ipsum suspendisse ultrices. Quis enim lobortis scelerisque fermentum dui faucibus.", 544 }, 545 { 546 "Lorem donec massa sapien faucibus et molestie ac. Lectus proin nibh nisl condimentum id venenatis a.", 547 "Ultricies mi eget mauris pharetra et ultrices neque ornare aenean.", 548 }, 549 { 550 "Amet tellus cras adipiscing enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Venenatis cras sed felis eget velit aliquet sagittis.", 551 "Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.", 552 "Odio ut sem nulla pharetra diam sit. Neque vitae tempus quam pellentesque nec nam aliquam sem.", 553 "Scelerisque eu ultrices vitae auctor eu augue. Ut eu sem integer vitae justo eget. Purus in massa tempor nec feugiat nisl pretium fusce id.", 554 }, 555 } 556 557 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 558 if exp, act := "GET", r.Method; exp != act { 559 t.Errorf("Wrong method: %v != %v", act, exp) 560 } 561 562 body := &bytes.Buffer{} 563 564 for _, test := range tests { 565 for _, part := range test { 566 body.WriteString(part) 567 body.WriteByte('\n') 568 } 569 body.WriteByte('\n') 570 } 571 body.WriteString("A msg that we won't read\nsecond part\n\n") 572 573 w.Header().Add("Content-Type", "application/octet-stream") 574 w.Write(body.Bytes()) 575 })) 576 defer tserve.Close() 577 578 conf := NewConfig() 579 conf.HTTPClient.URL = tserve.URL + "/testpost" 580 conf.HTTPClient.Retry = "1ms" 581 conf.HTTPClient.Stream.Enabled = true 582 conf.HTTPClient.Stream.Multipart = true 583 584 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 585 if err != nil { 586 t.Error(err) 587 return 588 } 589 590 for _, test := range tests { 591 var ts types.Transaction 592 var open bool 593 594 select { 595 case ts, open = <-h.TransactionChan(): 596 if !open { 597 t.Fatal("Chan not open") 598 } 599 if exp, act := len(test), ts.Payload.Len(); exp != act { 600 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 601 } 602 for i, part := range test { 603 if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act { 604 t.Errorf("Wrong part: %v != %v", act, exp) 605 } 606 } 607 case <-time.After(time.Second): 608 t.Errorf("Action timed out") 609 } 610 611 select { 612 case ts.ResponseChan <- response.NewAck(): 613 case <-time.After(time.Second): 614 t.Errorf("Action timed out") 615 } 616 } 617 618 h.CloseAsync() 619 if err := h.WaitForClose(time.Second); err != nil { 620 t.Error(err) 621 } 622 } 623 624 func TestHTTPClientStreamGETMultiRecover(t *testing.T) { 625 msgs := [][]string{ 626 {"foo", "bar"}, 627 {"foo", "baz"}, 628 } 629 630 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 631 if exp, act := "GET", r.Method; exp != act { 632 t.Errorf("Wrong method: %v != %v", act, exp) 633 } 634 635 body := &bytes.Buffer{} 636 for _, msg := range msgs { 637 for _, part := range msg { 638 body.WriteString(part) 639 body.WriteByte('\n') 640 } 641 body.WriteByte('\n') 642 } 643 644 w.Header().Add("Content-Type", "application/octet-stream") 645 w.Write(body.Bytes()) 646 })) 647 defer tserve.Close() 648 649 conf := NewConfig() 650 conf.HTTPClient.URL = tserve.URL + "/testpost" 651 conf.HTTPClient.Retry = "1ms" 652 conf.HTTPClient.Stream.Enabled = true 653 conf.HTTPClient.Stream.Multipart = true 654 655 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 656 if err != nil { 657 t.Error(err) 658 return 659 } 660 661 for i := 0; i < 10; i++ { 662 for _, testMsg := range msgs { 663 var ts types.Transaction 664 var open bool 665 select { 666 case ts, open = <-h.TransactionChan(): 667 if !open { 668 t.Fatal("Chan not open") 669 } 670 if exp, act := len(testMsg), ts.Payload.Len(); exp != act { 671 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 672 } 673 for j, part := range testMsg { 674 if exp, act := part, string(ts.Payload.Get(j).Get()); exp != act { 675 t.Errorf("Wrong part: %v != %v", act, exp) 676 } 677 } 678 case <-time.After(time.Second): 679 t.Errorf("Action timed out") 680 } 681 682 select { 683 case ts.ResponseChan <- response.NewAck(): 684 case <-time.After(time.Second): 685 t.Errorf("Action timed out") 686 } 687 } 688 } 689 690 h.CloseAsync() 691 if err := h.WaitForClose(time.Second); err != nil { 692 t.Error(err) 693 } 694 } 695 696 func TestHTTPClientStreamGETRecover(t *testing.T) { 697 msgs := []string{"foo", "bar"} 698 699 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 700 if exp, act := "GET", r.Method; exp != act { 701 t.Errorf("Wrong method: %v != %v", act, exp) 702 } 703 704 body := &bytes.Buffer{} 705 for _, msg := range msgs { 706 body.WriteString(msg) 707 body.WriteByte('\n') 708 } 709 710 w.Header().Add("Content-Type", "application/octet-stream") 711 w.Write(body.Bytes()) 712 })) 713 defer tserve.Close() 714 715 conf := NewConfig() 716 conf.HTTPClient.URL = tserve.URL + "/testpost" 717 conf.HTTPClient.Retry = "1ms" 718 conf.HTTPClient.Stream.Enabled = true 719 conf.HTTPClient.Stream.Multipart = false 720 721 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 722 if err != nil { 723 t.Error(err) 724 return 725 } 726 727 for i := 0; i < 10; i++ { 728 for _, testMsg := range msgs { 729 var ts types.Transaction 730 var open bool 731 select { 732 case ts, open = <-h.TransactionChan(): 733 if !open { 734 t.Fatal("Chan not open") 735 } 736 if exp, act := 1, ts.Payload.Len(); exp != act { 737 t.Fatalf("Wrong count of parts: %v != %v", act, exp) 738 } 739 if exp, act := testMsg, string(ts.Payload.Get(0).Get()); exp != act { 740 t.Errorf("Wrong part: %v != %v", act, exp) 741 } 742 case <-time.After(time.Second): 743 t.Errorf("Action timed out") 744 } 745 746 select { 747 case ts.ResponseChan <- response.NewAck(): 748 case <-time.After(time.Second): 749 t.Errorf("Action timed out") 750 } 751 } 752 } 753 754 h.CloseAsync() 755 if err := h.WaitForClose(time.Second); err != nil { 756 t.Error(err) 757 } 758 } 759 760 func TestHTTPClientStreamGETTokenization(t *testing.T) { 761 msgs := []string{`{"token":"foo"}`, `{"token":"bar"}`} 762 763 var tokensLock sync.Mutex 764 updateTokens := true 765 tokens := []string{} 766 767 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 768 assert.Equal(t, "GET", r.Method) 769 770 tokensLock.Lock() 771 if updateTokens { 772 tokens = append(tokens, r.URL.Query().Get("token")) 773 } 774 tokensLock.Unlock() 775 776 body := &bytes.Buffer{} 777 for _, msg := range msgs { 778 body.WriteString(msg) 779 body.WriteByte('\n') 780 } 781 782 w.Header().Add("Content-Type", "application/octet-stream") 783 w.Write(body.Bytes()) 784 })) 785 defer tserve.Close() 786 787 conf := NewConfig() 788 conf.HTTPClient.URL = tserve.URL + `/testpost?token=${!json("token")}` 789 conf.HTTPClient.Retry = "1ms" 790 conf.HTTPClient.Stream.Enabled = true 791 conf.HTTPClient.Stream.Multipart = false 792 793 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 794 require.NoError(t, err) 795 796 for i := 0; i < 10; i++ { 797 if i == 9 { 798 tokensLock.Lock() 799 updateTokens = false 800 tokensLock.Unlock() 801 } 802 803 for _, testMsg := range msgs { 804 var ts types.Transaction 805 var open bool 806 select { 807 case ts, open = <-h.TransactionChan(): 808 require.True(t, open) 809 require.Equal(t, 1, ts.Payload.Len()) 810 assert.Equal(t, testMsg, string(ts.Payload.Get(0).Get())) 811 case <-time.After(time.Second): 812 t.Errorf("Action timed out") 813 } 814 815 select { 816 case ts.ResponseChan <- response.NewAck(): 817 case <-time.After(time.Second): 818 t.Errorf("Action timed out") 819 } 820 } 821 } 822 823 tokensLock.Lock() 824 assert.Equal(t, []string{ 825 "null", "bar", "bar", "bar", "bar", "bar", "bar", "bar", "bar", 826 }, tokens) 827 tokensLock.Unlock() 828 829 h.CloseAsync() 830 require.NoError(t, h.WaitForClose(time.Second)) 831 } 832 833 func BenchmarkHTTPClientGETMultipart(b *testing.B) { 834 parts := []string{ 835 "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 836 "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", 837 "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 838 } 839 840 body := &bytes.Buffer{} 841 writer := multipart.NewWriter(body) 842 for _, p := range parts { 843 var err error 844 var part io.Writer 845 if part, err = writer.CreatePart(textproto.MIMEHeader{ 846 "Content-Type": []string{"application/octet-stream"}, 847 }); err == nil { 848 _, err = io.Copy(part, bytes.NewReader([]byte(p))) 849 } 850 if err != nil { 851 b.Fatal(err) 852 } 853 } 854 writer.Close() 855 header := writer.FormDataContentType() 856 857 tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 858 if exp, act := "GET", r.Method; exp != act { 859 b.Errorf("Wrong method: %v != %v", act, exp) 860 } 861 862 w.Header().Add("Content-Type", header) 863 w.Write(body.Bytes()) 864 })) 865 defer tserve.Close() 866 867 conf := NewConfig() 868 conf.HTTPClient.URL = tserve.URL + "/testpost" 869 conf.HTTPClient.Retry = "1ms" 870 871 h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop()) 872 if err != nil { 873 b.Error(err) 874 return 875 } 876 877 b.ReportAllocs() 878 b.ResetTimer() 879 880 for n := 0; n < b.N; n++ { 881 ts, open := <-h.TransactionChan() 882 if !open { 883 b.Fatal("Chan not open") 884 } 885 if exp, act := 3, ts.Payload.Len(); exp != act { 886 b.Fatalf("Wrong count of parts: %v != %v", act, exp) 887 } 888 for i, part := range parts { 889 if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act { 890 b.Errorf("Wrong part: %v != %v", act, exp) 891 } 892 } 893 ts.ResponseChan <- response.NewAck() 894 } 895 896 b.StopTimer() 897 898 h.CloseAsync() 899 if err := h.WaitForClose(time.Second); err != nil { 900 b.Error(err) 901 } 902 }