websocket

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 26, 2019 License: BSD-3-Clause Imports: 25 Imported by: 4

Documentation

Overview

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() {
	srv, err := websocket.NewServer(9001)
	if err != nil {
		log.Println("websocket: " + err.Error())
		os.Exit(2)
	}

	srv.HandleAuth = handleAuth
	srv.HandleText = handleText

	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

Constants

View Source
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

View Source
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.

View Source
var (
	ErrRouteInvMethod = errors.New("invalid method")
	ErrRouteInvTarget = errors.New("invalid target")
	ErrRouteDupParam  = errors.New("duplicate parameter on route")
)

List of route error values.

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

Functions

func NewBroadcast added in v0.10.1

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 added in v0.5.0

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

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

func NewFrameBin added in v0.5.0

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

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

func NewFrameClose added in v0.5.0

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 added in v0.5.0

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

NewFramePing create a masked PING control frame.

func NewFramePong added in v0.5.0

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

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

func NewFrameText added in v0.5.0

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

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

func Recv

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

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

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

Types

type Client

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

	// 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
	// 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) Connect added in v0.5.0

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

Connect to endpoint.

func (*Client) Quit added in v0.5.0

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 added in v0.5.0

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) SendClose added in v0.5.0

func (cl *Client) SendClose(status CloseCode, payload []byte) (err error)

SendClose send the control CLOSE frame to server. If waitResponse is true, client will wait for CLOSE response from server before closing the connection.

func (*Client) SendPing added in v0.5.0

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

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

func (*Client) SendPong added in v0.5.0

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

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

func (*Client) SendText added in v0.5.0

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 added in v0.5.0

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.

type ClientManager added in v0.5.0

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 added in v0.5.0

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

All return a copy of all client connections.

func (*ClientManager) Conns added in v0.5.0

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 added in v0.5.0

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

Context return the client context.

type CloseCode added in v0.5.0

type CloseCode uint16

CloseCode represent the server close status.

type ContextKey

type ContextKey byte

ContextKey define a type for context.

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

List of valid context key.

type Frame

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

Frame represent a WebSocket data protocol.

func (*Frame) IsData added in v0.5.0

func (f *Frame) IsData() bool

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

func (*Frame) Opcode

func (f *Frame) Opcode() Opcode

Opcode return the frame operation code.

func (*Frame) Payload

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

Payload return the frame payload.

type Frames added in v0.5.0

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

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 added in v0.5.0

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 added in v0.5.0

func (frames *Frames) Opcode() Opcode

Opcode return the operation code of the first frame.

type HandlerAuthFn

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

HandlerAuthFn define server callback type to handle authentication request.

type HandlerClientFn

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

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

type HandlerFrameFn added in v0.5.0

type HandlerFrameFn func(conn int, frame *Frame)

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

type HandlerPayloadFn added in v0.5.0

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

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

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.

type Opcode added in v0.5.0

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

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

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

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

type Server struct {
	Clients *ClientManager

	// 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
	// contains filtered or unexported fields
}

Server for websocket.

func NewServer

func NewServer(port int) (serv *Server)

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

func (*Server) AllowReservedBits added in v0.5.0

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 added in v0.5.0

func (serv *Server) ClientRemove(conn int)

ClientRemove remove client connection from server.

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

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

Start accepting incoming connection from clients.

func (*Server) Stop added in v0.5.0

func (serv *Server) Stop()

Stop the server.

Directories

Path Synopsis
cmd/client
Program client provide an example of chat client that connect to WebSocket server.
Program client provide an example of chat client that connect to WebSocket server.
cmd/server
Program server provide an example of WebSocket server as group chat.
Program server provide an example of WebSocket server as group chat.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL