Documentation ¶
Overview ¶
Package websocket provide the websocket library for server and client. [1][2]
The websocket server is implemented with epoll [3], which means it's only run on Linux.
Constraints ¶
Routing is handled by proxy layer (e.g. HAProxy).
References ¶
[1] https://tools.ietf.org/html/rfc6455
[2] https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
Index ¶
- Constants
- Variables
- func GenerateHandshakeAccept(key []byte) string
- func GenerateHandshakeKey() (key []byte)
- func GetConnectAddr(u *url.URL) (addr string)
- func Recv(fd int) (packet []byte, err error)
- func Send(fd int, packet []byte) (err error)
- func SendFrame(fd int, f *Frame, randomMask bool) (err error)
- type Client
- func (cl *Client) Handshake(path, origin, proto, ext string, headers http.Header) (err error)
- func (cl *Client) Open(addr string) (err error)
- func (cl *Client) ParseURI(endpoint string) (serverAddr string, err error)
- func (cl *Client) Reconnect() (err error)
- func (cl *Client) Recv() (packet []byte, err error)
- func (cl *Client) Send(ctx context.Context, req []byte, handler ClientRecvHandler) (err error)
- type ClientRecvHandler
- type ConnState
- type ContextKey
- type Frame
- type HandlerAuthFn
- type HandlerClientFn
- type HandlerFn
- type Handshake
- type Request
- type Response
- type RouteHandler
- type Server
- type UserSockets
Constants ¶
const ( ConnStateClosed ConnState = 0 ConnStateOpen = 1 << iota ConnStateConnected ConnStateError )
List of socket connection status.
const ( OpCodeCont = 0x0 OpCodeText = 0x1 OpCodeBin = 0x2 OpCodeClose = 0x8 OpCodePing = 0x9 OpCodePong = 0xA )
List of valid operation code in frame.
const ( FrameSmallPayload = 125 FrameMediumPayload = 126 FrameLargePayload = 127 )
List of frame length.
const ( FrameIsFinished = 0x80 FrameIsMasked = 0x80 )
List of frame FIN and MASK values.
Variables ¶
var ( // StatusNormal (1000) indicates a normal closure, meaning that the // purpose for which the connection was established has been // fulfilled. StatusNormal = []byte{0x03, 0xE8} //nolint: gochecknoglobals // StatusGone (1001) indicates that an endpoint is "going away", such // as a server going down or a browser having navigated away from a // page. StatusGone = []byte{0x03, 0xE9} //nolint: gochecknoglobals // StatusBadRequest (1002) indicates that an endpoint is terminating // the connection due to a protocol error. StatusBadRequest = []byte{0x03, 0xEA} //nolint: gochecknoglobals // StatusUnsupportedType (1003) indicates that an endpoint is // terminating the connection because it has received a type of data // it cannot accept (e.g., an endpoint that understands only text data // MAY send this if it receives a binary message). StatusUnsupportedType = []byte{0x03, 0xEB} //nolint: gochecknoglobals // StatusInvalidData (1007) indicates that an endpoint is terminating // the connection because it has received data within a message that // was not consistent with the type of the message (e.g., non-UTF-8 // [RFC3629] data within a text message). StatusInvalidData = []byte{0x03, 0xEF} //nolint: gochecknoglobals // StatusForbidden (1008) indicates that an endpoint is terminating // the connection because it has received a message that violates its // policy. This is a generic status code that can be returned when // there is no other more suitable status code (e.g., 1003 or 1009) or // if there is a need to hide specific details about the policy. StatusForbidden = []byte{0x03, 0xF0} //nolint: gochecknoglobals // StatusRequestEntityTooLarge (1009) indicates that an endpoint is // terminating the connection because it has received a message that // is too big for it to process. StatusRequestEntityTooLarge = []byte{0x03, 0xF1} //nolint: gochecknoglobals // StatusBadGateway (1010) indicates that an endpoint (client) is // terminating the connection because it has expected the server to // negotiate one or more extension, but the server didn't return them // in the response message of the WebSocket handshake. The list of // extensions that are needed SHOULD appear in the /reason/ part of // the Close frame. Note that this status code is not used by the // server, because it can fail the WebSocket handshake instead. StatusBadGateway = []byte{0x03, 0xF2} //nolint: gochecknoglobals // StatusInternalError or 1011 indicates that a server is terminating // the connection because it encountered an unexpected condition that // prevented it from fulfilling the request. StatusInternalError = []byte{0x03, 0xF3} //nolint: gochecknoglobals )
List of close code in network byte order. The name of status is mimicking the "net/http" status code.
Endpoints MAY use the following pre-defined status codes when sending a Close frame.
Status code 1004-1006, and 1015 is reserved and MUST NOT be used on Close payload.
See RFC6455 7.4.1-P45 for more information.
var ( ControlFrameClose = []byte{FrameIsFinished | OpCodeClose, 0x00} //nolint: gochecknoglobals ControlFrameCloseWithCode = []byte{FrameIsFinished | OpCodeClose, 0x02} //nolint: gochecknoglobals ControlFramePing = []byte{FrameIsFinished | OpCodePing, 0x00} //nolint: gochecknoglobals ControlFramePong = []byte{FrameIsFinished | OpCodePong, 0x00} //nolint: gochecknoglobals )
List of unmasked control frames, MUST used only by server.
var ( ErrBadRequest = errors.New("bad request") ErrRequestLength = errors.New("bad request: length is less than minimum") ErrRequestHeaderLength = errors.New("bad request: header length is less than minimum") ErrInvalidHTTPMethod = errors.New("invalid HTTP method") ErrInvalidHTTPVersion = errors.New("invalid HTTP version") ErrInvalidHeaderUpgrade = errors.New("invalid Upgrade header") ErrInvalidHeaderFormat = errors.New("invalid Header format") ErrInvalidHeaderHost = errors.New("invalid Host header") ErrInvalidHeaderWSKey = errors.New("invalid Sec-Websocket-Key header") ErrInvalidHeaderWSVersion = errors.New("invalid Sec-Websocket-Version header") ErrInvalidHeaderWSExtensions = errors.New("invalid Sec-Websocket-Extensions header") ErrInvalidHeaderWSProtocol = errors.New("invalid Sec-Websocket-Protocol header") ErrInvalidHeaderConn = errors.New("invalid Connection header") ErrMissingRequiredHeader = errors.New("missing required headers") ErrUnsupportedWSVersion = errors.New("unsupported Sec-WebSocket-Version") )
List of errors.
var ( ErrRouteInvMethod = errors.New("invalid method") ErrRouteInvTarget = errors.New("invalid target") ErrRouteDupParam = errors.New("duplicate parameter on route") )
List of route error values.
Functions ¶
func GenerateHandshakeAccept ¶
GenerateHandshakeAccept generate server accept key by concatenating key, defined in step 4 in Section 4.2.2, with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this concatenated value to obtain a 20-byte value and base64-encoding (see Section 4 of [RFC4648]) this 20-byte hash.
func GenerateHandshakeKey ¶
func GenerateHandshakeKey() (key []byte)
GenerateHandshakeKey randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]).
func GetConnectAddr ¶
GetConnectAddr return "host:port" from value in URL. By default, if no port is given, it will set to 80.
func Recv ¶
Recv read all content from file descriptor into slice of bytes.
On success it will return buffer from pool. Caller must put the buffer back to the pool.
On fail it will return nil buffer and error.
Types ¶
type Client ¶
type Client struct { State ConnState URL *url.URL IsTLS bool // contains filtered or unexported fields }
Client for websocket.
func NewClient ¶
NewClient create a new client connection to websocket server with a handshake.
The endpoint use the following format,
WebSocket URIs
This specification defines two URI schemes, using the ABNF syntax defined in RFC 5234 [RFC5234], and terminology and ABNF productions defined by the URI specification RFC 3986 [RFC3986].
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
...
The port component is OPTIONAL; the default for "ws" is port 80, while the default for "wss" is port 443.
func (*Client) Handshake ¶
Handshake send the websocket opening handshake.
RFC6455 P17-P19 1. The handshake MUST be a valid HTTP request as specified by [RFC2616]. 2. The method of the request MUST be GET, and the HTTP version MUST be at least 1.1. For example, if the WebSocket URI is "ws://example.com/chat", the first line sent should be "GET /chat HTTP/1.1". 3. The "Request-URI" part of the request MUST match the /resource name/ defined in Section 3 (a relative URI) or be an absolute http/https URI that, when parsed, has a /resource name/, /host/, and /port/ that match the corresponding ws/wss URI. 4. The request MUST contain a |Host| header field whose value contains /host/ plus optionally ":" followed by /port/ (when not using the default port). 5. The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword. 6. The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token. 7. The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection. NOTE: As an example, if the randomly selected value was the sequence of bytes 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10, the value of the header field would be "AQIDBAUGBwgJCgsMDQ4PEC==" 8. The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client. If the connection is from a non-browser client, the request MAY include this header field if the semantics of that client match the use-case described here for browser clients. The value of this header field is the ASCII serialization of origin of the context in which the code establishing the connection is running. See [RFC6454] for the details of how this header field value is constructed. As an example, if code downloaded from www.example.com attempts to establish a connection to ww2.example.com, the value of the header field would be "http://www.example.com". 9. The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13. NOTE: Although draft versions of this document (-09, -10, -11, and -12) were posted (they were mostly comprised of editorial changes and clarifications and not changes to the wire protocol), values 9, 10, 11, and 12 were not used as valid values for Sec-WebSocket-Version. These values were reserved in the IANA registry but were not and will not be used. 10. The request MAY include a header field with the name |Sec-WebSocket-Protocol|. If present, this value indicates one or more comma-separated subprotocol the client wishes to speak, ordered by preference. The elements that comprise this value MUST be non-empty strings with characters in the range U+0021 to U+007E not including separator characters as defined in [RFC2616] and MUST all be unique strings. The ABNF for the value of this header field is 1#token, where the definitions of constructs and rules are as given in [RFC2616]. 11. The request MAY include a header field with the name |Sec-WebSocket-Extensions|. If present, this value indicates the protocol-level extension(s) the client wishes to speak. The interpretation and format of this header field is described in Section 9.1. 12. The request MAY include any other header fields, for example, cookies [RFC6265] and/or authentication-related header fields such as the |Authorization| header field [RFC2616], which are processed according to documents that define them.
func (*Client) Open ¶
Open TCP connection to websocket server address in "host:port" format. If client "IsTLS" field is true, the connection is opened with TLS protocol and the remote name MUST have a valid certificate.
func (*Client) ParseURI ¶
ParseURI of websocket connection scheme from "endpoint" and set client URL and TLS status to true if scheme is "wss://".
On success it will set and return server address that can be used on Open().
On fail it will return empty server address and error.
type ClientRecvHandler ¶
ClientRecvHandler define a custom callback type for handling response from request.
type ContextKey ¶
type ContextKey uint64
ContextKey define a type for context.
const ( CtxKeyExternalJWT ContextKey = 1 << iota CtxKeyInternalJWT CtxKeyUID )
List of valid context key.
type Frame ¶
type Frame struct { Fin byte Opcode byte Masked byte Payload []byte // contains filtered or unexported fields }
Frame represent a websocket data protocol.
5.2 Base Framing Protocol 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ Mask: 1 bit Defines whether the "Payload data" is masked. If set to 1, a masking key is present in masking-key, and this is used to unmask the "Payload data" as per Section 5.3. All frames sent from client to server have this bit set to 1. Payload length: 7 bits, 7+16 bits, or 7+64 bits The length of the "Payload data", in bytes: if 0-125, that is the payload length. If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length. Multibyte length quantities are expressed in network byte order. Note that in all cases, the minimal number of bytes MUST be used to encode the length, for example, the length of a 124-byte-long string can't be encoded as the sequence 126, 0, 124. The payload length is the length of the "Extension data" + the length of the "Application data". The length of the "Extension data" may be zero, in which case the payload length is the length of the "Application data". Masking-key: 0 or 4 bytes All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame. This field is present if the mask bit is set to 1 and is absent if the mask bit is set to 0. See Section 5.3 for further information on client- to-server masking. Payload data: (x+y) bytes The "Payload data" is defined as "Extension data" concatenated with "Application data". Extension data: x bytes The "Extension data" is 0 bytes unless an extension has been negotiated. Any extension MUST specify the length of the "Extension data", or how that length may be calculated, and how the extension use MUST be negotiated during the opening handshake. If present, the "Extension data" is included in the total payload length. Application data: y bytes Arbitrary "Application data", taking up the remainder of the frame after any "Extension data". The length of the "Application data" is equal to the payload length minus the length of the "Extension data".
func Unpack ¶
Unpack websocket data protocol from raw bytes to one or more frames.
On success it will return one or more frames. On fail it will return zero frame.
func (*Frame) Pack ¶
Pack websocket Frame into packet that can be sent through network.
Caller must set frame fields Fin, Opcode, Masked, and Payload.
Frame payload len will be set based on length of payload.
Frame maskKey will be set randomly only if Masked is set and randomMask parameter is true.
RFC6455 5.1-P27 A server MUST NOT mask any frames that it sends to the client.
type HandlerAuthFn ¶
HandlerAuthFn callback type to handle authentication request.
type HandlerClientFn ¶
HandlerClientFn callback type to handle client request.
type Handshake ¶
type Handshake struct { URI []byte Host []byte Key []byte Extensions []byte Protocol []byte Header http.Header // contains filtered or unexported fields }
Handshake contains the websocket HTTP handshake request.
func (*Handshake) Parse ¶
Parse HTTP request.
RFC6455 4.1-P17-19 1. The handshake MUST be a valid HTTP request as specified by [RFC2616]. 2. The method of the request MUST be GET, and the HTTP version MUST be at least 1.1. For example, if the WebSocket URI is "ws://example.com/chat", the first line sent should be "GET /chat HTTP/1.1". 3. The "Request-URI" part of the request MUST match the /resource name/ defined in Section 3 (a relative URI) or be an absolute http/https URI that, when parsed, has a /resource name/, /host/, and /port/ that match the corresponding ws/wss URI. 4. The request MUST contain a |Host| header field whose value contains /host/ plus optionally ":" followed by /port/ (when not using the default port). 5. The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword. 6. The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token. 7. The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection. NOTE: As an example, if the randomly selected value was the sequence of bytes 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10, the value of the header field would be "AQIDBAUGBwgJCgsMDQ4PEC==" ... The |Sec-WebSocket-Key| header field MUST NOT appear more than once in an HTTP request. 8. The request MUST include a header field with the name |Origin| [RFC6454] if the request is coming from a browser client. If the connection is from a non-browser client, the request MAY include this header field if the semantics of that client match the use-case described here for browser clients. The value of this header field is the ASCII serialization of origin of the context in which the code establishing the connection is running. See [RFC6454] for the details of how this header field value is constructed. As an example, if code downloaded from www.example.com attempts to establish a connection to ww2.example.com, the value of the header field would be "http://www.example.com". 9. The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13. NOTE: Although draft versions of this document (-09, -10, -11, and -12) were posted (they were mostly comprised of editorial changes and clarifications and not changes to the wire protocol), values 9, 10, 11, and 12 were not used as valid values for Sec-WebSocket-Version. These values were reserved in the IANA registry but were not and will not be used. 10. The request MAY include a header field with the name |Sec-WebSocket-Protocol|. If present, this value indicates one or more comma-separated subprotocol the client wishes to speak, ordered by preference. The elements that comprise this value MUST be non-empty strings with characters in the range U+0021 to U+007E not including separator characters as defined in [RFC2616] and MUST all be unique strings. The ABNF for the value of this header field is 1#token, where the definitions of constructs and rules are as given in [RFC2616]. 11. The request MAY include a header field with the name |Sec-WebSocket-Extensions|. If present, this value indicates the protocol-level extension(s) the client wishes to speak. The interpretation and format of this header field is described in Section 9.1. 12. The request MAY include any other header fields, for example, cookies [RFC6265] and/or authentication-related header fields such as the |Authorization| header field [RFC2616], which are processed according to documents that define them.
Based on above requirements, the minimum handshake header is,
GET / HTTP/1.1\r\n (16 bytes) Host: a.com\r\n (13 bytes) Upgrade: websocket\r\n (20 bytes) Connection: Upgrade\r\n (21 bytes) Sec-Websocket-Key: (24 chars)\r\n (45 bytes) Sec-Websocket-Version: 13\r\n (27 bytes) \r\n (2 bytes)
If we count all characters, the minimum bytes would be: 144 bytes. Of course one can send request with long URI "/chat?query=<512 chars>" without headers and the length will be greater than 144 bytes.
The minimum length of request without HTTP line is: 144 - 16 = 128 bytes.
type Request ¶
type Request struct { // // Id is unique between request to differentiate multiple request // since each request is asynchronous. Client can use incremental // value or, the recommended way, using Unix timestamp with // millisecond. // ID uint64 `json:"id"` // Method is equal to HTTP method. Method string `json:"method"` // Target is equal to HTTP request RequestURI, e.g. "/path?query". Target string `json:"target"` // Body is equal to HTTP body on POST/PUT. Body string `json:"body"` // Path is Target without query. Path string `json:"-"` // Params are parameters as key-value in Target path that has been // parsed. Params targetParam `json:"-"` // Query is Target query. Query url.Values `json:"-"` }
Request define text payload format for client requesting resource on server.
Example of request format,
{ "id": 1512459721269, "method": "GET", "target": "/v1/auth/login", "body": "{ \"token\": \"xxx.yyy.zzz\" }" }
type Response ¶
type Response struct { ID uint64 `json:"id"` Code int32 `json:"code"` Message string `json:"message"` Body string `json:"body"` }
Response contains the data that server send to client as a reply from Request or as broadcast from client subscription.
If response type is a reply from Request, the ID from Request will be copied to the Response, and `code` and `message` field values are equal with HTTP response code and message.
Example of response format for replying request,
{ id: 1512459721269, code: 200, message: "", body: "" }
If response type is broadcast the ID and code MUST be 0, and the `message` field will contain the name of subscription. For example, when recipient of message read the message, server will publish a notification response as,
{ id: 0, code: 0, message: "message.read", body: "{ \"id\": ... }" }
type RouteHandler ¶
RouteHandler is a function that will be called when registered method and target match with request.
type Server ¶
type Server struct { HandleText HandlerFn HandleBin HandlerFn HandleClose HandlerFn HandlePing HandlerFn HandleAuth HandlerAuthFn HandleClientAdd HandlerClientFn HandleClientRemove HandlerClientFn // contains filtered or unexported fields }
Server for websocket.
func (*Server) RegisterTextHandler ¶
func (serv *Server) RegisterTextHandler(method, target string, handler RouteHandler) (err error)
RegisterTextHandler register specific function to be called by server when request opcode is text, and method and target matched with Request.
func (*Server) SendResponse ¶
SendResponse to client.
type UserSockets ¶
UserSockets define a mapping between user ID (uint64) and list of their connections ([]int)
Each user may have more than one connection (e.g. from Android, iOS, web, etc). By knowing which connections that user have, implementor of websocket server can broadcast a message.
func (*UserSockets) Add ¶
func (us *UserSockets) Add(uid uint64, conn int)
Add new socket connection to user ID only if the socket is not already exist.
func (*UserSockets) Remove ¶
func (us *UserSockets) Remove(uid uint64, conn int)
Remove socket from list of user's connection.