share: github.com/shuLhan/share/lib/websocket Index | Files | Directories

package websocket

import "github.com/shuLhan/share/lib/websocket"

Package websocket provide a library for creating WebSocket server or client.

The websocket server is implemented with epoll and kqueue, which means it's only run on Linux, Darwin, or BSD.

Pub-Sub Example

The following example show how create an authenticated WebSocket server that echo the data frame TEXT back to client.

import (
	...

	"github.com/shuLhan/share/lib/websocket"
)

var srv *websocket.Server

func handleAuth(req *Handshake) (ctx context.Context, err error) {
	URL, err := url.ParseRequestURI(string(req.URI))
	if err != nil {
		return nil, err
	}

	q := URL.Query()

	extJWT := q.Get("ticket")
	if len(extJWT) == 0 {
		return nil, fmt.Errorf("Missing authorization")
	}

	ctx = context.WithValue(context.Background(), CtxKeyExternalJWT, extJWT)
	ctx = context.WithValue(ctx, CtxKeyInternalJWT, _testInternalJWT)
	ctx = context.WithValue(ctx, CtxKeyUID, _testUID)

	return ctx, nil
}

func handleText(conn int, payload []byte) {
	packet := websocket.NewFrameText(false, payload)

	ctx := srv.Clients.Context(conn)

	// ... do something with connection context "ctx"

	err := websocket.Send(conn, packet)
	if err != nil {
		log.Println("handleText: " + err.Error())
	}
}

func main() {
	opts := &ServerOptions{
		Address: ":9001",
		HandleAuth: handleAuth,
		HandleText: handleText,
	}
	srv, err := websocket.NewServer(opts)
	if err != nil {
		log.Println("websocket: " + err.Error())
		os.Exit(2)
	}

	srv.Start()
}

References

- https://tools.ietf.org/html/rfc6455

- https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

- http://man7.org/linux/man-pages/man7/epoll.7.html

Index

Package Files

client.go clientmanager.go closecode.go contextkey.go doc.go frame.go frames.go funcs.go handler.go handshake.go opcode.go request.go response.go rootroute.go route.go server.go server_options.go targetparam.go websocket.go

Constants

const (
    // StatusNormal (1000) indicates a normal closure, meaning that the
    // purpose for which the connection was established has been
    // fulfilled.
    StatusNormal CloseCode = 1000

    // 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 = 1001

    // StatusBadRequest (1002) indicates that an endpoint is terminating
    // the connection due to a protocol error.
    StatusBadRequest = 1002

    // 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 = 1003

    // 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 = 1007

    // 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 = 1008

    // 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 = 1009

    // 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 = 1010

    // 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 = 1011
)

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.

Variables

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.

var (
    // ErrConnClosed define an error if client connection is not
    // connected.
    ErrConnClosed = fmt.Errorf("websocket: client is not connected")
)

func NewBroadcast Uses

func NewBroadcast(message, body string) (packet []byte, err error)

NewBroadcast create a new message for broadcast by server encoded as JSON and wrapped in TEXT frame.

func NewFrame Uses

func NewFrame(opcode Opcode, isMasked bool, payload []byte) []byte

NewFrame create a single finished frame with specific operation code and optional payload.

func NewFrameBin Uses

func NewFrameBin(isMasked bool, payload []byte) []byte

NewFrameBin create a single binary data frame with optional payload. Client frame must be masked.

func NewFrameClose Uses

func NewFrameClose(isMasked bool, code CloseCode, payload []byte) []byte

NewFrameClose create control CLOSE frame. The optional code represent the reason why the endpoint send the CLOSE frame, for closure. The optional payload represent the human readable reason, usually for debugging.

func NewFramePing Uses

func NewFramePing(isMasked bool, payload []byte) (packet []byte)

NewFramePing create a masked PING control frame.

func NewFramePong Uses

func NewFramePong(isMasked bool, payload []byte) (packet []byte)

NewFramePong create a masked PONG control frame to be used by client.

func NewFrameText Uses

func NewFrameText(isMasked bool, payload []byte) []byte

NewFrameText create a single text data frame with optional payload. Client frame must be masked.

func Recv Uses

func Recv(fd int) (packet []byte, err error)

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.

func Send Uses

func Send(fd int, packet []byte) (err error)

Send the packet through web socket file descriptor `fd`.

type Client Uses

type Client struct {
    sync.Mutex

    //
    // Endpoint contains URI of remote server.  The endpoint use the
    // following format,
    //
    //	ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
    //	wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
    //
    // The port component is OPTIONAL, default is 80 for "ws" scheme, and
    // 443 for "wss" scheme.
    //
    Endpoint string

    //
    // TLSConfig define custom TLS configuration when connecting to secure
    // WebSocket server.
    // The scheme of Endpoint must be "https" or "wss", or it will be
    // resetting back to nil.
    //
    TLSConfig *tls.Config

    // HandleBin callback that will be called after receiving data
    // frame binary from server.
    HandleBin ClientHandler

    // HandleQuit function that will be called when client connection is
    // closed.
    // Default is nil.
    HandleQuit func()

    // HandleRsvControl function that will be called when client received
    // reserved control frame (opcode 0xB-F) from server.
    // Default handler is nil.
    HandleRsvControl ClientHandler

    // HandleText callback that will be called after receiving data
    // frame text from server.
    HandleText ClientHandler

    // Headers The headers field can be used to pass custom headers during
    // handshake with server.  Any primary header fields ("host",
    // "upgrade", "connection", "sec-websocket-key",
    // "sec-websocket-version") will be deleted before handshake.
    Headers http.Header

    // The interval where PING control frame will be send to server.
    // The minimum and default value is 10 seconds.
    PingInterval time.Duration
    // contains filtered or unexported fields
}

Client for WebSocket protocol.

Unlike HTTP client or other most commmon TCP oriented client, the WebSocket client is actually asynchronous or passive-active instead of synchronous. At any time client connection is open to server, client can receive a message broadcast from server.

Case examples: if client send "A" to server, and expect that server response with "A+", server may send message "B" before sending "A+". Another case is when client connection is open, server may send "B" and "C" in any order without any request send by client previously.

Due to this model, the way to handle response from server is centralized using handlers instead of using single send request-response.

Client Example

The following snippet show how to create a client and handling response from request or broadcast from server,

cl := &Client{
	Endpoint: "ws://127.0.0.1:9001",
	HandleText: func(cl *Client, frame *Frame) error {
		// Process response from request or broadcast from
		// server.
		return nil
	}
}

err := cl.Connect()
if err != nil {
	log.Fatal(err.Error())
}

err := cl.SendText([]byte("Hello from client"))
if err != nil {
	log.Fatal(err.Error())
}

At any time, server may send PING or CLOSE the connection. For this messages, client already handled it by sending PONG message or by closing underlying connection automatically. Implementor can check closed connection from error returned from Send methods to match with ErrConnClosed.

func (*Client) Close Uses

func (cl *Client) Close() (err error)

Close gracefully close the client connection.

func (*Client) Connect Uses

func (cl *Client) Connect() (err error)

Connect to endpoint.

func (*Client) Quit Uses

func (cl *Client) Quit()

Quit force close the client connection without sending CLOSE control frame. This function MUST be used only when error receiving packet from server (e.g. lost connection) to release the resource.

func (*Client) SendBin Uses

func (cl *Client) SendBin(payload []byte) error

SendBin send data frame as binary to server. If handler is nil, no response will be read from server.

func (*Client) SendPing Uses

func (cl *Client) SendPing(payload []byte) error

SendPing send control PING frame to server, expecting PONG as response.

func (*Client) SendPong Uses

func (cl *Client) SendPong(payload []byte) error

SendPong send the control frame PONG to server, by using payload from PING frame.

func (*Client) SendText Uses

func (cl *Client) SendText(payload []byte) (err error)

SendText send data frame as text to server. If handler is nil, no response will be read from server.

type ClientHandler Uses

type ClientHandler func(cl *Client, frame *Frame) (err error)

ClientHandler define a callback type for client to handle packet from server (either broadcast or from response of request) in the form of frame.

Returning a non-nil error will cause the underlying connection to be closed.

type ClientManager Uses

type ClientManager struct {
    sync.Mutex
    // contains filtered or unexported fields
}

ClientManager manage list of active websocket connections on server.

This library assume that each connection belong to a user in the server, where each user is representated by uint64.

For a custom management of user use HandleClientAdd and HandleClientRemove on Server.

func (*ClientManager) All Uses

func (cls *ClientManager) All() (conns []int)

All return a copy of all client connections.

func (*ClientManager) Conns Uses

func (cls *ClientManager) Conns(uid uint64) (conns []int)

Conns return list of connections by user ID.

Each user may have more than one connection (e.g. from Android, iOS, or web). By knowing which connections that user have, implementor of websocket server can broadcast a message to all connections.

func (*ClientManager) Context Uses

func (cls *ClientManager) Context(conn int) (ctx context.Context)

Context return the client context.

type CloseCode Uses

type CloseCode uint16

CloseCode represent the server close status.

type ContextKey Uses

type ContextKey byte

ContextKey define a type for context.

const (
    CtxKeyExternalJWT ContextKey = 1 << iota
    CtxKeyInternalJWT
    CtxKeyUID
)

List of valid context key.

type Frame Uses

type Frame struct {
    // contains filtered or unexported fields
}

Frame represent a WebSocket data protocol.

func (*Frame) IsData Uses

func (f *Frame) IsData() bool

IsData return true if frame is either text or binary data frame.

func (*Frame) Opcode Uses

func (f *Frame) Opcode() Opcode

Opcode return the frame operation code.

func (*Frame) Payload Uses

func (f *Frame) Payload() []byte

Payload return the frame payload.

type Frames Uses

type Frames struct {
    // contains filtered or unexported fields
}

Frames represent continuous (fragmented) frame.

A fragmented message consists of a single frame with the FIN bit clear and an opcode other than 0, followed by zero or more frames with the FIN bit clear and the opcode set to 0, and terminated by a single frame with the FIN bit set and an opcode of 0.

func Unpack Uses

func Unpack(in []byte) (frames *Frames)

Unpack websocket data protocol from raw bytes to one or more frames.

When receiving packet from client, the underlying protocol or operating system may buffered the packet. Client may send a single frame one at time, but server may receive one or more frame in one packet; and vice versa. That's the reason why the Unpack return multiple frame instead of single frame.

On success it will return one or more frames. On fail it will return zero frame.

func (*Frames) Append Uses

func (frames *Frames) Append(f *Frame)

Append a frame as part of continuous frame. This function does not check if the appended frame is valid (i.e. zero operation code on second or later frame).

func (*Frames) Opcode Uses

func (frames *Frames) Opcode() Opcode

Opcode return the operation code of the first frame.

type HandlerAuthFn Uses

type HandlerAuthFn func(req *Handshake) (ctx context.Context, err error)

HandlerAuthFn define server callback type to handle authentication request.

type HandlerClientFn Uses

type HandlerClientFn func(ctx context.Context, conn int)

HandlerClientFn define server callback type to handle new client connection or removed client connection.

type HandlerFrameFn Uses

type HandlerFrameFn func(conn int, frame *Frame)

HandlerFrameFn define a server callback type to handle client request with single frame.

type HandlerPayloadFn Uses

type HandlerPayloadFn func(conn int, payload []byte)

HandlerPayloadFn define server callback type to handle data frame from client.

type HandlerStatusFn Uses

type HandlerStatusFn func() (contentType string, data []byte)

HandlerStatusFn define server callback type to handle status request. It must return the content type of data, for example "text/plain", and the status data to be send to client.

type Handshake Uses

type Handshake struct {
    URL        *url.URL
    Host       []byte
    Key        []byte
    Extensions []byte
    Protocol   []byte
    Header     http.Header
    // contains filtered or unexported fields
}

Handshake contains the websocket HTTP handshake request.

type Opcode Uses

type Opcode byte

Opcode represent the websocket operation code.

const (
    OpcodeCont        Opcode = 0x0
    OpcodeText        Opcode = 0x1
    OpcodeBin         Opcode = 0x2
    OpcodeDataRsv3    Opcode = 0x3 // %x3-7 are reserved for further non-control frames
    OpcodeDataRsv4    Opcode = 0x4
    OpcodeDataRsv5    Opcode = 0x5
    OpcodeDataRsv6    Opcode = 0x6
    OpcodeDataRsv7    Opcode = 0x7
    OpcodeClose       Opcode = 0x8
    OpcodePing        Opcode = 0x9
    OpcodePong        Opcode = 0xA
    OpcodeControlRsvB Opcode = 0xB // %xB-F are reserved for further control frames
    OpcodeControlRsvC Opcode = 0xC
    OpcodeControlRsvD Opcode = 0xD
    OpcodeControlRsvE Opcode = 0xE
    OpcodeControlRsvF Opcode = 0xF
)

List of valid operation code in frame.

type Request Uses

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:"-"`

    // Conn is the client connection, where the request come from.
    Conn int
}

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 Uses

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 Uses

type RouteHandler func(ctx context.Context, req *Request) (res Response)

RouteHandler is a function that will be called when registered method and target match with request.

type Server Uses

type Server struct {
    Clients *ClientManager
    // contains filtered or unexported fields
}

Server for websocket. //

func NewServer Uses

func NewServer(opts *ServerOptions) (serv *Server)

NewServer will create new web-socket server that listen on specific port number.

func (*Server) AllowReservedBits Uses

func (serv *Server) AllowReservedBits(one, two, three bool)

AllowReservedBits allow receiving frame with RSV1, RSV2, or RSV3 bit set. Calling this function means server has negotiated the extension that use the reserved bits through handshake with client using HandleAuth.

If a nonzero value is received in reserved bits and none of the negotiated extensions defines the meaning of such a nonzero value, server will close the connection (RFC 6455, section 5.2).

func (*Server) ClientRemove Uses

func (serv *Server) ClientRemove(conn int)

ClientRemove remove client connection from server.

func (*Server) RegisterTextHandler Uses

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) Start Uses

func (serv *Server) Start() (err error)

Start accepting incoming connection from clients.

func (*Server) Stop Uses

func (serv *Server) Stop()

Stop the server.

type ServerOptions Uses

type ServerOptions struct {
    // Address to listen for WebSocket connection.
    // Default to ":80".
    Address string

    // ConnectPath define the HTTP path where WebSocket connection
    // handshake will be processed. Default to "/".
    ConnectPath string

    // StatusPath define a HTTP path to check for server status.
    // Default to ConnectPath +"/status" if its empty.
    // The StatusPath is handled by HandleStatus callback in the server.
    StatusPath string

    // HandleAuth callback that will be called when receiving
    // client handshake.
    HandleAuth HandlerAuthFn

    // HandleClientAdd callback that will called after client handshake
    // and, if HandleAuth is defined, after client is authenticated.
    HandleClientAdd HandlerClientFn

    // HandleClientRemove callback that will be called before client
    // connection being removed and closed by server.
    HandleClientRemove HandlerClientFn

    // HandleRsvControl callback that will be called when server received
    // reserved control frame (opcode 0xB-F) from client.
    // Default handle is nil.
    HandleRsvControl HandlerFrameFn

    // HandleText callback that will be called after receiving data
    // frame(s) text from client.
    // Default handle parse the payload into Request and pass it to
    // registered routes.
    HandleText HandlerPayloadFn

    // HandleBin callback that will be called after receiving data
    // frame(s) binary from client.
    HandleBin HandlerPayloadFn

    // HandleStatus function that will be called when server receive
    // request for status as defined in ServerOptions.StatusPath.
    HandleStatus HandlerStatusFn
}

Directories

PathSynopsis
examples

Package websocket imports 27 packages (graph) and is imported by 2 packages. Updated 2020-11-28. Refresh now. Tools for package owners.