package wsimport ()// Constants used by Dialer.const (DefaultClientReadBufferSize = 4096DefaultClientWriteBufferSize = 4096)// Handshake represents handshake result.typeHandshakestruct {// Protocol is the subprotocol selected during handshake. Protocol string// Extensions is the list of negotiated extensions. Extensions []httphead.Option}// Errors used by the websocket client.var (ErrHandshakeBadStatus = fmt.Errorf("unexpected http status")ErrHandshakeBadSubProtocol = fmt.Errorf("unexpected protocol in %q header", headerSecProtocol)ErrHandshakeBadExtensions = fmt.Errorf("unexpected extensions in %q header", headerSecProtocol))// DefaultDialer is dialer that holds no options and is used by Dial function.varDefaultDialerDialer// Dial is like Dialer{}.Dial().func ( context.Context, string) (net.Conn, *bufio.Reader, Handshake, error) {returnDefaultDialer.Dial(, )}// Dialer contains options for establishing websocket connection to an url.typeDialerstruct {// ReadBufferSize and WriteBufferSize is an I/O buffer sizes. // They used to read and write http data while upgrading to WebSocket. // Allocated buffers are pooled with sync.Pool to avoid extra allocations. // // If a size is zero then default value is used. ReadBufferSize, WriteBufferSize int// Timeout is the maximum amount of time a Dial() will wait for a connect // and an handshake to complete. // // The default is no timeout. Timeout time.Duration// Protocols is the list of subprotocols that the client wants to speak, // ordered by preference. // // See https://tools.ietf.org/html/rfc6455#section-4.1 Protocols []string// Extensions is the list of extensions that client wants to speak. // // Note that if server decides to use some of this extensions, Dial() will // return Handshake struct containing a slice of items, which are the // shallow copies of the items from this list. That is, internals of // Extensions items are shared during Dial(). // // See https://tools.ietf.org/html/rfc6455#section-4.1 // See https://tools.ietf.org/html/rfc6455#section-9.1 Extensions []httphead.Option// Header is an optional HandshakeHeader instance that could be used to // write additional headers to the handshake request. // // It used instead of any key-value mappings to avoid allocations in user // land. Header HandshakeHeader// OnStatusError is the callback that will be called after receiving non // "101 Continue" HTTP response status. It receives an io.Reader object // representing server response bytes. That is, it gives ability to parse // HTTP response somehow (probably with http.ReadResponse call) and make a // decision of further logic. // // The arguments are only valid until the callback returns. OnStatusError func(status int, reason []byte, resp io.Reader)// OnHeader is the callback that will be called after successful parsing of // header, that is not used during WebSocket handshake procedure. That is, // it will be called with non-websocket headers, which could be relevant // for application-level logic. // // The arguments are only valid until the callback returns. // // Returned value could be used to prevent processing response. OnHeader func(key, value []byte) (err error)// NetDial is the function that is used to get plain tcp connection. // If it is not nil, then it is used instead of net.Dialer. NetDial func(ctx context.Context, network, addr string) (net.Conn, error)// TLSClient is the callback that will be called after successful dial with // received connection and its remote host name. If it is nil, then the // default tls.Client() will be used. // If it is not nil, then TLSConfig field is ignored. TLSClient func(conn net.Conn, hostname string) net.Conn// TLSConfig is passed to tls.Client() to start TLS over established // connection. If TLSClient is not nil, then it is ignored. If TLSConfig is // non-nil and its ServerName is empty, then for every Dial() it will be // cloned and appropriate ServerName will be set. TLSConfig *tls.Config// WrapConn is the optional callback that will be called when connection is // ready for an i/o. That is, it will be called after successful dial and // TLS initialization (for "wss" schemes). It may be helpful for different // user land purposes such as end to end encryption. // // Note that for debugging purposes of an http handshake (e.g. sent request // and received response), there is an wsutil.DebugDialer struct. WrapConn func(conn net.Conn) net.Conn}// Dial connects to the url host and upgrades connection to WebSocket.//// If server has sent frames right after successful handshake then returned// buffer will be non-nil. In other cases buffer is always nil. For better// memory efficiency received non-nil bufio.Reader should be returned to the// inner pool with PutReader() function after use.//// Note that Dialer does not implement IDNA (RFC5895) logic as net/http does.// If you want to dial non-ascii host name, take care of its name serialization// avoiding bad request issues. For more info see net/http Request.Write()// implementation, especially cleanHost() function.func ( Dialer) ( context.Context, string) ( net.Conn, *bufio.Reader, Handshake, error) { , := url.ParseRequestURI()if != nil {returnnil, nil, , }// Prepare context to dial with. Initially it is the same as original, but // if d.Timeout is non-zero and points to time that is before ctx.Deadline, // we use more shorter context for dial. := vartime.Timeif := .Timeout; != 0 { = time.Now().Add()if , := .Deadline(); ! || .Before() {varcontext.CancelFunc , = context.WithDeadline(, )defer () } }if , = .dial(, ); != nil {return , nil, , }deferfunc() {if != nil { .Close() } }()if == context.Background() {// No need to start I/O interrupter goroutine which is not zero-cost. .SetDeadline()defer .SetDeadline(noDeadline) } else {// Context could be canceled or its deadline could be exceeded. // Start the interrupter goroutine to handle context cancelation. := setupContextDeadliner(, )deferfunc() {// Map Upgrade() error to a possible context expiration error. That // is, even if Upgrade() err is nil, context could be already // expired and connection be "poisoned" by SetDeadline() call. // In that case we must not return ctx.Err() error. (&) }() } , , = .Upgrade(, )return , , , }var (// netEmptyDialer is a net.Dialer without options, used in Dialer.dial() if // Dialer.NetDial is not provided. netEmptyDialer net.Dialer// tlsEmptyConfig is an empty tls.Config used as default one. tlsEmptyConfig tls.Config)func tlsDefaultConfig() *tls.Config {return &tlsEmptyConfig}func hostport(, string) (, string) {var ( = strings.LastIndexByte(, ':') = strings.IndexByte(, ']') )if > {return [:], }return , + }func ( Dialer) ( context.Context, *url.URL) ( net.Conn, error) { := .NetDialif == nil { = netEmptyDialer.DialContext }switch .Scheme {case"ws": , := hostport(.Host, ":80") , = (, "tcp", )case"wss": , := hostport(.Host, ":443") , = (, "tcp", )if != nil {returnnil, } := .TLSClientif == nil { = .tlsClient } = (, )default:returnnil, fmt.Errorf("unexpected websocket scheme: %q", .Scheme) }if := .WrapConn; != nil { = () }return , }func ( Dialer) ( net.Conn, string) net.Conn { := .TLSConfigif == nil { = tlsDefaultConfig() }if .ServerName == "" { = tlsCloneConfig() .ServerName = }// Do not make conn.Handshake() here because downstairs we will prepare // i/o on this conn with proper context's timeout handling.returntls.Client(, )}var (// This variables are set like in net/net.go. // noDeadline is just zero value for readability. noDeadline = time.Time{}// aLongTimeAgo is a non-zero time, far in the past, used for immediate // cancelation of dials. aLongTimeAgo = time.Unix(42, 0))// Upgrade writes an upgrade request to the given io.ReadWriter conn at given// url u and reads a response from it.//// It is a caller responsibility to manage I/O deadlines on conn.//// It returns handshake info and some bytes which could be written by the peer// right after response and be caught by us during buffered read.func ( Dialer) ( io.ReadWriter, *url.URL) ( *bufio.Reader, Handshake, error) {// headerSeen constants helps to report whether or not some header was seen // during reading request bytes.const ( = 1 << iota// headerSeenAll is the value that we expect to receive at the end of // headers read/parse loop. = 0 | | | ) = pbufio.GetReader(,nonZero(.ReadBufferSize, DefaultClientReadBufferSize), ) := pbufio.GetWriter(,nonZero(.WriteBufferSize, DefaultClientWriteBufferSize), )deferfunc() {pbufio.PutWriter()if .Buffered() == 0 || != nil {// Server does not wrote additional bytes to the connection or // error occurred. That is, no reason to return buffer.pbufio.PutReader() = nil } }() := make([]byte, nonceSize)initNonce()httpWriteUpgradeRequest(, , , .Protocols, .Extensions, .Header)if := .Flush(); != nil {return , , }// Read HTTP status line like "HTTP/1.1 101 Switching Protocols". , := readLine()if != nil {return , , }// Begin validation of the response. // See https://tools.ietf.org/html/rfc6455#section-4.2.2 // Parse request line data like HTTP version, uri and method. , := httpParseResponseLine()if != nil {return , , }// Even if RFC says "1.1 or higher" without mentioning the part of the // version, we apply it only to minor part.if .major != 1 || .minor < 1 { = ErrHandshakeBadProtocolreturn , , }if .status != http.StatusSwitchingProtocols { = StatusError(.status)if := .OnStatusError; != nil {// Invoke callback with multireader of status-line bytes br. (.status, .reason,io.MultiReader(bytes.NewReader(),strings.NewReader(crlf), , ), ) }return , , }// If response status is 101 then we expect all technical headers to be // valid. If not, then we stop processing response without giving user // ability to read non-technical headers. That is, we do not distinguish // technical errors (such as parsing error) and protocol errors.varbytefor { , := readLine()if != nil { = return , , }iflen() == 0 {// Blank line, no more lines to read.break } , , := httpParseHeaderLine()if ! { = ErrMalformedResponsereturn , , }switchbtsToString() {caseheaderUpgradeCanonical: |= if !bytes.Equal(, specHeaderValueUpgrade) && !bytes.EqualFold(, specHeaderValueUpgrade) { = ErrHandshakeBadUpgradereturn , , }caseheaderConnectionCanonical: |= // Note that as RFC6455 says: // > A |Connection| header field with value "Upgrade". // That is, in server side, "Connection" header could contain // multiple token. But in response it must contains exactly one.if !bytes.Equal(, specHeaderValueConnection) && !bytes.EqualFold(, specHeaderValueConnection) { = ErrHandshakeBadConnectionreturn , , }caseheaderSecAcceptCanonical: |= if !checkAcceptFromNonce(, ) { = ErrHandshakeBadSecAcceptreturn , , }caseheaderSecProtocolCanonical:// RFC6455 1.3: // "The server selects one or none of the acceptable protocols // and echoes that value in its handshake to indicate that it has // selected that protocol."for , := range .Protocols {ifstring() == { .Protocol = break } }if .Protocol == "" {// Server echoed subprotocol that is not present in client // requested protocols. = ErrHandshakeBadSubProtocolreturn , , }caseheaderSecExtensionsCanonical: .Extensions, = matchSelectedExtensions(, .Extensions, .Extensions)if != nil {return , , }default:if := .OnHeader; != nil {if := (, ); != nil { = return , , } } } }if == nil && != {switch {case & == 0: = ErrHandshakeBadUpgradecase & == 0: = ErrHandshakeBadConnectioncase & == 0: = ErrHandshakeBadSecAcceptdefault:panic("unknown headers state") } }return , , }// PutReader returns bufio.Reader instance to the inner reuse pool.// It is useful in rare cases, when Dialer.Dial() returns non-nil buffer which// contains unprocessed buffered data, that was sent by the server quickly// right after handshake.func ( *bufio.Reader) {pbufio.PutReader()}// StatusError contains an unexpected status-line code from the server.typeStatusErrorintfunc ( StatusError) () string {return"unexpected HTTP response status: " + strconv.Itoa(int())}func isTimeoutError( error) bool { , := .(net.Error)return && .Timeout()}func matchSelectedExtensions( []byte, , []httphead.Option) ([]httphead.Option, error) {iflen() == 0 {return , nil }var (inthttphead.Optionerror ) = -1 := func() ( bool) {for , := range {// A server accepts one or more extensions by including a // |Sec-WebSocket-Extensions| header field containing one or more // extensions that were requested by the client. // // The interpretation of any extension parameters, and what // constitutes a valid response by a server to a requested set of // parameters by a client, will be defined by each such extension.ifbytes.Equal(.Name, .Name) {// Check parsed extension to be present in client // requested extensions. We move matched extension // from client list to avoid allocation of httphead.Option.Name, // httphead.Option.Parameters have to be copied from the header .Parameters, _ = .Parameters.Copy(make([]byte, .Parameters.Size())) = append(, )returntrue } }returnfalse } := httphead.ScanOptions(, func( int, , , []byte) httphead.Control {if != {// Met next option. = if != 0 && !() {// Server returned non-requested extension. = ErrHandshakeBadExtensionsreturnhttphead.ControlBreak } = httphead.Option{Name: } }if != nil { .Parameters.Set(, ) }returnhttphead.ControlContinue })if ! { = ErrMalformedResponsereturn , }if !() {return , ErrHandshakeBadExtensions }return , }// setupContextDeadliner is a helper function that starts connection I/O// interrupter goroutine.//// Started goroutine calls SetDeadline() with long time ago value when context// become expired to make any I/O operations failed. It returns done function// that stops started goroutine and maps error received from conn I/O methods// to possible context expiration error.//// In concern with possible SetDeadline() call inside interrupter goroutine,// caller passes pointer to its I/O error (even if it is nil) to done(&err).// That is, even if I/O error is nil, context could be already expired and// connection "poisoned" by SetDeadline() call. In that case done(&err) will// store at *err ctx.Err() result. If err is caused not by timeout, it will// leaved untouched.func setupContextDeadliner( context.Context, net.Conn) ( func(*error)) {var ( = make(chanstruct{}) = make(chanerror, 1) )gofunc() {select {case<-: <- nilcase<-.Done():// Cancel i/o immediately. .SetDeadline(aLongTimeAgo) <- .Err() } }()returnfunc( *error) {close()// If ctx.Err() is non-nil and the original err is net.Error with // Timeout() == true, then it means that I/O was canceled by us by // SetDeadline(aLongTimeAgo) call, or by somebody else previously // by conn.SetDeadline(x). // // Even on race condition when both deadlines are expired // (SetDeadline() made not by us and context's), we prefer ctx.Err() to // be returned.if := <-; != nil && (* == nil || isTimeoutError(*)) { * = } }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.