websocketx

package
v0.0.0-...-9392aba Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2024 License: MIT Imports: 11 Imported by: 2

Documentation

Overview

Package websocket provides a handler for websockets

Index

Examples

Constants

View Source
const (
	DefaultWriteInterval    = 10 * time.Second
	DefaultReadInterval     = time.Minute
	DefaultHandshakeTimeout = time.Second
	DefaultPingInterval     = (DefaultReadInterval * 9) / 10
	DefaultReadLimit        = 2048 // bytes
)

Defaults for Options

View Source
const MsgFailedCloseFrame = "error writing close frame"

Variables

View Source
var (
	// ErrConnectionShutdownWith indicates the connection closed because connection.ShutdownWith was called.
	ErrConnectionShutdownWith = errors.New("Connection.ShutdownWith called")

	// ErrConnectionClose indicates that the connection closed because connection.Close was called.
	ErrConnectionClose = errors.New("connection.Close called")
)

Functions

func RequireProtocols

func RequireProtocols(protocols ...string) func(*http.Request) error

RequireProtocols returns a function which enforces that at least one of the given protocols is used by the given request. It is intended to be used with [Server.Check].

Types

type CloseCause

type CloseCause struct {
	// CloseFrame is the close frame that cause the closure.
	// If no close frame was received, contains the [StatusAbnormalClosure] code.
	Frame CloseFrame

	// WasClean indicates if the connection is closed after receiving a close frame
	// from the client, or after having sent a close frame.
	//
	// NOTE: This roughly corresponds to the websocket JavaScript API's CloseEvent's wasClean.
	// However in situations where the server sends a close frame, but never receives a response
	// the WasClean field may still be true.
	// This detail is not considered part of the public API of this package, and may change in the future.
	WasClean bool

	// Err contains the error that caused the closure of this connection.
	// This may be an error returned from the read or write end of the connection,
	// or a server-side error.
	//
	// A special value contained in this field is ErrShuttingDown.
	Err error
}

CloseCause is returned by calling [close.Cause] on the context of a connection. It indicates the reason why the server was closed.

func (CloseCause) Error

func (cc CloseCause) Error() string

CloseCause implements the error interface.

func (CloseCause) Unwrap

func (cc CloseCause) Unwrap() error

Unwrap implements the underlying Unwrap interface.

type CloseFrame

type CloseFrame struct {
	Code   StatusCode
	Reason string
}

CloseFrame represents a closing control frame of a websocket connection. It is defined RFC 6455, section 5.5.1.

func (CloseFrame) Body

func (cf CloseFrame) Body() []byte

Message returns the message body used to send this frame to a remote endpoint.

func (CloseFrame) IsZero

func (cf CloseFrame) IsZero() bool

IsZero checks if this CloseFrame has the zero value

type Connection

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

Connection represents a connection to a single websocket client.

func (*Connection) Close

func (conn *Connection) Close() error

Close closes the connection to the peer without sending a specific close message. See [CloseWith] for providing the client with a reason for the closure.

func (*Connection) Context

func (conn *Connection) Context() context.Context

Context returns a context for operations on this context. Once it is closed, no more read or write operations are permitted.

The context.Cause will return an error of type CloseCause, representing the reason for the closure.

The Context may close before the Handler function has returned. To wait for the handler function to return, use Wait() instead.

func (*Connection) Read

func (conn *Connection) Read() <-chan Message

Read returns a channel that receives Text and Binary Messages from the peer. Once the websocket connection state is corrupted or closed, the channel is closed.

Multiple invocations of Read returns the same channel.

func (*Connection) Request

func (conn *Connection) Request() *http.Request

Request returns a clone of the original request used for upgrading the connection. It can be used to e.g. check for authentication.

Multiple calls to Request may return the same Request.

func (*Connection) Shutdown

func (conn *Connection) Shutdown()

Shutdown waits until the connection has been closed and the handler returns.

NOTE: This name exists to correspond to the graceful server shutdown.

func (*Connection) ShutdownWith

func (conn *Connection) ShutdownWith(frame CloseFrame)

ShutdownWith shuts down this connection with the given code and text for the client.

ShutdownWith automatically formats a close message, sends it, and waits for the close handshake to complete or timeout. The timeout used is the normal ReadInterval timeout.

When closeCode is 0, uses CloseNormalClosure.

func (*Connection) Subprotocol

func (conn *Connection) Subprotocol() string

Subprotocol returns the negotiated protocol for the connection. If no subprotocol has been negotiated, returns the empty string.

func (*Connection) Write

func (conn *Connection) Write(message Message) error

Write queues the provided message for sending and blocks until the given message has been sent.

Write returns a non-nil error if and only if the message failed to send. In such a case, Write will internally close the connection and return an error of type CancelCause.

Call Write concurrently with other read and write calls is safe. When multiple calls to Write are in-progress, all messages will be sent, but their order is undefined unless the callers explicitly coordinate.

func (*Connection) WriteBinary

func (conn *Connection) WriteBinary(source []byte) error

WriteBinary is like Write, but it sends a BinaryMessage with the given text.

func (*Connection) WritePrepared

func (conn *Connection) WritePrepared(message PreparedMessage) error

WritePrepared is like Write, but sends a PreparedMessage instead.

func (*Connection) WriteText

func (sh *Connection) WriteText(text string) error

WriteText is like Write, but is sends a TextMessage with the given text.

type ConnectionState

type ConnectionState int

ConnectionState is the state of the connection

const (
	CONNECTING ConnectionState = iota
	OPEN
	CLOSING
	CLOSED
)

Different connection states

type Handler

type Handler func(*Connection)

Handler handles a connection sent to the server. It should exit as soon as possible once the connection's context is closed. Handler may not retain a reference to its' argument past the function returning.

type Message

type Message struct {
	Type int
	Body []byte
}

Message represents a message sent between client and server.

func NewBinaryMessage

func NewBinaryMessage(data []byte) Message

NewBinaryMessage creates a new binary message with the given text

func NewTextMessage

func NewTextMessage(text string) Message

NewTextMessage creates a new text message with the given text

func (Message) Binary

func (msg Message) Binary() bool

Binary checks if this message is a binary message

func (Message) MustPrepare

func (msg Message) MustPrepare() PreparedMessage

MustPrepare is like Prepare, but panic()s when preparing fails.

func (Message) Prepare

func (msg Message) Prepare() (PreparedMessage, error)

Prepare prepares a message for sending

func (Message) Text

func (msg Message) Text() bool

Text checks if this message is a text message

type Options

type Options struct {
	// HandshakeTimeout specifies the duration for the open and close handshakes to complete.
	// If the handshakes fail within this time, the connection is closed immediately with an error.
	HandshakeTimeout time.Duration

	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
	// size is zero, then buffers allocated by the HTTP server are used. The
	// I/O buffer sizes do not limit the size of the messages that can be sent
	// or received.
	ReadBufferSize, WriteBufferSize int

	// ReadInterval is the maximum amount of time to wait for a new packet on
	// the underlying network connection.
	// After a read has timed out, the websocket connection state is corrupt and
	// the connection will be closed.
	//
	// To ensure that the client sends at least some packet within this time, use [PingInterval].
	ReadInterval time.Duration

	// WriteInterval is the write deadline on the underlying network
	// connection. After a write has timed out, the websocket state is corrupt and
	// the connection will be closed.
	WriteInterval time.Duration

	// WriteBufferPool is a pool of buffers for write operations. If the value
	// is not set, then write buffers are allocated to the connection for the
	// lifetime of the connection.
	//
	// A pool is most useful when the application has a modest volume of writes
	// across a large number of connections.
	//
	// Applications should use a single pool for each unique value of
	// WriteBufferSize.
	WriteBufferPool websocket.BufferPool

	// Subprotocols specifies the server's supported protocols in order of
	// preference. If this field is not nil, then the server negotiates a
	// subprotocol by selecting the first match in this list with a protocol
	// requested by the client. If there's no match, then no protocol is
	// negotiated (the Sec-Websocket-Protocol header is not included in the
	// handshake response).
	Subprotocols []string

	// PingInterval is the amount of time between successive Ping messages sent to be sent to the peer.
	// A ping message is intended to invoke a Poke response from the client, ensuring that the connection
	// has not been corrupted, and a package is received in at most [ReadInterval].
	//
	// For this purpose, PingInterval should be less than [ReadInterval].
	PingInterval time.Duration

	// ReadLimit is the maximum size in bytes for a message read from the peer. If a
	// message exceeds the limit, the connection sends a close message to the peer
	// and returns an error to the application.
	ReadLimit int64

	// CompressionLevel is the compression level of packages received to and from the peer.
	// See the [compress/flate] package for compression levels.
	//
	// A compression level of [flate.NoCompression] means that compression is disabled.
	// This is ignored if compression has not been negotiated with the client.
	CompressionLevel int
}

Options describes options for connections made to a Server. It is not guaranteed that options changed after the first call to [ServeHTTP] are accepted.

See [Options.SetDefault] for appropriate defaults.

func (Options) CompressionEnabled

func (opt Options) CompressionEnabled() bool

CompressionEnabled determines if the server should attempt to negotiate per message compression (RFC 7692). This method returning true does not guarantee that compression will be supported; only that the server will attempt to negotiate enabling it.

This is enabled when the CompressionLevel is not set to flate.NoCompression.

func (*Options) SetDefaults

func (opt *Options) SetDefaults()

SetDefaults sets defaults for options. See the appropriate default constants for this package.

type PreparedMessage

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

PreparedMessage represents a message that caches its' on-the-wire encoding. This saves re-applying compression.

type Server

type Server struct {

	// Handler is called for incoming client connections.
	// It must not be nil.
	Handler Handler

	// Fallback specifies the handler for generating HTTP responses if the client
	// did not request an upgrade to a websocket connection.
	// If Fallback is nil, sends an appropriate [http.StatusUpgradeRequired]
	Fallback http.Handler

	// Check check if additional client requirements are met before establishing
	// a websocket connection and returns a caller-exposed error if
	// this is not the case. If Check is nil, every potential client is
	// allowed to connect.
	//
	// A client that is rejected will receive a [http.StatusForbidden] response
	// with a body of the error returned by this function.
	//
	// A typical use case includes enforcing a specific subprotocol,
	// before even reaching the handler, see [RequireProtocols].
	//
	// Check is called before CheckOrigin.
	Check func(r *http.Request) error

	// CheckOrigin returns true if the request Origin header is acceptable. If
	// CheckOrigin is nil, then a safe default is used: return false if the
	// Origin request header is present and the origin host is not equal to
	// request Host header.
	//
	// A CheckOrigin function should carefully validate the request origin to
	// prevent cross-site request forgery.
	//
	// CheckOrigin is only called if the Check function passes.
	CheckOrigin func(r *http.Request) bool

	// Error specifies the function for generating HTTP error responses. If Error
	// is nil, then http.Error is used to generate the HTTP response.
	Error func(w http.ResponseWriter, r *http.Request, status int, reason error)

	// Options determine further options for future connections.
	Options Options
	// contains filtered or unexported fields
}

Server implements a websocket server.

Example (Echo)

A simple echo server for messages

package main

import (
	"fmt"

	"github.com/tkw1536/pkglib/websocketx"
	"github.com/tkw1536/pkglib/websocketx/websockettest"

	"github.com/gorilla/websocket"
)

func main() {
	// create a very simple websocket server that just echoes back messages
	var server websocketx.Server

	done := make(chan struct{})
	server.Handler = func(ws *websocketx.Connection) {
		// when finished, print that the handler exited
		// and close the done channel
		defer fmt.Println("Handler() returned")
		defer close(done)

		// read and write messages back forever
		for {
			select {
			case <-ws.Context().Done():
				return
			case msg := <-ws.Read():
				if err := ws.Write(msg); err != nil {
					panic(err)
				}
			}
		}
	}

	// The following code below is just for connection to the server.
	// It is just used to make sure that everything works.

	// create an actual server
	// create a new test server
	wss := websockettest.NewServer(&server)
	defer wss.Close()

	// create a new test client
	client, _ := wss.Dial(nil, nil)
	defer client.Close()

	// send a bunch of example messages
	messageCount := 1000

	// send it a lot of times
	for i := range messageCount {
		// generate an example message to send
		message := fmt.Sprintf("message %d", i)
		var kind int
		if i%2 == 0 {
			kind = websocket.BinaryMessage
		} else {
			kind = websocket.TextMessage
		}

		// write it or die
		if err := client.WriteMessage(kind, []byte(message)); err != nil {
			panic(err)
		}

		// read the message
		tp, p, err := client.ReadMessage()
		if err != nil {
			panic(err)
		}
		if tp != kind {
			panic("incorrect type received")
		}
		if string(p) != message {
			panic("incorrect answer received")
		}
	}

	client.Close()
	<-done
}
Output:

Handler() returned
Example (Panic)

Demonstrates how panic()ing handlers are handled handler

package main

import (
	"fmt"

	"github.com/tkw1536/pkglib/websocketx"
	"github.com/tkw1536/pkglib/websocketx/websockettest"

	"github.com/gorilla/websocket"
)

func main() {
	var server websocketx.Server

	server.Handler = func(ws *websocketx.Connection) {
		_ = ws.WriteText("normal message") // ignore error, we're panic()ing in the next line anyways
		panic("test panic")
	}

	// The following code below is just for connection to the server.
	// It is just used to make sure that everything works.

	// create an actual server
	wss := websockettest.NewServer(&server)
	defer wss.Close()

	// Connect to the server
	client, _, err := websocket.DefaultDialer.Dial(wss.URL, nil)
	if err != nil {
		panic(err)
	}
	defer client.Close()

	// print text messages
	for {
		tp, p, err := client.ReadMessage()
		if err != nil {
			return
		}

		// ignore non-text-messages
		if tp != websocket.TextMessage {
			continue
		}
		fmt.Println(string(p))
	}

}
Output:

normal message
Example (Prepared)
package main

import (
	"fmt"

	"github.com/tkw1536/pkglib/websocketx"
	"github.com/tkw1536/pkglib/websocketx/websockettest"

	"github.com/gorilla/websocket"
)

func main() {

	var server websocketx.Server

	// prepare a message to send
	msg := websocketx.NewTextMessage("i am prepared").MustPrepare()
	server.Handler = func(ws *websocketx.Connection) {
		if err := ws.WritePrepared(msg); err != nil {
			panic(err)
		}
	}

	// The following code below is just for connection to the server.
	// It is just used to make sure that everything works.

	// create an actual server
	wss := websockettest.NewServer(&server)
	defer wss.Close()

	client, _ := wss.Dial(nil, nil)
	defer client.Close()

	// print text messages
	for {
		tp, p, err := client.ReadMessage()
		if err != nil {
			return
		}

		// ignore non-text-messages
		if tp != websocket.TextMessage {
			continue
		}
		fmt.Println(string(p))
	}

}
Output:

i am prepared
Example (Send)

A simple server that sends data to the client.

package main

import (
	"fmt"

	"github.com/tkw1536/pkglib/websocketx"
	"github.com/tkw1536/pkglib/websocketx/websockettest"

	"github.com/gorilla/websocket"
)

func main() {
	var server websocketx.Server

	server.Handler = func(ws *websocketx.Connection) {
		if err := ws.WriteText("hello"); err != nil {
			panic(err)
		}
		if err := ws.WriteText("world"); err != nil {
			panic(err)
		}
	}

	// The following code below is just for connection to the server.
	// It is just used to make sure that everything works.

	// create a new test server
	wss := websockettest.NewServer(&server)
	defer wss.Close()

	// create a new test client
	client, _ := wss.Dial(nil, nil)
	defer client.Close()

	// print text messages
	for {
		tp, p, err := client.ReadMessage()
		if err != nil {
			return
		}

		// ignore non-text-messages
		if tp != websocket.TextMessage {
			continue
		}
		fmt.Println(string(p))
	}

}
Output:

hello
world

func (*Server) Close

func (server *Server) Close()

Close immediately closes the server by closing all client connections. Close does not wait for any existing connections to finish their shutdown.

To force shutdown, and then wait for any active handlers, use a call to Close followed by a call to Shutdown.

Use [Shutdown] or [ShutdownWith] instead.

func (*Server) RequireProtocols

func (server *Server) RequireProtocols()

RequireProtocols enforces that for any client to connect, at least one of the subprotocols has to be supported.

This overrides the server.Check function.

func (*Server) ServeHTTP

func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

This is typically a websocket upgrade request. If a non http-client

func (*Server) Shutdown

func (server *Server) Shutdown()

Shutdown gracefully shuts down the server.

Shutdown first informs the server to stop accepting new connection attempts. Then it waits (indefinitely) for all existing connections to stop.

See also [ShutdownWith] and [Close].

func (*Server) ShutdownWith

func (server *Server) ShutdownWith(frame CloseFrame)

ShutdownWith gracefully shuts down the server by sending each client the given CloseFrame. If frame is the zero value, uses a default frame indicating that the server is shutting down instead.

ShutdownWith first informs the server to stop accepting new connection attempts. Then it closes all existing connections by sending the given error. Finally it waits (indefinitely) for all existing connections to stop.

See also [Shutdown] and [Close].

type StatusCode

type StatusCode uint16

StatusCode is a status code as defined in RFC 6455, Section 7.4.

const (
	// Indicates a normal closure, meaning that the purpose for
	// which the connection was established has been fulfilled.
	StatusNormalClosure StatusCode = 1000

	// Indicates that an endpoint is "going away", such as a server
	// going down or a browser having navigated away from a page.
	StatusGoingAway StatusCode = 1001

	// indicates that an endpoint is terminating the connection due
	// to a protocol error.
	StatusProtocolError StatusCode = 1002

	// 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).
	StatusUnsupportedData StatusCode = 1003

	// A reserved value and MUST NOT be set as a status code in a
	// Close control frame by an endpoint.  It is designated for use in
	// applications expecting a status code to indicate that no status
	// code was actually present.
	StatusNoStatusReceived StatusCode = 1005

	// A reserved value and MUST NOT be set as a status code in a
	// Close control frame by an endpoint.  It is designated for use in
	// applications expecting a status code to indicate that the
	// connection was closed abnormally, e.g., without sending or
	// receiving a Close control frame.
	StatusAbnormalClosure StatusCode = 1006

	// 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).
	StatusInvalidFramePayloadData StatusCode = 1007

	// 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., [StatusUnsupportedData] or [StatusCloseMessageTooBig]) or if there
	// is a need to hide specific details about the policy.
	StatusPolicyViolation StatusCode = 1008

	// Indicates that an endpoint is terminating the connection
	// because it has received a message that is too big for it to
	// process.
	StatusMessageTooBig StatusCode = 1009

	// 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.
	StatusMandatoryExtension StatusCode = 1010

	// Indicates that a remote endpoint is terminating the connection
	// because it encountered an unexpected condition that prevented it from
	// fulfilling the request.
	StatusInternalErr StatusCode = 1011

	// Indicates that the service is restarted. A client may reconnect,
	// and if it choses to do, should reconnect using a randomized delay
	// of 5 - 30s.
	StatusServiceRestart StatusCode = 1012

	// Indicates that the service is experiencing overload. A client
	// should only connect to a different IP (when there are multiple for the
	// target) or reconnect to the same IP upon user action.
	StatusTryAgainLater StatusCode = 1013

	// Indicates that the server was acting as a gateway or proxy and
	// received an invalid response from the upstream server.
	StatusBadGateway StatusCode = 1014

	// Additional status codes registered in the IANA registry.
	StatusUnauthorized StatusCode = 3000
	StatusForbidden    StatusCode = 3003
	StatusTimeout      StatusCode = 3008
)

Defined Websocket status codes as per RFC 6455, Section 7.4.1. Primarily from rfc, see also iana.

func (StatusCode) Name

func (st StatusCode) Name() string

Name returns the name of this status code, if it is known. If the name is not known, returns the empty string.

func (StatusCode) String

func (st StatusCode) String() string

String turns this status code in human-readable form for display.

Directories

Path Synopsis
websockettest provides a server for testing.
websockettest provides a server for testing.

Jump to

Keyboard shortcuts

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