|
|
@@ -6,12 +6,14 @@ package websocket
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
+ "context"
|
|
|
"crypto/tls"
|
|
|
"errors"
|
|
|
"io"
|
|
|
"io/ioutil"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
+ "net/http/httptrace"
|
|
|
"net/url"
|
|
|
"strings"
|
|
|
"time"
|
|
|
@@ -51,6 +53,10 @@ type Dialer struct {
|
|
|
// NetDial is nil, net.Dial is used.
|
|
|
NetDial func(network, addr string) (net.Conn, error)
|
|
|
|
|
|
+ // NetDialContext specifies the dial function for creating TCP connections. If
|
|
|
+ // NetDialContext is nil, net.DialContext is used.
|
|
|
+ NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
+
|
|
|
// Proxy specifies a function to return a proxy for a given
|
|
|
// Request. If the function returns a non-nil error, the
|
|
|
// request is aborted with the provided error.
|
|
|
@@ -95,6 +101,11 @@ type Dialer struct {
|
|
|
Jar http.CookieJar
|
|
|
}
|
|
|
|
|
|
+// Dial creates a new client connection by calling DialContext with a background context.
|
|
|
+func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
|
|
+ return d.DialContext(urlStr, requestHeader, context.Background())
|
|
|
+}
|
|
|
+
|
|
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
|
|
|
|
|
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
|
|
@@ -124,17 +135,18 @@ var DefaultDialer = &Dialer{
|
|
|
// nilDialer is dialer to use when receiver is nil.
|
|
|
var nilDialer Dialer = *DefaultDialer
|
|
|
|
|
|
-// Dial creates a new client connection. Use requestHeader to specify the
|
|
|
+// DialContext creates a new client connection. Use requestHeader to specify the
|
|
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
|
|
// Use the response.Header to get the selected subprotocol
|
|
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
|
|
//
|
|
|
+// The context will be used in the request and in the Dialer
|
|
|
+//
|
|
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
|
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
|
|
// etcetera. The response body may not contain the entire response and does not
|
|
|
// need to be closed by the application.
|
|
|
-func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
|
|
-
|
|
|
+func (d *Dialer) DialContext(urlStr string, requestHeader http.Header, ctx context.Context) (*Conn, *http.Response, error) {
|
|
|
if d == nil {
|
|
|
d = &nilDialer
|
|
|
}
|
|
|
@@ -172,6 +184,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
Header: make(http.Header),
|
|
|
Host: u.Host,
|
|
|
}
|
|
|
+ req = req.WithContext(ctx)
|
|
|
|
|
|
// Set the cookies present in the cookie jar of the dialer
|
|
|
if d.Jar != nil {
|
|
|
@@ -215,20 +228,30 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
|
|
}
|
|
|
|
|
|
- var deadline time.Time
|
|
|
if d.HandshakeTimeout != 0 {
|
|
|
- deadline = time.Now().Add(d.HandshakeTimeout)
|
|
|
+ var cancel func()
|
|
|
+ ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
|
|
+ defer cancel()
|
|
|
}
|
|
|
|
|
|
// Get network dial function.
|
|
|
- netDial := d.NetDial
|
|
|
- if netDial == nil {
|
|
|
- netDialer := &net.Dialer{Deadline: deadline}
|
|
|
- netDial = netDialer.Dial
|
|
|
+ var netDial func(network, add string) (net.Conn, error)
|
|
|
+
|
|
|
+ if d.NetDialContext != nil {
|
|
|
+ netDial = func(network, addr string) (net.Conn, error) {
|
|
|
+ return d.NetDialContext(ctx, network, addr)
|
|
|
+ }
|
|
|
+ } else if d.NetDial != nil {
|
|
|
+ netDial = d.NetDial
|
|
|
+ } else {
|
|
|
+ netDialer := &net.Dialer{}
|
|
|
+ netDial = func(network, addr string) (net.Conn, error) {
|
|
|
+ return netDialer.DialContext(ctx, network, addr)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// If needed, wrap the dial function to set the connection deadline.
|
|
|
- if !deadline.Equal(time.Time{}) {
|
|
|
+ if deadline, ok := ctx.Deadline(); ok {
|
|
|
forwardDial := netDial
|
|
|
netDial = func(network, addr string) (net.Conn, error) {
|
|
|
c, err := forwardDial(network, addr)
|
|
|
@@ -260,7 +283,17 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
}
|
|
|
|
|
|
hostPort, hostNoPort := hostPortNoPort(u)
|
|
|
+ trace := httptrace.ContextClientTrace(ctx)
|
|
|
+ if trace != nil && trace.GetConn != nil {
|
|
|
+ trace.GetConn(hostPort)
|
|
|
+ }
|
|
|
+
|
|
|
netConn, err := netDial("tcp", hostPort)
|
|
|
+ if trace != nil && trace.GotConn != nil {
|
|
|
+ trace.GotConn(httptrace.GotConnInfo{
|
|
|
+ Conn: netConn,
|
|
|
+ })
|
|
|
+ }
|
|
|
if err != nil {
|
|
|
return nil, nil, err
|
|
|
}
|
|
|
@@ -278,13 +311,16 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
}
|
|
|
tlsConn := tls.Client(netConn, cfg)
|
|
|
netConn = tlsConn
|
|
|
- if err := tlsConn.Handshake(); err != nil {
|
|
|
- return nil, nil, err
|
|
|
+
|
|
|
+ var err error
|
|
|
+ if trace != nil {
|
|
|
+ err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
|
|
+ } else {
|
|
|
+ err = doHandshake(tlsConn, cfg)
|
|
|
}
|
|
|
- if !cfg.InsecureSkipVerify {
|
|
|
- if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, err
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -294,6 +330,12 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
return nil, nil, err
|
|
|
}
|
|
|
|
|
|
+ if trace != nil && trace.GotFirstResponseByte != nil {
|
|
|
+ if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
|
|
+ trace.GotFirstResponseByte()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
resp, err := http.ReadResponse(conn.br, req)
|
|
|
if err != nil {
|
|
|
return nil, nil, err
|
|
|
@@ -339,3 +381,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|
|
netConn = nil // to avoid close in defer.
|
|
|
return conn, resp, nil
|
|
|
}
|
|
|
+
|
|
|
+func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
|
|
+ if err := tlsConn.Handshake(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !cfg.InsecureSkipVerify {
|
|
|
+ if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|