github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/dialer_test.go (about) 1 package ws 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/base64" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math/rand" 12 "net" 13 "net/http" 14 "net/url" 15 "testing" 16 "time" 17 18 "github.com/gobwas/httphead" 19 ) 20 21 func TestDialerRequest(t *testing.T) { 22 for _, test := range []struct { 23 dialer Dialer 24 url string 25 exp *http.Request 26 err bool 27 }{ 28 { 29 url: "wss://example.org/chat", 30 exp: setProto(1, 1, 31 mustMakeRequest("GET", "wss://example.org/chat", http.Header{ 32 headerUpgrade: []string{"websocket"}, 33 headerConnection: []string{"Upgrade"}, 34 headerSecVersion: []string{"13"}, 35 headerSecKey: []string{"some key"}, 36 }), 37 ), 38 }, 39 { 40 dialer: Dialer{ 41 Protocols: []string{"foo", "bar"}, 42 Extensions: []httphead.Option{ 43 httphead.NewOption("foo", map[string]string{ 44 "bar": "1", 45 }), 46 httphead.NewOption("baz", nil), 47 }, 48 Header: HandshakeHeaderHTTP(http.Header{ 49 "Origin": []string{"who knows"}, 50 }), 51 }, 52 url: "wss://example.org/chat", 53 exp: setProto(1, 1, 54 mustMakeRequest("GET", "wss://example.org/chat", http.Header{ 55 headerUpgrade: []string{"websocket"}, 56 headerConnection: []string{"Upgrade"}, 57 headerSecVersion: []string{"13"}, 58 headerSecKey: []string{"some key"}, 59 60 headerSecProtocol: []string{"foo, bar"}, 61 headerSecExtensions: []string{"foo;bar=1,baz"}, 62 63 "Origin": []string{"who knows"}, 64 }), 65 ), 66 }, 67 } { 68 t.Run("", func(t *testing.T) { 69 u, err := url.ParseRequestURI(test.url) 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 var buf bytes.Buffer 75 conn := struct { 76 io.Reader 77 io.Writer 78 }{io.LimitReader(&buf, 0), &buf} 79 80 _, _, err = test.dialer.Upgrade(&conn, u) 81 if err == io.EOF { 82 err = nil 83 } 84 if test.err && err == nil { 85 t.Errorf("expected error; got nil") 86 } 87 if !test.err && err != nil { 88 t.Errorf("unexpected error: %s", err) 89 } 90 if test.err { 91 return 92 } 93 94 act := buf.Bytes() 95 exp := dumpRequest(test.exp) 96 97 act = sortHeaders(maskHeader(act, headerSecKey, "<masked>")) 98 exp = sortHeaders(maskHeader(exp, headerSecKey, "<masked>")) 99 100 if !bytes.Equal(act, exp) { 101 t.Errorf("unexpected request:\nact:\n%s\nexp:\n%s\n", act, exp) 102 } 103 if _, err := http.ReadRequest(bufio.NewReader(&buf)); err != nil { 104 t.Errorf("read request error: %s", err) 105 return 106 } 107 }) 108 } 109 } 110 111 func makeAccept(nonce []byte) []byte { 112 accept := make([]byte, acceptSize) 113 initAcceptFromNonce(accept, nonce) 114 return accept 115 } 116 117 func BenchmarkPutAccept(b *testing.B) { 118 nonce := make([]byte, nonceSize) 119 _, err := rand.Read(nonce[:]) 120 if err != nil { 121 b.Fatal(err) 122 } 123 p := make([]byte, acceptSize) 124 b.StartTimer() 125 for i := 0; i < b.N; i++ { 126 initAcceptFromNonce(p, nonce) 127 } 128 } 129 130 func BenchmarkCheckNonce(b *testing.B) { 131 nonce := make([]byte, nonceSize) 132 _, err := rand.Read(nonce[:]) 133 if err != nil { 134 b.Fatal(err) 135 } 136 137 accept := makeAccept(nonce) 138 139 b.ResetTimer() 140 for i := 0; i < b.N; i++ { 141 _ = checkAcceptFromNonce(nonce, accept) 142 } 143 } 144 145 func TestDialerHandshake(t *testing.T) { 146 const ( 147 acceptNo = iota 148 acceptInvalid 149 acceptValid 150 ) 151 for _, test := range []struct { 152 name string 153 dialer Dialer 154 res *http.Response 155 frames []Frame 156 accept int 157 err error 158 wantBuffer bool 159 }{ 160 { 161 res: &http.Response{ 162 StatusCode: 101, 163 ProtoMajor: 1, 164 ProtoMinor: 1, 165 Header: http.Header{ 166 headerConnection: []string{"Upgrade"}, 167 headerUpgrade: []string{"websocket"}, 168 }, 169 }, 170 accept: acceptValid, 171 }, 172 { 173 dialer: Dialer{ 174 Protocols: []string{"xml", "json", "soap"}, 175 }, 176 res: &http.Response{ 177 StatusCode: 101, 178 ProtoMajor: 1, 179 ProtoMinor: 1, 180 Header: http.Header{ 181 headerConnection: []string{"Upgrade"}, 182 headerUpgrade: []string{"websocket"}, 183 headerSecProtocol: []string{"json"}, 184 }, 185 }, 186 accept: acceptValid, 187 }, 188 { 189 dialer: Dialer{ 190 Protocols: []string{"xml", "json", "soap"}, 191 }, 192 res: &http.Response{ 193 StatusCode: 101, 194 ProtoMajor: 1, 195 ProtoMinor: 1, 196 Header: http.Header{ 197 headerConnection: []string{"Upgrade"}, 198 headerUpgrade: []string{"websocket"}, 199 }, 200 }, 201 accept: acceptValid, 202 }, 203 { 204 dialer: Dialer{ 205 Extensions: []httphead.Option{ 206 httphead.NewOption("foo", map[string]string{ 207 "bar": "1", 208 }), 209 httphead.NewOption("baz", nil), 210 }, 211 }, 212 res: &http.Response{ 213 StatusCode: 101, 214 ProtoMajor: 1, 215 ProtoMinor: 1, 216 Header: http.Header{ 217 headerConnection: []string{"Upgrade"}, 218 headerUpgrade: []string{"websocket"}, 219 headerSecExtensions: []string{"foo;bar=1"}, 220 }, 221 }, 222 accept: acceptValid, 223 }, 224 { 225 dialer: Dialer{ 226 Extensions: []httphead.Option{ 227 httphead.NewOption("foo", map[string]string{ 228 "bar": "1", 229 }), 230 httphead.NewOption("baz", nil), 231 }, 232 }, 233 res: &http.Response{ 234 StatusCode: 101, 235 ProtoMajor: 1, 236 ProtoMinor: 1, 237 Header: http.Header{ 238 headerConnection: []string{"Upgrade"}, 239 headerUpgrade: []string{"websocket"}, 240 }, 241 }, 242 accept: acceptValid, 243 }, 244 { 245 dialer: Dialer{ 246 Protocols: []string{"xml", "json", "soap"}, 247 Extensions: []httphead.Option{ 248 httphead.NewOption("foo", map[string]string{ 249 "bar": "1", 250 }), 251 httphead.NewOption("baz", nil), 252 }, 253 }, 254 res: &http.Response{ 255 StatusCode: 101, 256 ProtoMajor: 1, 257 ProtoMinor: 1, 258 Header: http.Header{ 259 headerConnection: []string{"Upgrade"}, 260 headerUpgrade: []string{"websocket"}, 261 headerSecProtocol: []string{"json"}, 262 headerSecExtensions: []string{"foo;bar=1"}, 263 }, 264 }, 265 accept: acceptValid, 266 }, 267 { 268 name: "resp with frames", 269 res: &http.Response{ 270 StatusCode: 101, 271 ProtoMajor: 1, 272 ProtoMinor: 1, 273 Header: http.Header{ 274 headerConnection: []string{"Upgrade"}, 275 headerUpgrade: []string{"websocket"}, 276 }, 277 }, 278 accept: acceptValid, 279 frames: []Frame{ 280 NewTextFrame([]byte("hello, gopherizer!")), 281 }, 282 wantBuffer: true, 283 }, 284 { 285 name: "resp with body", 286 res: &http.Response{ 287 StatusCode: 101, 288 ProtoMajor: 1, 289 ProtoMinor: 1, 290 Header: http.Header{ 291 headerConnection: []string{"Upgrade"}, 292 headerUpgrade: []string{"websocket"}, 293 }, 294 Body: ioutil.NopCloser(bytes.NewReader([]byte(`hello, gopher!`))), 295 ContentLength: 14, 296 }, 297 accept: acceptValid, 298 wantBuffer: true, 299 }, 300 301 // Error cases. 302 303 { 304 name: "bad proto", 305 res: &http.Response{ 306 StatusCode: 101, 307 ProtoMajor: 2, 308 ProtoMinor: 1, 309 Header: make(http.Header), 310 }, 311 err: ErrHandshakeBadProtocol, 312 }, 313 { 314 name: "bad status", 315 res: &http.Response{ 316 StatusCode: 400, 317 ProtoMajor: 1, 318 ProtoMinor: 1, 319 Header: make(http.Header), 320 }, 321 err: StatusError(400), 322 wantBuffer: false, 323 }, 324 { 325 name: "bad status with body", 326 res: &http.Response{ 327 StatusCode: 400, 328 ProtoMajor: 1, 329 ProtoMinor: 1, 330 Header: make(http.Header), 331 Body: ioutil.NopCloser(bytes.NewReader( 332 []byte(`<error description here>`), 333 )), 334 ContentLength: 24, 335 }, 336 err: StatusError(400), 337 wantBuffer: false, 338 }, 339 { 340 name: "bad upgrade", 341 res: &http.Response{ 342 StatusCode: 101, 343 ProtoMajor: 1, 344 ProtoMinor: 1, 345 Header: http.Header{ 346 headerConnection: []string{"Upgrade"}, 347 }, 348 }, 349 accept: acceptValid, 350 err: ErrHandshakeBadUpgrade, 351 }, 352 { 353 name: "bad upgrade", 354 res: &http.Response{ 355 StatusCode: 101, 356 ProtoMajor: 1, 357 ProtoMinor: 1, 358 Header: http.Header{ 359 headerConnection: []string{"Upgrade"}, 360 headerUpgrade: []string{"oops"}, 361 }, 362 }, 363 accept: acceptValid, 364 err: ErrHandshakeBadUpgrade, 365 }, 366 { 367 name: "bad connection", 368 res: &http.Response{ 369 StatusCode: 101, 370 ProtoMajor: 1, 371 ProtoMinor: 1, 372 Header: http.Header{ 373 headerUpgrade: []string{"websocket"}, 374 }, 375 }, 376 accept: acceptValid, 377 err: ErrHandshakeBadConnection, 378 }, 379 { 380 name: "bad connection", 381 res: &http.Response{ 382 StatusCode: 101, 383 ProtoMajor: 1, 384 ProtoMinor: 1, 385 Header: http.Header{ 386 headerConnection: []string{"oops!"}, 387 headerUpgrade: []string{"websocket"}, 388 }, 389 }, 390 accept: acceptValid, 391 err: ErrHandshakeBadConnection, 392 }, 393 { 394 name: "bad accept", 395 res: &http.Response{ 396 StatusCode: 101, 397 ProtoMajor: 1, 398 ProtoMinor: 1, 399 Header: http.Header{ 400 headerConnection: []string{"Upgrade"}, 401 headerUpgrade: []string{"websocket"}, 402 }, 403 }, 404 accept: acceptInvalid, 405 err: ErrHandshakeBadSecAccept, 406 }, 407 { 408 name: "bad accept", 409 res: &http.Response{ 410 StatusCode: 101, 411 ProtoMajor: 1, 412 ProtoMinor: 1, 413 Header: http.Header{ 414 headerConnection: []string{"Upgrade"}, 415 headerUpgrade: []string{"websocket"}, 416 }, 417 }, 418 accept: acceptNo, 419 err: ErrHandshakeBadSecAccept, 420 }, 421 { 422 name: "bad subprotocol", 423 res: &http.Response{ 424 StatusCode: 101, 425 ProtoMajor: 1, 426 ProtoMinor: 1, 427 Header: http.Header{ 428 headerConnection: []string{"Upgrade"}, 429 headerUpgrade: []string{"websocket"}, 430 headerSecProtocol: []string{"oops!"}, 431 }, 432 }, 433 accept: acceptValid, 434 err: ErrHandshakeBadSubProtocol, 435 }, 436 { 437 name: "bad extensions", 438 res: &http.Response{ 439 StatusCode: 101, 440 ProtoMajor: 1, 441 ProtoMinor: 1, 442 Header: http.Header{ 443 headerConnection: []string{"Upgrade"}, 444 headerUpgrade: []string{"websocket"}, 445 headerSecExtensions: []string{"foo,bar;baz=1"}, 446 }, 447 }, 448 accept: acceptValid, 449 err: ErrHandshakeBadExtensions, 450 }, 451 } { 452 t.Run(test.name, func(t *testing.T) { 453 client, server := net.Pipe() 454 go func() { 455 // This routine is our fake web-server. It reads request after 456 // client wrote it. Then it optionally could send some frames 457 // set in test case. 458 req, err := http.ReadRequest(bufio.NewReader(client)) 459 if err != nil { 460 t.Fatal(err) 461 } 462 463 switch test.accept { 464 case acceptInvalid: 465 k := make([]byte, nonceSize) 466 rand.Read(k) 467 nonce := string(k) 468 accept := makeAccept(strToBytes(nonce)) 469 test.res.Header.Set(headerSecAccept, string(accept)) 470 case acceptValid: 471 nonce := req.Header.Get(headerSecKey) 472 accept := makeAccept(strToBytes(nonce)) 473 test.res.Header.Set(headerSecAccept, string(accept)) 474 } 475 476 test.res.Request = req 477 bts := dumpResponse(test.res) 478 479 var buf bytes.Buffer 480 for _, f := range test.frames { 481 if err := WriteFrame(&buf, f); err != nil { 482 t.Fatal(err) 483 } 484 bts = append(bts, buf.Bytes()...) 485 buf.Reset() 486 } 487 488 client.Write(bts) 489 client.Close() 490 }() 491 492 conn := &stubConn{ 493 read: func(p []byte) (int, error) { 494 return server.Read(p) 495 }, 496 write: func(p []byte) (int, error) { 497 n, err := server.Write(p) 498 return n, err 499 }, 500 close: func() error { return nil }, 501 } 502 503 test.dialer.NetDial = func(_ context.Context, _, _ string) (net.Conn, error) { 504 return conn, nil 505 } 506 test.dialer.OnStatusError = func(status int, reason []byte, r io.Reader) { 507 res, err := http.ReadResponse( 508 bufio.NewReader(r), 509 nil, 510 ) 511 if err != nil { 512 t.Errorf("read response inside OnStatusError error: %v", err) 513 } 514 if act, exp := dumpResponse(res), dumpResponse(test.res); !bytes.Equal(act, exp) { 515 t.Errorf( 516 "unexpected response from OnStatusError:\nact:\n%s\nexp:\n%s\n", 517 act, exp, 518 ) 519 } 520 } 521 522 _, br, _, err := test.dialer.Dial(context.Background(), "ws://gobwas.com") 523 if test.err != err { 524 t.Fatalf("unexpected error: %v;\n\twant %v", err, test.err) 525 } 526 527 if (test.wantBuffer || len(test.frames) > 0) && br == nil { 528 t.Fatalf("Dial() returned empty bufio.Reader") 529 } 530 if !test.wantBuffer && br != nil { 531 t.Fatalf("Dial() returned non-empty bufio.Reader") 532 } 533 for i, exp := range test.frames { 534 act, err := ReadFrame(br) 535 if err != nil { 536 t.Fatalf("can not read %d-th frame: %v", i, err) 537 } 538 if act.Header != exp.Header { 539 t.Fatalf( 540 "unexpected %d-th frame header: %v; want %v", 541 i, act.Header, exp.Header, 542 ) 543 } 544 if !bytes.Equal(act.Payload, exp.Payload) { 545 t.Fatalf( 546 "unexpected %d-th frame payload:\n%v\nwant:\n%v", 547 i, act.Payload, exp.Payload, 548 ) 549 } 550 } 551 }) 552 } 553 } 554 555 // Used to emulate net.Error behavior, which is usually returned when 556 // connection deadline exceeds. 557 type errTimeout struct { 558 error 559 } 560 561 func (errTimeout) Timeout() bool { return true } 562 func (errTimeout) Temporary() bool { return false } 563 564 func TestDialerCancelation(t *testing.T) { 565 ioErrDeadline := errTimeout{ 566 fmt.Errorf("stub: i/o timeout"), 567 } 568 569 for _, test := range []struct { 570 name string 571 dialer Dialer 572 dialDelay time.Duration 573 ctxTimeout time.Duration 574 ctxCancelAfter time.Duration 575 err error 576 }{ 577 { 578 ctxTimeout: time.Millisecond * 100, 579 err: context.DeadlineExceeded, 580 }, 581 { 582 ctxCancelAfter: time.Millisecond * 100, 583 err: context.Canceled, 584 }, 585 { 586 dialer: Dialer{ 587 Timeout: time.Millisecond * 100, 588 }, 589 ctxTimeout: time.Millisecond * 150, 590 err: context.DeadlineExceeded, 591 }, 592 { 593 ctxTimeout: time.Millisecond * 100, 594 dialDelay: time.Millisecond * 200, 595 err: context.DeadlineExceeded, 596 }, 597 { 598 ctxCancelAfter: time.Millisecond * 100, 599 dialDelay: time.Millisecond * 200, 600 err: context.Canceled, 601 }, 602 } { 603 t.Run(test.name, func(t *testing.T) { 604 var timer *time.Timer 605 deadline := make(chan error, 10) 606 conn := &stubConn{ 607 setDeadline: func(t time.Time) error { 608 if timer != nil { 609 timer.Stop() 610 } 611 if t.IsZero() { 612 return nil 613 } 614 d := t.Sub(time.Now()) 615 if d < 0 { 616 deadline <- ioErrDeadline 617 } else { 618 timer = time.AfterFunc(d, func() { 619 deadline <- ioErrDeadline 620 }) 621 } 622 623 return nil 624 }, 625 read: func(p []byte) (int, error) { 626 if err := <-deadline; err != nil { 627 return 0, err 628 } 629 return len(p), nil 630 }, 631 write: func(p []byte) (int, error) { 632 if err := <-deadline; err != nil { 633 return 0, err 634 } 635 return len(p), nil 636 }, 637 } 638 639 test.dialer.NetDial = func(ctx context.Context, _, _ string) (net.Conn, error) { 640 if t := test.dialDelay; t != 0 { 641 delay := time.After(t) 642 select { 643 case <-delay: 644 case <-ctx.Done(): 645 return nil, ctx.Err() 646 } 647 } 648 return conn, nil 649 } 650 651 ctx := context.Background() 652 if t := test.ctxTimeout; t != 0 { 653 var cancel context.CancelFunc 654 ctx, cancel = context.WithTimeout(ctx, t) 655 defer cancel() 656 } 657 if t := test.ctxCancelAfter; t != 0 { 658 var cancel context.CancelFunc 659 ctx, cancel = context.WithCancel(ctx) 660 time.AfterFunc(t, cancel) 661 } 662 663 _, _, _, err := test.dialer.Dial(ctx, "ws://gobwas.com") 664 if err != test.err { 665 t.Fatalf("unexpected error: %q; want %q", err, test.err) 666 } 667 }) 668 } 669 } 670 671 func BenchmarkDialer(b *testing.B) { 672 for _, test := range []struct { 673 dialer Dialer 674 }{ 675 { 676 dialer: DefaultDialer, 677 }, 678 } { 679 // We need to "mock" the rand.Read method used to generate nonce random 680 // bytes for Sec-WebSocket-Key header. 681 rand.Seed(0) 682 need := b.N * nonceKeySize 683 nonceBytes := make([]byte, need) 684 n, err := rand.Read(nonceBytes) 685 if err != nil { 686 b.Fatal(err) 687 } 688 if n != need { 689 b.Fatalf("not enough random nonce bytes: %d; want %d", n, need) 690 } 691 rand.Seed(0) 692 693 resp := &http.Response{ 694 StatusCode: 101, 695 ProtoMajor: 1, 696 ProtoMinor: 1, 697 Header: http.Header{ 698 headerConnection: []string{"Upgrade"}, 699 headerUpgrade: []string{"websocket"}, 700 headerSecAccept: []string{"fill it later"}, 701 }, 702 } 703 rs := make([][]byte, b.N) 704 for i := range rs { 705 nonce := make([]byte, nonceSize) 706 base64.StdEncoding.Encode( 707 nonce, 708 nonceBytes[i*nonceKeySize:i*nonceKeySize+nonceKeySize], 709 ) 710 accept := makeAccept(nonce) 711 resp.Header[headerSecAccept] = []string{string(accept)} 712 rs[i] = dumpResponse(resp) 713 } 714 715 var i int 716 conn := stubConn{ 717 read: func(p []byte) (int, error) { 718 bts := rs[i] 719 if len(p) < len(bts) { 720 b.Fatalf("short buffer") 721 } 722 return copy(p, bts), io.EOF 723 }, 724 write: func(p []byte) (int, error) { 725 return len(p), nil 726 }, 727 } 728 var nc net.Conn = conn 729 test.dialer.NetDial = func(_ context.Context, net, addr string) (net.Conn, error) { 730 return nc, nil 731 } 732 733 b.ResetTimer() 734 for i = 0; i < b.N; i++ { 735 _, _, _, err := test.dialer.Dial(context.Background(), "ws://example.org") 736 if err != nil { 737 b.Fatal(err) 738 } 739 } 740 } 741 } 742 743 func TestHostPort(t *testing.T) { 744 for _, test := range []struct { 745 name string 746 host string 747 port string 748 749 expHostname string 750 expHostport string 751 }{ 752 { 753 host: "foo", 754 port: ":80", 755 expHostname: "foo", 756 expHostport: "foo:80", 757 }, 758 { 759 host: "foo:1234", 760 port: ":80", 761 expHostname: "foo", 762 expHostport: "foo:1234", 763 }, 764 { 765 name: "ipv4", 766 host: "127.0.0.1", 767 port: ":80", 768 expHostname: "127.0.0.1", 769 expHostport: "127.0.0.1:80", 770 }, 771 { 772 name: "ipv4", 773 host: "127.0.0.1:1234", 774 port: ":80", 775 expHostname: "127.0.0.1", 776 expHostport: "127.0.0.1:1234", 777 }, 778 { 779 name: "ipv6", 780 host: "[0:0:0:0:0:0:0:1]", 781 port: ":80", 782 expHostname: "[0:0:0:0:0:0:0:1]", 783 expHostport: "[0:0:0:0:0:0:0:1]:80", 784 }, 785 { 786 name: "ipv6", 787 host: "[0:0:0:0:0:0:0:1]:1234", 788 port: ":80", 789 expHostname: "[0:0:0:0:0:0:0:1]", 790 expHostport: "[0:0:0:0:0:0:0:1]:1234", 791 }, 792 } { 793 t.Run(test.name, func(t *testing.T) { 794 actHostname, actHostport := hostport(test.host, test.port) 795 if actHostname != test.expHostname { 796 t.Errorf( 797 "actual hostname = %q; want %q", 798 actHostname, test.expHostname, 799 ) 800 } 801 if actHostport != test.expHostport { 802 t.Errorf( 803 "actual hostname = %q; want %q", 804 actHostport, test.expHostport, 805 ) 806 } 807 }) 808 } 809 } 810 811 type stubConn struct { 812 read func([]byte) (int, error) 813 write func([]byte) (int, error) 814 close func() error 815 setDeadline func(time.Time) error 816 setWriteDeadline func(time.Time) error 817 setReadDeadline func(time.Time) error 818 } 819 820 func (s stubConn) Read(p []byte) (int, error) { return s.read(p) } 821 func (s stubConn) Write(p []byte) (int, error) { return s.write(p) } 822 func (s stubConn) LocalAddr() net.Addr { return nil } 823 func (s stubConn) RemoteAddr() net.Addr { return nil } 824 func (s stubConn) Close() error { 825 if s.close != nil { 826 return s.close() 827 } 828 return nil 829 } 830 func (s stubConn) SetDeadline(t time.Time) error { 831 if s.setDeadline != nil { 832 return s.setDeadline(t) 833 } 834 return nil 835 } 836 func (s stubConn) SetReadDeadline(t time.Time) error { 837 if s.setReadDeadline != nil { 838 return s.setReadDeadline(t) 839 } 840 return nil 841 } 842 func (s stubConn) SetWriteDeadline(t time.Time) error { 843 if s.setWriteDeadline != nil { 844 return s.setWriteDeadline(t) 845 } 846 return nil 847 }