Source File
server.go
Belonging Package
github.com/gobwas/ws
package wsimport ()// Constants used by ConnUpgrader.const (DefaultServerReadBufferSize = 4096DefaultServerWriteBufferSize = 512)// Errors used by both client and server when preparing WebSocket handshake.var (ErrHandshakeBadProtocol = RejectConnectionError(RejectionStatus(http.StatusHTTPVersionNotSupported),RejectionReason("handshake error: bad HTTP protocol version"),)ErrHandshakeBadMethod = RejectConnectionError(RejectionStatus(http.StatusMethodNotAllowed),RejectionReason("handshake error: bad HTTP request method"),)ErrHandshakeBadHost = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerHost)),)ErrHandshakeBadUpgrade = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerUpgrade)),)ErrHandshakeBadConnection = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerConnection)),)ErrHandshakeBadSecAccept = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerSecAccept)),)ErrHandshakeBadSecKey = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerSecKey)),)ErrHandshakeBadSecVersion = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerSecVersion)),))// ErrMalformedResponse is returned by Dialer to indicate that server response// can not be parsed.var ErrMalformedResponse = fmt.Errorf("malformed HTTP response")// ErrMalformedRequest is returned when HTTP request can not be parsed.var ErrMalformedRequest = RejectConnectionError(RejectionStatus(http.StatusBadRequest),RejectionReason("malformed HTTP request"),)// ErrHandshakeUpgradeRequired is returned by Upgrader to indicate that// connection is rejected because given WebSocket version is malformed.//// According to RFC6455:// If this version does not match a version understood by the server, the// server MUST abort the WebSocket handshake described in this section and// instead send an appropriate HTTP error code (such as 426 Upgrade Required)// and a |Sec-WebSocket-Version| header field indicating the version(s) the// server is capable of understanding.var ErrHandshakeUpgradeRequired = RejectConnectionError(RejectionStatus(http.StatusUpgradeRequired),RejectionHeader(HandshakeHeaderString(headerSecVersion+": 13\r\n")),RejectionReason(fmt.Sprintf("handshake error: bad %q header", headerSecVersion)),)// ErrNotHijacker is an error returned when http.ResponseWriter does not// implement http.Hijacker interface.var ErrNotHijacker = RejectConnectionError(RejectionStatus(http.StatusInternalServerError),RejectionReason("given http.ResponseWriter is not a http.Hijacker"),)// DefaultHTTPUpgrader is an HTTPUpgrader that holds no options and is used by// UpgradeHTTP function.var DefaultHTTPUpgrader HTTPUpgrader// UpgradeHTTP is like HTTPUpgrader{}.Upgrade().func ( *http.Request, http.ResponseWriter) (net.Conn, *bufio.ReadWriter, Handshake, error) {return DefaultHTTPUpgrader.Upgrade(, )}// DefaultUpgrader is an Upgrader that holds no options and is used by Upgrade// function.var DefaultUpgrader Upgrader// Upgrade is like Upgrader{}.Upgrade().func ( io.ReadWriter) (Handshake, error) {return DefaultUpgrader.Upgrade()}// HTTPUpgrader contains options for upgrading connection to websocket from// net/http Handler arguments.type HTTPUpgrader struct {// Timeout is the maximum amount of time an Upgrade() will spent while// writing handshake response.//// The default is no timeout.Timeout time.Duration// Header is an optional http.Header mapping that could be used to// write additional headers to the handshake response.//// Note that if present, it will be written in any result of handshake.Header http.Header// Protocol is the select function that is used to select subprotocol from// list requested by client. If this field is set, then the first matched// protocol is sent to a client as negotiated.Protocol func(string) bool// Extension is the select function that is used to select extensions from// list requested by client. If this field is set, then the all matched// extensions are sent to a client as negotiated.//// Deprecated: use Negotiate instead.Extension func(httphead.Option) bool// Negotiate is the callback that is used to negotiate extensions from// the client's offer. If this field is set, then the returned non-zero// extensions are sent to the client as accepted extensions in the// response.//// The argument is only valid until the Negotiate callback returns.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.Negotiate func(httphead.Option) (httphead.Option, error)}// Upgrade upgrades http connection to the websocket connection.//// It hijacks net.Conn from w and returns received net.Conn and// bufio.ReadWriter. On successful handshake it returns Handshake struct// describing handshake info.func ( HTTPUpgrader) ( *http.Request, http.ResponseWriter) ( net.Conn, *bufio.ReadWriter, Handshake, error) {// Hijack connection first to get the ability to write rejection errors the// same way as in Upgrader., , = hijack()if != nil {httpError(, .Error(), http.StatusInternalServerError)return , , ,}// See https://tools.ietf.org/html/rfc6455#section-4.1// The method of the request MUST be GET, and the HTTP version MUST be at least 1.1.var stringif .Method != http.MethodGet {= ErrHandshakeBadMethod} else if .ProtoMajor < 1 || (.ProtoMajor == 1 && .ProtoMinor < 1) {= ErrHandshakeBadProtocol} else if .Host == "" {= ErrHandshakeBadHost} else if := httpGetHeader(.Header, headerUpgradeCanonical); != "websocket" && !strings.EqualFold(, "websocket") {= ErrHandshakeBadUpgrade} else if := httpGetHeader(.Header, headerConnectionCanonical); != "Upgrade" && !strHasToken(, "upgrade") {= ErrHandshakeBadConnection} else if = httpGetHeader(.Header, headerSecKeyCanonical); len() != nonceSize {= ErrHandshakeBadSecKey} else if := httpGetHeader(.Header, headerSecVersionCanonical); != "13" {// According to RFC6455://// If this version does not match a version understood by the server,// the server MUST abort the WebSocket handshake described in this// section and instead send an appropriate HTTP error code (such as 426// Upgrade Required) and a |Sec-WebSocket-Version| header field// indicating the version(s) the server is capable of understanding.//// So we branching here cause empty or not present version does not// meet the ABNF rules of RFC6455://// version = DIGIT | (NZDIGIT DIGIT) |// ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)// ; Limited to 0-255 range, with no leading zeros//// That is, if version is really invalid – we sent 426 status, if it// not present or empty – it is 400.if != "" {= ErrHandshakeUpgradeRequired} else {= ErrHandshakeBadSecVersion}}if := .Protocol; == nil && != nil {:= .Header[headerSecProtocolCanonical]for := 0; < len() && == nil && .Protocol == ""; ++ {var bool.Protocol, = strSelectProtocol([], )if ! {= ErrMalformedRequest}}}if := .Negotiate; == nil && != nil {for , := range .Header[headerSecExtensionsCanonical] {.Extensions, = negotiateExtensions(strToBytes(), .Extensions, )if != nil {break}}}// DEPRECATED path.if := .Extension; == nil && != nil && .Negotiate == nil {:= .Header[headerSecExtensionsCanonical]for := 0; < len() && == nil; ++ {var bool.Extensions, = btsSelectExtensions(strToBytes([]), .Extensions, )if ! {= ErrMalformedRequest}}}// Clear deadlines set by server..SetDeadline(noDeadline)if := .Timeout; != 0 {.SetWriteDeadline(time.Now().Add())defer .SetWriteDeadline(noDeadline)}var handshakeHeaderif := .Header; != nil {[0] = HandshakeHeaderHTTP()}if == nil {httpWriteResponseUpgrade(.Writer, strToBytes(), , .WriteTo)= .Writer.Flush()} else {var intif , := .(*ConnectionRejectedError); {= .code[1] = .header}if == 0 {= http.StatusInternalServerError}httpWriteResponseError(.Writer, , , .WriteTo)// Do not store Flush() error to not override already existing one._ = .Writer.Flush()}return , , ,}// Upgrader contains options for upgrading connection to websocket.type Upgrader struct {// 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.//// Usually it is useful to set read buffer size bigger than write buffer// size because incoming request could contain long header values, such as// Cookie. Response, in other way, could be big only if user write multiple// custom headers. Usually response takes less than 256 bytes.ReadBufferSize, WriteBufferSize int// Protocol is a select function that is used to select subprotocol// from list requested by client. If this field is set, then the first matched// protocol is sent to a client as negotiated.//// The argument is only valid until the callback returns.Protocol func([]byte) bool// ProtocolCustrom allow user to parse Sec-WebSocket-Protocol header manually.// Note that returned bytes must be valid until Upgrade returns.// If ProtocolCustom is set, it used instead of Protocol function.ProtocolCustom func([]byte) (string, bool)// Extension is a select function that is used to select extensions// from list requested by client. If this field is set, then the all matched// extensions are sent to a client as negotiated.//// Note that Extension may be called multiple times and implementations// must track uniqueness of accepted extensions manually.//// The argument is only valid until the callback returns.//// According to the RFC6455 order of extensions passed by a client is// significant. That is, returning true from this function means that no// other extension with the same name should be checked because server// accepted the most preferable extension right now:// "Note that the order of extensions is significant. Any interactions between// multiple extensions MAY be defined in the documents defining the extensions.// In the absence of such definitions, the interpretation is that the header// fields listed by the client in its request represent a preference of the// header fields it wishes to use, with the first options listed being most// preferable."//// Deprecated: use Negotiate instead.Extension func(httphead.Option) bool// ExtensionCustom allow user to parse Sec-WebSocket-Extensions header// manually.//// If ExtensionCustom() decides to accept received extension, it must// append appropriate option to the given slice of httphead.Option.// It returns results of append() to the given slice and a flag that// reports whether given header value is wellformed or not.//// Note that ExtensionCustom may be called multiple times and// implementations must track uniqueness of accepted extensions manually.//// Note that returned options should be valid until Upgrade returns.// If ExtensionCustom is set, it used instead of Extension function.ExtensionCustom func([]byte, []httphead.Option) ([]httphead.Option, bool)// Negotiate is the callback that is used to negotiate extensions from// the client's offer. If this field is set, then the returned non-zero// extensions are sent to the client as accepted extensions in the// response.//// The argument is only valid until the Negotiate callback returns.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.Negotiate func(httphead.Option) (httphead.Option, error)// Header is an optional HandshakeHeader instance that could be used to// write additional headers to the handshake response.//// It used instead of any key-value mappings to avoid allocations in user// land.//// Note that if present, it will be written in any result of handshake.Header HandshakeHeader// OnRequest is a callback that will be called after request line// successful parsing.//// The arguments are only valid until the callback returns.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.OnRequest func(uri []byte) error// OnHost is a callback that will be called after "Host" header successful// parsing.//// It is separated from OnHeader callback because the Host header must be// present in each request since HTTP/1.1. Thus Host header is non-optional// and required for every WebSocket handshake.//// The arguments are only valid until the callback returns.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.OnHost func(host []byte) error// OnHeader is a 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.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.OnHeader func(key, value []byte) error// OnBeforeUpgrade is a callback that will be called before sending// successful upgrade response.//// Setting OnBeforeUpgrade allows user to make final application-level// checks and decide whether this connection is allowed to successfully// upgrade to WebSocket.//// It must return non-nil either HandshakeHeader or error and never both.//// If returned error is non-nil then connection is rejected and response is// sent with appropriate HTTP error code and body set to error message.//// RejectConnectionError could be used to get more control on response.OnBeforeUpgrade func() (header HandshakeHeader, err error)}// Upgrade zero-copy upgrades connection to WebSocket. It interprets given conn// as connection with incoming HTTP Upgrade request.//// It is a caller responsibility to manage i/o timeouts on conn.//// Non-nil error means that request for the WebSocket upgrade is invalid or// malformed and usually connection should be closed.// Even when error is non-nil Upgrade will write appropriate response into// connection in compliance with RFC.func ( Upgrader) ( io.ReadWriter) ( 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 |||||)// Prepare I/O buffers.// TODO(gobwas): make it configurable.:= pbufio.GetReader(,nonZero(.ReadBufferSize, DefaultServerReadBufferSize),):= pbufio.GetWriter(,nonZero(.WriteBufferSize, DefaultServerWriteBufferSize),)defer func() {pbufio.PutReader()pbufio.PutWriter()}()// Read HTTP request line like "GET /ws HTTP/1.1"., := readLine()if != nil {return ,}// Parse request line data like HTTP version, uri and method., := httpParseRequestLine()if != nil {return ,}// Prepare stack-based handshake header list.:= handshakeHeader{0: .Header,}// Parse and check HTTP request.// As RFC6455 says:// The client's opening handshake consists of the following parts. If the// server, while reading the handshake, finds that the client did not// send a handshake that matches the description below (note that as per// [RFC2616], the order of the header fields is not important), including// but not limited to any violations of the ABNF grammar specified for// the components of the handshake, the server MUST stop processing the// client's handshake and return an HTTP response with an appropriate// error code (such as 400 Bad Request).//// See https://tools.ietf.org/html/rfc6455#section-4.2.1// An HTTP/1.1 or higher GET request, including a "Request-URI".//// Even if RFC says "1.1 or higher" without mentioning the part of the// version, we apply it only to minor part.switch {case .major != 1 || .minor < 1:// Abort processing the whole request because we do not even know how// to actually parse it.= ErrHandshakeBadProtocolcase btsToString(.method) != http.MethodGet:= ErrHandshakeBadMethoddefault:if := .OnRequest; != nil {= (.uri)}}// Start headers read/parse loop.var (// headerSeen reports which header was seen by setting corresponding// bit on.byte= make([]byte, nonceSize))for == nil {, := readLine()if != nil {return ,}if len() == 0 {// Blank line, no more lines to read.break}, , := httpParseHeaderLine()if ! {= ErrMalformedRequestbreak}switch btsToString() {case headerHostCanonical:|=if := .OnHost; != nil {= ()}case headerUpgradeCanonical:|=if !bytes.Equal(, specHeaderValueUpgrade) && !bytes.EqualFold(, specHeaderValueUpgrade) {= ErrHandshakeBadUpgrade}case headerConnectionCanonical:|=if !bytes.Equal(, specHeaderValueConnection) && !btsHasToken(, specHeaderValueConnectionLower) {= ErrHandshakeBadConnection}case headerSecVersionCanonical:|=if !bytes.Equal(, specHeaderValueSecVersion) {= ErrHandshakeUpgradeRequired}case headerSecKeyCanonical:|=if len() != nonceSize {= ErrHandshakeBadSecKey} else {copy(, )}case headerSecProtocolCanonical:if , := .ProtocolCustom, .Protocol; .Protocol == "" && ( != nil || != nil) {var boolif != nil {.Protocol, = ()} else {.Protocol, = btsSelectProtocol(, )}if ! {= ErrMalformedRequest}}case headerSecExtensionsCanonical:if := .Negotiate; == nil && != nil {.Extensions, = negotiateExtensions(, .Extensions, )}// DEPRECATED path.if , := .ExtensionCustom, .Extension; .Negotiate == nil && ( != nil || != nil) {var boolif != nil {.Extensions, = (, .Extensions)} else {.Extensions, = btsSelectExtensions(, .Extensions, )}if ! {= ErrMalformedRequest}}default:if := .OnHeader; != nil {= (, )}}}switch {case == nil && != :switch {case & == 0:// As RFC2616 says:// A client MUST include a Host header field in all HTTP/1.1// request messages. If the requested URI does not include an// Internet host name for the service being requested, then the// Host header field MUST be given with an empty value. An// HTTP/1.1 proxy MUST ensure that any request message it// forwards does contain an appropriate Host header field that// identifies the service being requested by the proxy. All// Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad// Request) status code to any HTTP/1.1 request message which// lacks a Host header field.= ErrHandshakeBadHostcase & == 0:= ErrHandshakeBadUpgradecase & == 0:= ErrHandshakeBadConnectioncase & == 0:// In case of empty or not present version we do not send 426 status,// because it does not meet the ABNF rules of RFC6455://// version = DIGIT | (NZDIGIT DIGIT) |// ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)// ; Limited to 0-255 range, with no leading zeros//// That is, if version is really invalid – we sent 426 status as above, if it// not present – it is 400.= ErrHandshakeBadSecVersioncase & == 0:= ErrHandshakeBadSecKeydefault:panic("unknown headers state")}case == nil && .OnBeforeUpgrade != nil:[1], = .OnBeforeUpgrade()}if != nil {var intif , := .(*ConnectionRejectedError); {= .code[1] = .header}if == 0 {= http.StatusInternalServerError}httpWriteResponseError(, , , .WriteTo)// Do not store Flush() error to not override already existing one._ = .Flush()return ,}httpWriteResponseUpgrade(, , , .WriteTo)= .Flush()return ,}type handshakeHeader [2]HandshakeHeaderfunc ( handshakeHeader) ( io.Writer) ( int64, error) {for := 0; < len() && == nil; ++ {if := []; != nil {var int64, = .WriteTo()+=}}return ,}
![]() |
The pages are generated with Golds v0.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. |