@@ -30,50 +30,17 @@ var ErrBadHandshake = errors.New("websocket: bad handshake")
30
30
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
31
31
// non-nil *http.Response so that callers can handle redirects, authentication,
32
32
// etc.
33
+ //
34
+ // Deprecated: Use Dialer instead.
33
35
func NewClient (netConn net.Conn , u * url.URL , requestHeader http.Header , readBufSize , writeBufSize int ) (c * Conn , response * http.Response , err error ) {
34
- challengeKey , err := generateChallengeKey ()
35
- if err != nil {
36
- return nil , nil , err
36
+ d := Dialer {
37
+ ReadBufferSize : readBufSize ,
38
+ WriteBufferSize : writeBufSize ,
39
+ NetDial : func (net , addr string ) (net.Conn , error ) {
40
+ return netConn , nil
41
+ },
37
42
}
38
- acceptKey := computeAcceptKey (challengeKey )
39
-
40
- c = newConn (netConn , false , readBufSize , writeBufSize )
41
- p := c .writeBuf [:0 ]
42
- p = append (p , "GET " ... )
43
- p = append (p , u .RequestURI ()... )
44
- p = append (p , " HTTP/1.1\r \n Host: " ... )
45
- p = append (p , u .Host ... )
46
- // "Upgrade" is capitalized for servers that do not use case insensitive
47
- // comparisons on header tokens.
48
- p = append (p , "\r \n Upgrade: websocket\r \n Connection: Upgrade\r \n Sec-WebSocket-Version: 13\r \n Sec-WebSocket-Key: " ... )
49
- p = append (p , challengeKey ... )
50
- p = append (p , "\r \n " ... )
51
- for k , vs := range requestHeader {
52
- for _ , v := range vs {
53
- p = append (p , k ... )
54
- p = append (p , ": " ... )
55
- p = append (p , v ... )
56
- p = append (p , "\r \n " ... )
57
- }
58
- }
59
- p = append (p , "\r \n " ... )
60
-
61
- if _ , err := netConn .Write (p ); err != nil {
62
- return nil , nil , err
63
- }
64
-
65
- resp , err := http .ReadResponse (c .br , & http.Request {Method : "GET" , URL : u })
66
- if err != nil {
67
- return nil , nil , err
68
- }
69
- if resp .StatusCode != 101 ||
70
- ! strings .EqualFold (resp .Header .Get ("Upgrade" ), "websocket" ) ||
71
- ! strings .EqualFold (resp .Header .Get ("Connection" ), "upgrade" ) ||
72
- resp .Header .Get ("Sec-Websocket-Accept" ) != acceptKey {
73
- return nil , resp , ErrBadHandshake
74
- }
75
- c .subprotocol = resp .Header .Get ("Sec-Websocket-Protocol" )
76
- return c , resp , nil
43
+ return d .Dial (u .String (), requestHeader )
77
44
}
78
45
79
46
// A Dialer contains options for connecting to WebSocket server.
@@ -99,17 +66,15 @@ type Dialer struct {
99
66
100
67
var errMalformedURL = errors .New ("malformed ws or wss URL" )
101
68
102
- // parseURL parses the URL. The url.Parse function is not used here because
103
- // url.Parse mangles the path.
69
+ // parseURL parses the URL.
70
+ //
71
+ // This function is a replacement for the standard library url.Parse function.
72
+ // In Go 1.4 and earlier, url.Parse loses information from the path.
104
73
func parseURL (s string ) (* url.URL , error ) {
105
74
// From the RFC:
106
75
//
107
76
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
108
77
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
109
- //
110
- // We don't use the net/url parser here because the dialer interface does
111
- // not provide a way for applications to work around percent deocding in
112
- // the net/url parser.
113
78
114
79
var u url.URL
115
80
switch {
@@ -131,7 +96,8 @@ func parseURL(s string) (*url.URL, error) {
131
96
}
132
97
133
98
if strings .Contains (u .Host , "@" ) {
134
- // WebSocket URIs do not contain user information.
99
+ // Don't bother parsing user information because user information is
100
+ // not allowed in websocket URIs.
135
101
return nil , errMalformedURL
136
102
}
137
103
@@ -166,16 +132,67 @@ var DefaultDialer = &Dialer{}
166
132
// etcetera. The response body may not contain the entire response and does not
167
133
// need to be closed by the application.
168
134
func (d * Dialer ) Dial (urlStr string , requestHeader http.Header ) (* Conn , * http.Response , error ) {
135
+
136
+ if d == nil {
137
+ d = & Dialer {}
138
+ }
139
+
140
+ challengeKey , err := generateChallengeKey ()
141
+ if err != nil {
142
+ return nil , nil , err
143
+ }
144
+
169
145
u , err := parseURL (urlStr )
170
146
if err != nil {
171
147
return nil , nil , err
172
148
}
173
149
174
- hostPort , hostNoPort := hostPortNoPort (u )
150
+ switch u .Scheme {
151
+ case "ws" :
152
+ u .Scheme = "http"
153
+ case "wss" :
154
+ u .Scheme = "https"
155
+ default :
156
+ return nil , nil , errMalformedURL
157
+ }
175
158
176
- if d == nil {
177
- d = & Dialer {}
159
+ if u .User != nil {
160
+ // User name and password are not allowed in websocket URIs.
161
+ return nil , nil , errMalformedURL
162
+ }
163
+
164
+ req := & http.Request {
165
+ Method : "GET" ,
166
+ URL : u ,
167
+ Proto : "HTTP/1.1" ,
168
+ ProtoMajor : 1 ,
169
+ ProtoMinor : 1 ,
170
+ Header : make (http.Header ),
171
+ Host : u .Host ,
172
+ }
173
+
174
+ // Set the request headers using the capitalization for names and values in
175
+ // RFC examples. Although the capitalization shouldn't matter, there are
176
+ // servers that depend on it. The Header.Set method is not used because the
177
+ // method canonicalizes the header names.
178
+ req .Header ["Upgrade" ] = []string {"websocket" }
179
+ req .Header ["Connection" ] = []string {"Upgrade" }
180
+ req .Header ["Sec-WebSocket-Key" ] = []string {challengeKey }
181
+ req .Header ["Sec-WebSocket-Version" ] = []string {"13" }
182
+ if len (d .Subprotocols ) > 0 {
183
+ req .Header ["Sec-WebSocket-Protocol" ] = []string {strings .Join (d .Subprotocols , ", " )}
178
184
}
185
+ for k , vs := range requestHeader {
186
+ if k == "Host" {
187
+ if len (vs ) > 0 {
188
+ req .Host = vs [0 ]
189
+ }
190
+ } else {
191
+ req .Header [k ] = vs
192
+ }
193
+ }
194
+
195
+ hostPort , hostNoPort := hostPortNoPort (u )
179
196
180
197
var deadline time.Time
181
198
if d .HandshakeTimeout != 0 {
@@ -203,7 +220,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
203
220
return nil , nil , err
204
221
}
205
222
206
- if u .Scheme == "wss " {
223
+ if u .Scheme == "https " {
207
224
cfg := d .TLSClientConfig
208
225
if cfg == nil {
209
226
cfg = & tls.Config {ServerName : hostNoPort }
@@ -224,45 +241,33 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
224
241
}
225
242
}
226
243
227
- if len (d .Subprotocols ) > 0 {
228
- h := http.Header {}
229
- for k , v := range requestHeader {
230
- h [k ] = v
231
- }
232
- h .Set ("Sec-Websocket-Protocol" , strings .Join (d .Subprotocols , ", " ))
233
- requestHeader = h
234
- }
235
-
236
- if len (requestHeader ["Host" ]) > 0 {
237
- // This can be used to supply a Host: header which is different from
238
- // the dial address.
239
- u .Host = requestHeader .Get ("Host" )
244
+ conn := newConn (netConn , false , d .ReadBufferSize , d .WriteBufferSize )
240
245
241
- // Drop "Host" header
242
- h := http.Header {}
243
- for k , v := range requestHeader {
244
- if k == "Host" {
245
- continue
246
- }
247
- h [k ] = v
248
- }
249
- requestHeader = h
246
+ if err := req .Write (netConn ); err != nil {
247
+ return nil , nil , err
250
248
}
251
249
252
- conn , resp , err := NewClient (netConn , u , requestHeader , d .ReadBufferSize , d .WriteBufferSize )
253
-
250
+ resp , err := http .ReadResponse (conn .br , req )
254
251
if err != nil {
255
- if err == ErrBadHandshake {
256
- // Before closing the network connection on return from this
257
- // function, slurp up some of the response to aid application
258
- // debugging.
259
- buf := make ([]byte , 1024 )
260
- n , _ := io .ReadFull (resp .Body , buf )
261
- resp .Body = ioutil .NopCloser (bytes .NewReader (buf [:n ]))
262
- }
263
- return nil , resp , err
252
+ return nil , nil , err
253
+ }
254
+ if resp .StatusCode != 101 ||
255
+ ! strings .EqualFold (resp .Header .Get ("Upgrade" ), "websocket" ) ||
256
+ ! strings .EqualFold (resp .Header .Get ("Connection" ), "upgrade" ) ||
257
+ resp .Header .Get ("Sec-Websocket-Accept" ) != computeAcceptKey (challengeKey ) {
258
+ // Before closing the network connection on return from this
259
+ // function, slurp up some of the response to aid application
260
+ // debugging.
261
+ buf := make ([]byte , 1024 )
262
+ n , _ := io .ReadFull (resp .Body , buf )
263
+ resp .Body = ioutil .NopCloser (bytes .NewReader (buf [:n ]))
264
+ return nil , resp , ErrBadHandshake
265
+ } else {
266
+ resp .Body = ioutil .NopCloser (bytes .NewReader ([]byte {}))
264
267
}
265
268
269
+ conn .subprotocol = resp .Header .Get ("Sec-Websocket-Protocol" )
270
+
266
271
netConn .SetDeadline (time.Time {})
267
272
netConn = nil // to avoid close in defer.
268
273
return conn , resp , nil
0 commit comments