client.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright 2009 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package websocket
  5. import (
  6. "bufio"
  7. "context"
  8. "io"
  9. "net"
  10. "net/http"
  11. "net/url"
  12. "time"
  13. )
  14. // DialError is an error that occurs while dialling a websocket server.
  15. type DialError struct {
  16. *Config
  17. Err error
  18. }
  19. func (e *DialError) Error() string {
  20. return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
  21. }
  22. // NewConfig creates a new WebSocket config for client connection.
  23. func NewConfig(server, origin string) (config *Config, err error) {
  24. config = new(Config)
  25. config.Version = ProtocolVersionHybi13
  26. config.Location, err = url.ParseRequestURI(server)
  27. if err != nil {
  28. return
  29. }
  30. config.Origin, err = url.ParseRequestURI(origin)
  31. if err != nil {
  32. return
  33. }
  34. config.Header = http.Header(make(map[string][]string))
  35. return
  36. }
  37. // NewClient creates a new WebSocket client connection over rwc.
  38. func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
  39. br := bufio.NewReader(rwc)
  40. bw := bufio.NewWriter(rwc)
  41. err = hybiClientHandshake(config, br, bw)
  42. if err != nil {
  43. return
  44. }
  45. buf := bufio.NewReadWriter(br, bw)
  46. ws = newHybiClientConn(config, buf, rwc)
  47. return
  48. }
  49. // Dial opens a new client connection to a WebSocket.
  50. func Dial(url_, protocol, origin string) (ws *Conn, err error) {
  51. config, err := NewConfig(url_, origin)
  52. if err != nil {
  53. return nil, err
  54. }
  55. if protocol != "" {
  56. config.Protocol = []string{protocol}
  57. }
  58. return DialConfig(config)
  59. }
  60. var portMap = map[string]string{
  61. "ws": "80",
  62. "wss": "443",
  63. }
  64. func parseAuthority(location *url.URL) string {
  65. if _, ok := portMap[location.Scheme]; ok {
  66. if _, _, err := net.SplitHostPort(location.Host); err != nil {
  67. return net.JoinHostPort(location.Host, portMap[location.Scheme])
  68. }
  69. }
  70. return location.Host
  71. }
  72. // DialConfig opens a new client connection to a WebSocket with a config.
  73. func DialConfig(config *Config) (ws *Conn, err error) {
  74. return config.DialContext(context.Background())
  75. }
  76. // DialContext opens a new client connection to a WebSocket, with context support for timeouts/cancellation.
  77. func (config *Config) DialContext(ctx context.Context) (*Conn, error) {
  78. if config.Location == nil {
  79. return nil, &DialError{config, ErrBadWebSocketLocation}
  80. }
  81. if config.Origin == nil {
  82. return nil, &DialError{config, ErrBadWebSocketOrigin}
  83. }
  84. dialer := config.Dialer
  85. if dialer == nil {
  86. dialer = &net.Dialer{}
  87. }
  88. client, err := dialWithDialer(ctx, dialer, config)
  89. if err != nil {
  90. return nil, &DialError{config, err}
  91. }
  92. // Cleanup the connection if we fail to create the websocket successfully
  93. success := false
  94. defer func() {
  95. if !success {
  96. _ = client.Close()
  97. }
  98. }()
  99. var ws *Conn
  100. var wsErr error
  101. doneConnecting := make(chan struct{})
  102. go func() {
  103. defer close(doneConnecting)
  104. ws, err = NewClient(config, client)
  105. if err != nil {
  106. wsErr = &DialError{config, err}
  107. }
  108. }()
  109. // The websocket.NewClient() function can block indefinitely, make sure that we
  110. // respect the deadlines specified by the context.
  111. select {
  112. case <-ctx.Done():
  113. // Force the pending operations to fail, terminating the pending connection attempt
  114. _ = client.SetDeadline(time.Now())
  115. <-doneConnecting // Wait for the goroutine that tries to establish the connection to finish
  116. return nil, &DialError{config, ctx.Err()}
  117. case <-doneConnecting:
  118. if wsErr == nil {
  119. success = true // Disarm the deferred connection cleanup
  120. }
  121. return ws, wsErr
  122. }
  123. }