go.uber.org/yarpc@v1.72.1/transport/http/outbound_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package http 22 23 import ( 24 "bytes" 25 "context" 26 "crypto/tls" 27 "errors" 28 "io/ioutil" 29 "net/http" 30 "net/http/httptest" 31 "net/url" 32 "strconv" 33 "sync" 34 "testing" 35 "time" 36 37 "github.com/golang/mock/gomock" 38 "github.com/opentracing/opentracing-go" 39 "github.com/stretchr/testify/assert" 40 "github.com/stretchr/testify/require" 41 "go.uber.org/yarpc/api/peer/peertest" 42 "go.uber.org/yarpc/api/transport" 43 "go.uber.org/yarpc/encoding/raw" 44 "go.uber.org/yarpc/internal/testtime" 45 "go.uber.org/yarpc/peer/abstractpeer" 46 "go.uber.org/yarpc/pkg/lifecycle" 47 "go.uber.org/yarpc/yarpcerrors" 48 ) 49 50 func TestNewOutbound(t *testing.T) { 51 ctrl := gomock.NewController(t) 52 chooser := peertest.NewMockChooser(ctrl) 53 54 out := NewOutbound(chooser) 55 require.NotNil(t, out) 56 assert.Equal(t, chooser, out.Chooser()) 57 } 58 59 func TestTransportNamer(t *testing.T) { 60 assert.Equal(t, TransportName, NewOutbound(nil).TransportName()) 61 } 62 63 func TestNewSingleOutboundPanic(t *testing.T) { 64 require.Panics(t, func() { 65 // invalid url should cause panic 66 NewTransport().NewSingleOutbound("127.0.0.1:") 67 }, 68 "expected to panic") 69 } 70 71 func TestCreateRequest(t *testing.T) { 72 appHeader := map[string]string{ 73 "app-key1": "app-val1", 74 "app-key2": "app-val2", 75 } 76 tests := []struct { 77 desc string 78 urlTemplate *url.URL 79 treq *transport.Request 80 wantError bool 81 }{ 82 { 83 desc: "wrong uri", 84 urlTemplate: &url.URL{Scheme: "%"}, // invalid 85 treq: &transport.Request{}, 86 wantError: true, 87 }, 88 { 89 desc: "successful creation", 90 treq: &transport.Request{ 91 Headers: transport.HeadersFromMap(appHeader), 92 }, 93 }, 94 } 95 96 for _, tt := range tests { 97 t.Run(tt.desc, func(t *testing.T) { 98 o := &Outbound{urlTemplate: defaultURLTemplate} 99 if tt.urlTemplate != nil { 100 o.urlTemplate = tt.urlTemplate 101 } 102 hreq, err := o.createRequest(tt.treq) 103 if tt.wantError { 104 assert.Error(t, err) 105 return 106 } 107 assert.Len(t, hreq.Header, len(appHeader), "wrong number of header") 108 for k, v := range appHeader { 109 assert.Equal(t, v, hreq.Header.Get(ApplicationHeaderPrefix+k), "header value mismatch") 110 } 111 }) 112 } 113 } 114 115 func TestCallSuccess(t *testing.T) { 116 successServer := httptest.NewServer(http.HandlerFunc( 117 func(w http.ResponseWriter, req *http.Request) { 118 defer req.Body.Close() 119 120 ttl := req.Header.Get(TTLMSHeader) 121 ttlms, err := strconv.Atoi(ttl) 122 assert.NoError(t, err, "can parse TTL header") 123 assert.InDelta(t, ttlms, testtime.X*1000.0, testtime.X*5.0, "ttl header within tolerance") 124 125 assert.Equal(t, "caller", req.Header.Get(CallerHeader)) 126 assert.Equal(t, "service", req.Header.Get(ServiceHeader)) 127 assert.Equal(t, "raw", req.Header.Get(EncodingHeader)) 128 assert.Equal(t, "hello", req.Header.Get(ProcedureHeader)) 129 130 body, err := ioutil.ReadAll(req.Body) 131 if assert.NoError(t, err) { 132 assert.Equal(t, []byte("world"), body) 133 } 134 135 w.Header().Set("rpc-header-foo", "bar") 136 _, err = w.Write([]byte("great success")) 137 assert.NoError(t, err) 138 }, 139 )) 140 defer successServer.Close() 141 142 httpTransport := NewTransport() 143 defer httpTransport.Stop() 144 out := httpTransport.NewSingleOutbound(successServer.URL) 145 require.NoError(t, out.Start(), "failed to start outbound") 146 defer out.Stop() 147 148 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 149 defer cancel() 150 res, err := out.Call(ctx, &transport.Request{ 151 Caller: "caller", 152 Service: "service", 153 Encoding: raw.Encoding, 154 Procedure: "hello", 155 Body: bytes.NewReader([]byte("world")), 156 }) 157 require.NoError(t, err) 158 defer res.Body.Close() 159 160 foo, ok := res.Headers.Get("foo") 161 assert.True(t, ok, "value for foo expected") 162 assert.Equal(t, "bar", foo, "foo value mismatch") 163 164 body, err := ioutil.ReadAll(res.Body) 165 if assert.NoError(t, err) { 166 assert.Equal(t, []byte("great success"), body) 167 } 168 } 169 170 func TestCallOneWaySuccessWithBody(t *testing.T) { 171 successServer := httptest.NewServer(http.HandlerFunc( 172 func(w http.ResponseWriter, req *http.Request) { 173 defer req.Body.Close() 174 175 ttl := req.Header.Get(TTLMSHeader) 176 ttlms, err := strconv.Atoi(ttl) 177 assert.NoError(t, err, "can parse TTL header") 178 assert.InDelta(t, ttlms, testtime.X*1000.0, testtime.X*5.0, "ttl header within tolerance") 179 180 assert.Equal(t, "caller", req.Header.Get(CallerHeader)) 181 assert.Equal(t, "service", req.Header.Get(ServiceHeader)) 182 assert.Equal(t, "raw", req.Header.Get(EncodingHeader)) 183 assert.Equal(t, "hello", req.Header.Get(ProcedureHeader)) 184 185 body, err := ioutil.ReadAll(req.Body) 186 if assert.NoError(t, err) { 187 assert.Equal(t, []byte("world"), body) 188 } 189 190 w.Header().Set("rpc-header-foo", "bar") 191 _, err = w.Write([]byte("great success")) 192 assert.NoError(t, err) 193 }, 194 )) 195 defer successServer.Close() 196 197 httpTransport := NewTransport() 198 defer httpTransport.Stop() 199 out := httpTransport.NewSingleOutbound(successServer.URL) 200 require.NoError(t, out.Start(), "failed to start outbound") 201 defer out.Stop() 202 203 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 204 defer cancel() 205 ack, err := out.CallOneway(ctx, &transport.Request{ 206 Caller: "caller", 207 Service: "service", 208 Encoding: raw.Encoding, 209 Procedure: "hello", 210 Body: bytes.NewReader([]byte("world")), 211 }) 212 require.NoError(t, err) 213 require.NotNil(t, ack) 214 } 215 216 func TestCallOneWaySuccess(t *testing.T) { 217 successServer := httptest.NewServer(http.HandlerFunc( 218 func(w http.ResponseWriter, req *http.Request) { 219 defer req.Body.Close() 220 221 ttl := req.Header.Get(TTLMSHeader) 222 ttlms, err := strconv.Atoi(ttl) 223 assert.NoError(t, err, "can parse TTL header") 224 assert.InDelta(t, ttlms, testtime.X*1000.0, testtime.X*5.0, "ttl header within tolerance") 225 226 assert.Equal(t, "caller", req.Header.Get(CallerHeader)) 227 assert.Equal(t, "service", req.Header.Get(ServiceHeader)) 228 assert.Equal(t, "raw", req.Header.Get(EncodingHeader)) 229 assert.Equal(t, "hello", req.Header.Get(ProcedureHeader)) 230 231 body, err := ioutil.ReadAll(req.Body) 232 if assert.NoError(t, err) { 233 assert.Equal(t, []byte("world"), body) 234 } 235 236 w.Header().Set("rpc-header-foo", "bar") 237 assert.NoError(t, err) 238 }, 239 )) 240 defer successServer.Close() 241 242 httpTransport := NewTransport() 243 defer httpTransport.Stop() 244 out := httpTransport.NewSingleOutbound(successServer.URL) 245 require.NoError(t, out.Start(), "failed to start outbound") 246 defer out.Stop() 247 248 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 249 defer cancel() 250 ack, err := out.CallOneway(ctx, &transport.Request{ 251 Caller: "caller", 252 Service: "service", 253 Encoding: raw.Encoding, 254 Procedure: "hello", 255 Body: bytes.NewReader([]byte("world")), 256 }) 257 require.NoError(t, err) 258 require.NotNil(t, ack) 259 } 260 261 func TestCallOneWayFailWithoutDeadline(t *testing.T) { 262 successServer := httptest.NewServer(nil) 263 defer successServer.Close() 264 265 httpTransport := NewTransport() 266 defer httpTransport.Stop() 267 out := httpTransport.NewSingleOutbound(successServer.URL) 268 require.NoError(t, out.Start(), "failed to start outbound") 269 defer out.Stop() 270 271 ack, err := out.CallOneway(context.Background(), &transport.Request{ 272 Caller: "caller", 273 Service: "service", 274 Encoding: raw.Encoding, 275 Procedure: "hello", 276 Body: bytes.NewReader([]byte("world")), 277 }) 278 require.Error(t, err) 279 require.Nil(t, ack) 280 } 281 282 func TestAddReservedHeader(t *testing.T) { 283 tests := []string{ 284 "Rpc-Foo", 285 "rpc-header-foo", 286 "RPC-Bar", 287 } 288 289 for _, tt := range tests { 290 assert.Panics(t, func() { AddHeader(tt, "bar") }) 291 } 292 } 293 294 func TestOutboundHeaders(t *testing.T) { 295 tests := []struct { 296 desc string 297 context context.Context 298 headers transport.Headers 299 opts []OutboundOption 300 301 wantHeaders map[string]string 302 }{ 303 { 304 desc: "application headers", 305 headers: transport.NewHeaders().With("foo", "bar").With("baz", "Qux"), 306 wantHeaders: map[string]string{ 307 "Rpc-Header-Foo": "bar", 308 "Rpc-Header-Baz": "Qux", 309 }, 310 }, 311 { 312 desc: "extra headers", 313 headers: transport.NewHeaders().With("x", "y"), 314 opts: []OutboundOption{ 315 AddHeader("X-Foo", "bar"), 316 AddHeader("X-BAR", "BAZ"), 317 }, 318 wantHeaders: map[string]string{ 319 "Rpc-Header-X": "y", 320 "X-Foo": "bar", 321 "X-Bar": "BAZ", 322 }, 323 }, 324 { 325 desc: "pseudo headers", 326 headers: transport.NewHeaders().With(":authority", "localhost").With(":path", "/my/path").With(":scheme", "http").With(":method", "POST").With("baz", "Qux"), 327 wantHeaders: map[string]string{ 328 "Rpc-Header-Baz": "Qux", 329 }, 330 }, 331 } 332 333 httpTransport := NewTransport() 334 defer httpTransport.Stop() 335 336 for _, tt := range tests { 337 server := httptest.NewServer(http.HandlerFunc( 338 func(w http.ResponseWriter, r *http.Request) { 339 defer r.Body.Close() 340 for k, v := range tt.wantHeaders { 341 assert.Equal( 342 t, v, r.Header.Get(k), "%v: header %v did not match", tt.desc, k) 343 } 344 }, 345 )) 346 defer server.Close() 347 348 ctx := tt.context 349 if ctx == nil { 350 var cancel context.CancelFunc 351 ctx, cancel = context.WithTimeout(context.Background(), testtime.Second) 352 defer cancel() 353 } 354 355 out := httpTransport.NewSingleOutbound(server.URL, tt.opts...) 356 assert.Len(t, out.Transports(), 1, "transports must contain the transport") 357 // we use == instead of assert.Equal because we want to do a pointer 358 // comparison 359 assert.True(t, httpTransport == out.Transports()[0], "transports must match") 360 361 require.NoError(t, out.Start(), "failed to start outbound") 362 defer out.Stop() 363 364 res, err := out.Call(ctx, &transport.Request{ 365 Caller: "caller", 366 Service: "service", 367 Encoding: raw.Encoding, 368 Headers: tt.headers, 369 Procedure: "hello", 370 Body: bytes.NewReader([]byte("world")), 371 }) 372 373 if !assert.NoError(t, err, "%v: call failed", tt.desc) { 374 continue 375 } 376 377 if !assert.NoError(t, res.Body.Close(), "%v: failed to close response body") { 378 continue 379 } 380 } 381 } 382 383 func TestOutboundApplicationError(t *testing.T) { 384 const ( 385 appErrDetails = "thrift ex message" 386 appErrName = "thrift ex name" 387 ) 388 389 tests := []struct { 390 desc string 391 status string 392 appError bool 393 appErrName string 394 appErrDetails string 395 appErrCode yarpcerrors.Code 396 }{ 397 { 398 desc: "ok", 399 status: "success", 400 appError: false, 401 }, 402 { 403 desc: "error", 404 status: "error", 405 appError: true, 406 appErrName: appErrName, 407 appErrDetails: appErrDetails, 408 appErrCode: yarpcerrors.CodeNotFound, 409 }, 410 { 411 desc: "not an error", 412 status: "lolwut", 413 appError: false, 414 }, 415 } 416 417 httpTransport := NewTransport() 418 defer httpTransport.Stop() 419 420 for _, tt := range tests { 421 server := httptest.NewServer(http.HandlerFunc( 422 func(w http.ResponseWriter, r *http.Request) { 423 w.Header().Add("Rpc-Status", tt.status) 424 425 if tt.appError { 426 w.Header().Add(_applicationErrorDetailsHeader, tt.appErrDetails) 427 w.Header().Add(_applicationErrorNameHeader, tt.appErrName) 428 w.Header().Add(_applicationErrorCodeHeader, strconv.Itoa(int(tt.appErrCode))) 429 } 430 431 defer r.Body.Close() 432 }, 433 )) 434 defer server.Close() 435 436 out := httpTransport.NewSingleOutbound(server.URL) 437 438 require.NoError(t, out.Start(), "failed to start outbound") 439 defer out.Stop() 440 441 ctx := context.Background() 442 ctx, cancel := context.WithTimeout(ctx, 100*testtime.Millisecond) 443 defer cancel() 444 445 res, err := out.Call(ctx, &transport.Request{ 446 Caller: "caller", 447 Service: "service", 448 Encoding: raw.Encoding, 449 Procedure: "hello", 450 Body: bytes.NewReader([]byte("world")), 451 }) 452 453 assert.Equal(t, res.ApplicationError, tt.appError, "%v: application status", tt.desc) 454 if tt.appError { 455 require.NotNil(t, res.ApplicationErrorMeta) 456 assert.Equal(t, tt.appErrDetails, res.ApplicationErrorMeta.Details) 457 assert.Equal(t, tt.appErrName, res.ApplicationErrorMeta.Name) 458 assert.Equal(t, &tt.appErrCode, res.ApplicationErrorMeta.Code) 459 } 460 461 if !assert.NoError(t, err, "%v: call failed", tt.desc) { 462 continue 463 } 464 465 if !assert.NoError(t, res.Body.Close(), "%v: failed to close response body") { 466 continue 467 } 468 } 469 } 470 471 func TestCallFailures(t *testing.T) { 472 notFoundServer := httptest.NewServer(http.NotFoundHandler()) 473 defer notFoundServer.Close() 474 475 internalErrorServer := httptest.NewServer(http.HandlerFunc( 476 func(w http.ResponseWriter, r *http.Request) { 477 http.Error(w, "great sadness", http.StatusInternalServerError) 478 })) 479 defer internalErrorServer.Close() 480 481 httpTransport := NewTransport() 482 defer httpTransport.Stop() 483 484 tests := []struct { 485 url string 486 messages []string 487 }{ 488 {"not a URL", []string{"protocol scheme"}}, 489 {notFoundServer.URL, []string{"404", "page not found"}}, 490 {internalErrorServer.URL, []string{"great sadness"}}, 491 } 492 493 for _, tt := range tests { 494 out := httpTransport.NewSingleOutbound(tt.url) 495 require.NoError(t, out.Start(), "failed to start outbound") 496 defer out.Stop() 497 498 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 499 defer cancel() 500 _, err := out.Call(ctx, &transport.Request{ 501 Caller: "caller", 502 Service: "service", 503 Encoding: raw.Encoding, 504 Procedure: "wat", 505 Body: bytes.NewReader([]byte("huh")), 506 }) 507 assert.Error(t, err, "expected failure") 508 for _, msg := range tt.messages { 509 assert.Contains(t, err.Error(), msg) 510 } 511 } 512 } 513 514 func TestStartMultiple(t *testing.T) { 515 httpTransport := NewTransport() 516 defer httpTransport.Stop() 517 out := httpTransport.NewSingleOutbound("http://localhost:9999") 518 519 var wg sync.WaitGroup 520 signal := make(chan struct{}) 521 522 for i := 0; i < 10; i++ { 523 wg.Add(1) 524 go func() { 525 defer wg.Done() 526 <-signal 527 528 err := out.Start() 529 assert.NoError(t, err) 530 }() 531 } 532 close(signal) 533 wg.Wait() 534 } 535 536 func TestStopMultiple(t *testing.T) { 537 httpTransport := NewTransport() 538 defer httpTransport.Stop() 539 out := httpTransport.NewSingleOutbound("http://127.0.0.1:9999") 540 541 err := out.Start() 542 require.NoError(t, err) 543 544 var wg sync.WaitGroup 545 signal := make(chan struct{}) 546 547 for i := 0; i < 10; i++ { 548 wg.Add(1) 549 go func() { 550 defer wg.Done() 551 <-signal 552 553 err := out.Stop() 554 assert.NoError(t, err) 555 }() 556 } 557 close(signal) 558 wg.Wait() 559 } 560 561 func TestCallWithoutStarting(t *testing.T) { 562 httpTransport := NewTransport() 563 defer httpTransport.Stop() 564 out := httpTransport.NewSingleOutbound("http://127.0.0.1:9999") 565 566 ctx, cancel := context.WithTimeout(context.Background(), 200*testtime.Millisecond) 567 defer cancel() 568 _, err := out.Call( 569 ctx, 570 &transport.Request{ 571 Caller: "caller", 572 Service: "service", 573 Encoding: raw.Encoding, 574 Procedure: "foo", 575 Body: bytes.NewReader([]byte("sup")), 576 }, 577 ) 578 579 assert.Equal(t, yarpcerrors.FailedPreconditionErrorf("error waiting for HTTP outbound to start for service: service: context finished while waiting for instance to start: context deadline exceeded"), err) 580 } 581 582 func TestGetPeerForRequestErr(t *testing.T) { 583 ctrl := gomock.NewController(t) 584 defer ctrl.Finish() 585 586 tests := []struct { 587 name string 588 peer *peertest.MockPeer 589 err error 590 }{ 591 { 592 name: "error choosing peer", 593 }, 594 { 595 name: "error casting peer", 596 peer: peertest.NewMockPeer(ctrl), 597 err: errors.New("err"), 598 }, 599 } 600 601 for _, tt := range tests { 602 t.Run(tt.name, func(t *testing.T) { 603 chooser := peertest.NewMockChooser(ctrl) 604 605 out := NewTransport().NewSingleOutbound("http://127.0.0.1:9999") 606 out.chooser = chooser 607 608 ctx := context.Background() 609 treq := &transport.Request{} 610 611 chooser.EXPECT().Choose(ctx, treq).Return(tt.peer, nil, tt.err) 612 613 _, _, err := out.getPeerForRequest(ctx, treq) 614 require.Error(t, err) 615 }) 616 } 617 } 618 619 func TestWithCoreHeaders(t *testing.T) { 620 endpoint := "http://127.0.0.1:9999" 621 httpTransport := NewTransport() 622 defer httpTransport.Stop() 623 out := httpTransport.NewSingleOutbound(endpoint) 624 defer out.Stop() 625 require.NoError(t, out.Start()) 626 627 httpReq := httptest.NewRequest("", endpoint, nil) 628 629 shardKey := "sharding" 630 routingKey := "routing" 631 routingDelegate := "delegate" 632 callerProcedure := "callerprocedure" 633 634 treq := &transport.Request{ 635 ShardKey: shardKey, 636 RoutingKey: routingKey, 637 RoutingDelegate: routingDelegate, 638 CallerProcedure: callerProcedure, 639 } 640 result := out.withCoreHeaders(httpReq, treq, time.Second) 641 642 assert.Equal(t, shardKey, result.Header.Get(ShardKeyHeader)) 643 assert.Equal(t, routingKey, result.Header.Get(RoutingKeyHeader)) 644 assert.Equal(t, routingDelegate, result.Header.Get(RoutingDelegateHeader)) 645 assert.Equal(t, callerProcedure, result.Header.Get(CallerProcedureHeader)) 646 } 647 648 func TestNoRequest(t *testing.T) { 649 tran := NewTransport() 650 defer tran.Stop() 651 out := tran.NewSingleOutbound("localhost:0") 652 653 _, err := out.Call(context.Background(), nil) 654 assert.Equal(t, yarpcerrors.InvalidArgumentErrorf("request for http unary outbound was nil"), err) 655 656 _, err = out.CallOneway(context.Background(), nil) 657 assert.Equal(t, yarpcerrors.InvalidArgumentErrorf("request for http oneway outbound was nil"), err) 658 } 659 660 func TestOutboundNoDeadline(t *testing.T) { 661 tran := NewTransport() 662 defer tran.Stop() 663 out := tran.NewSingleOutbound("http://foo-host:8080") 664 665 _, err := out.call(context.Background(), &transport.Request{}) 666 assert.Equal(t, yarpcerrors.Newf(yarpcerrors.CodeInvalidArgument, "missing context deadline"), err) 667 } 668 669 func TestServiceMatchSuccess(t *testing.T) { 670 matchServer := httptest.NewServer(http.HandlerFunc( 671 func(w http.ResponseWriter, req *http.Request) { 672 defer req.Body.Close() 673 w.Header().Set(ServiceHeader, req.Header.Get(ServiceHeader)) 674 _, err := w.Write([]byte("Service name header return")) 675 assert.NoError(t, err) 676 }, 677 )) 678 defer matchServer.Close() 679 680 httpTransport := NewTransport() 681 defer httpTransport.Stop() 682 out := httpTransport.NewSingleOutbound(matchServer.URL) 683 require.NoError(t, out.Start(), "failed to start outbound") 684 defer out.Stop() 685 686 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 687 defer cancel() 688 _, err := out.Call(ctx, &transport.Request{ 689 Service: "Service", 690 }) 691 require.NoError(t, err) 692 } 693 694 func TestServiceMatchFailed(t *testing.T) { 695 mismatchServer := httptest.NewServer(http.HandlerFunc( 696 func(w http.ResponseWriter, req *http.Request) { 697 defer req.Body.Close() 698 w.Header().Set(ServiceHeader, "ThisIsAWrongSvcName") 699 _, err := w.Write([]byte("Wrong service name header return")) 700 assert.NoError(t, err) 701 }, 702 )) 703 defer mismatchServer.Close() 704 705 httpTransport := NewTransport() 706 defer httpTransport.Stop() 707 out := httpTransport.NewSingleOutbound(mismatchServer.URL) 708 require.NoError(t, out.Start(), "failed to start outbound") 709 defer out.Stop() 710 711 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 712 defer cancel() 713 _, err := out.Call(ctx, &transport.Request{ 714 Service: "Service", 715 }) 716 assert.Error(t, err, "expected failure for service name dismatch") 717 } 718 719 func TestServiceMatchNoHeader(t *testing.T) { 720 noHeaderServer := httptest.NewServer(http.HandlerFunc( 721 func(w http.ResponseWriter, req *http.Request) { 722 defer req.Body.Close() 723 // intentionally do not set a response header 724 _, err := w.Write([]byte("No service name header return")) 725 assert.NoError(t, err) 726 }, 727 )) 728 defer noHeaderServer.Close() 729 730 httpTransport := NewTransport() 731 defer httpTransport.Stop() 732 out := httpTransport.NewSingleOutbound(noHeaderServer.URL) 733 require.NoError(t, out.Start(), "failed to start outbound") 734 defer out.Stop() 735 736 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 737 defer cancel() 738 _, err := out.Call(ctx, &transport.Request{ 739 Service: "Service", 740 }) 741 require.NoError(t, err) 742 } 743 744 type RoundTripFunc func(req *http.Request) *http.Response 745 746 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 747 return f(req), nil 748 } 749 750 type errorReadCloser struct { 751 closeErr error 752 } 753 754 func (errorReadCloser) Read(p []byte) (n int, err error) { 755 return 756 } 757 758 func (e errorReadCloser) Close() error { 759 return e.closeErr 760 } 761 762 func TestCallResponseCloseError(t *testing.T) { 763 httpTransport := Transport{ 764 client: &http.Client{ 765 Transport: RoundTripFunc(func(req *http.Request) *http.Response { 766 return &http.Response{ 767 StatusCode: 200, 768 Body: errorReadCloser{closeErr: errors.New("test error")}, 769 Header: http.Header{ 770 "Rpc-Service": []string{"wrong-service"}, 771 }, 772 } 773 }), 774 }, 775 tracer: opentracing.GlobalTracer(), 776 } 777 ctrl := gomock.NewController(t) 778 chooser := peertest.NewMockChooser(ctrl) 779 chooser.EXPECT().Start().Return(nil) 780 peer := &httpPeer{ 781 Peer: &abstractpeer.Peer{}, 782 } 783 chooser.EXPECT().Choose(gomock.Any(), gomock.Any()).Return(peer, func(error) {}, nil) 784 o := &Outbound{ 785 once: lifecycle.NewOnce(), 786 chooser: chooser, 787 urlTemplate: defaultURLTemplate, 788 tracer: httpTransport.tracer, 789 transport: &httpTransport, 790 bothResponseError: true, 791 client: httpTransport.client, 792 } 793 err := o.Start() 794 require.NoError(t, err) 795 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 796 defer cancel() 797 _, err = o.Call(ctx, &transport.Request{ 798 Service: "Service", 799 }) 800 require.Errorf(t, err, "Received unexpected error:code:internal message:test error") 801 } 802 803 func TestCallOneWayResponseCloseError(t *testing.T) { 804 httpTransport := Transport{ 805 client: &http.Client{ 806 Transport: RoundTripFunc(func(req *http.Request) *http.Response { 807 return &http.Response{ 808 StatusCode: 200, 809 Body: errorReadCloser{closeErr: errors.New("test error")}, 810 Header: http.Header{}, 811 } 812 }), 813 }, 814 tracer: opentracing.GlobalTracer(), 815 } 816 ctrl := gomock.NewController(t) 817 chooser := peertest.NewMockChooser(ctrl) 818 chooser.EXPECT().Start().Return(nil) 819 peer := &httpPeer{ 820 Peer: &abstractpeer.Peer{}, 821 } 822 chooser.EXPECT().Choose(gomock.Any(), gomock.Any()).Return(peer, func(error) {}, nil) 823 o := &Outbound{ 824 once: lifecycle.NewOnce(), 825 chooser: chooser, 826 urlTemplate: defaultURLTemplate, 827 tracer: httpTransport.tracer, 828 transport: &httpTransport, 829 bothResponseError: true, 830 client: httpTransport.client, 831 } 832 err := o.Start() 833 require.NoError(t, err) 834 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 835 defer cancel() 836 _, err = o.CallOneway(ctx, &transport.Request{ 837 Service: "Service", 838 }) 839 require.Errorf(t, err, "Received unexpected error:code:internal message:test error") 840 } 841 842 func TestIsolatedSchemaChange(t *testing.T) { 843 tr := &Transport{client: &http.Client{Transport: http.DefaultTransport}} 844 plainOutbound := tr.NewOutbound(nil) 845 tlsOutbound := tr.NewOutbound(nil, OutboundTLSConfiguration(&tls.Config{})) 846 assert.NotEqual(t, plainOutbound.urlTemplate, tlsOutbound.urlTemplate) 847 assert.Equal(t, "http", plainOutbound.urlTemplate.Scheme) 848 assert.Equal(t, "https", tlsOutbound.urlTemplate.Scheme) 849 }