Documentation
¶
Index ¶
- Constants
- func IsReservedEvent(eventName string) bool
- type AnonymousChannel
- func (ch *AnonymousChannel) Abort(err error) error
- func (ch *AnonymousChannel) Close() error
- func (ch *AnonymousChannel) Context() context.Context
- func (ch *AnonymousChannel) Emit(ctx context.Context, arguments ...any) error
- func (ch *AnonymousChannel) On(eventName string, handler any) *AnonymousChannel
- func (ch *AnonymousChannel) Once(eventName string, handler any) *AnonymousChannel
- func (ch *AnonymousChannel) Request(ctx context.Context, arguments ...any) (any, error)
- type ChannelClosedError
- type Client
- type ClientChannel
- func (ch *ClientChannel) Close() error
- func (ch ClientChannel) Emit(ctx context.Context, arguments ...any) error
- func (ch ClientChannel) Name() string
- func (ch ClientChannel) On(eventName string, handler any) ClientChannel
- func (ch ClientChannel) Once(eventName string, handler any) ClientChannel
- func (ch ClientChannel) Request(ctx context.Context, arguments ...any) (response any, err error)
- type ClientError
- type CloseHandler
- type CloseHandlerOld
- type Conn
- type ErrorHandler
- type HandlerContextFunc
- type Message
- type MessageHandler
- type OpenHandler
- type Server
- type ServerChannel
- func (c *ServerChannel) Close() error
- func (c ServerChannel) Emit(ctx context.Context, arguments ...any) (errs []ClientError)
- func (c ServerChannel) Name() string
- func (c ServerChannel) On(eventName string, handler any) ServerChannel
- func (c ServerChannel) Once(eventName string, handler any) ServerChannel
- type StatusCode
Examples ¶
Constants ¶
const ( EventOpen = "open" EventConnect = "connect" EventError = "error" EventMessage = "message" EventClose = "close" EventDisconnect = "disconnect" )
List of reserved event names. It is an error to send or receive events on the main channel with these event names.
Variables ¶
This section is empty.
Functions ¶
func IsReservedEvent ¶
IsReservedEvent checks if the event name is a reserved event name
Types ¶
type AnonymousChannel ¶ added in v1.7.0
type AnonymousChannel struct {
// contains filtered or unexported fields
}
AnonymousChannel is a request-scoped channel created when a handler returns *AnonymousChannel in response to a request. It allows streaming or multi-message patterns over a single WebSocket connection.
Emit and Request are only available once the channel has been delivered to the requestor (i.e. after the handler returns it). Before that point, both methods return errNotReady. On and Once may be called freely at any time, including inside the handler before the channel is returned.
func Channel ¶ added in v1.7.0
func Channel(ctx context.Context) *AnonymousChannel
Channel returns the *AnonymousChannel for the current request handler context, creating it lazily on the first call. It returns nil if called outside of a request handler.
Example ¶
ExampleChannel shows how to stream events from a server handler back to the requesting client using an anonymous channel.
The handler calls Channel to obtain an *AnonymousChannel tied to the current request, launches a goroutine that emits a series of "data" events followed by an "end" event, and returns the channel so the library can send the creation response to the client.
On the client side the [Client.Request] return value is a *AnonymousChannel when the server responds with a channel. Register handlers on it before any events can arrive:
resp, err := client.Request(ctx, "subscribe", "news")
if err == nil {
if ch, ok := resp.(*wrapper.AnonymousChannel); ok {
ch.On("data", func(update string) error {
fmt.Println("update:", update)
return nil
})
}
}
package main
import (
"context"
"fmt"
wrapper "github.com/bminer/ws-server-wrapper-go"
)
func main() {
wsServer := wrapper.NewServer()
updates := []string{"first", "second", "third"}
wsServer.On("subscribe", func(ctx context.Context, topic string) (*wrapper.AnonymousChannel, error) {
ch := wrapper.Channel(ctx) // obtain the anonymous channel for this request
go func() {
defer ch.Close()
for _, u := range updates {
msg := fmt.Sprintf("[%s] %s", topic, u)
if err := ch.Emit(ch.Context(), "data", msg); err != nil {
return // client disconnected or aborted
}
}
ch.Emit(ch.Context(), "end") //nolint:errcheck
}()
return ch, nil
})
_ = wsServer
}
Output:
func (*AnonymousChannel) Abort ¶ added in v1.7.0
func (ch *AnonymousChannel) Abort(err error) error
Abort sends an abort message to the remote end with the provided reason and closes this anonymous channel. If err is nil, it defaults to context.Canceled.
func (*AnonymousChannel) Close ¶ added in v1.7.0
func (ch *AnonymousChannel) Close() error
Close removes all event handlers for this anonymous channel and cancels its context. It does NOT send an abort message to the remote end; use Abort for that. Close is idempotent and safe to call multiple times.
func (*AnonymousChannel) Context ¶ added in v1.7.0
func (ch *AnonymousChannel) Context() context.Context
Context returns a context that is cancelled when this anonymous channel is closed or aborted. The cancellation cause reflects the abort reason when the remote end sends an abort message.
func (*AnonymousChannel) Emit ¶ added in v1.7.0
func (ch *AnonymousChannel) Emit(ctx context.Context, arguments ...any) error
Emit sends an event on this anonymous channel. The passed context can be used to cancel writing the message to the client. The first argument must be the event name string. Returns an error if the channel is not yet ready, is closed, or if the message could not be sent.
func (*AnonymousChannel) On ¶ added in v1.7.0
func (ch *AnonymousChannel) On(eventName string, handler any) *AnonymousChannel
On adds an event handler for the specified event on this anonymous channel. See ClientChannel.On for handler signature details.
func (*AnonymousChannel) Once ¶ added in v1.7.0
func (ch *AnonymousChannel) Once(eventName string, handler any) *AnonymousChannel
Once adds a one-time event handler for the specified event on this anonymous channel. See ClientChannel.On for handler signature details.
type ChannelClosedError ¶ added in v1.6.0
type ChannelClosedError struct {
Channel string
}
ChannelClosedError indicates the channel has been closed and cannot be used.
func (ChannelClosedError) Error ¶ added in v1.6.0
func (e ChannelClosedError) Error() string
Error returns the error message as a string.
type Client ¶
type Client struct {
ClientChannel // the "main" client channel with no name
// contains filtered or unexported fields
}
Client represents a WebSocket client
func ClientFromContext ¶ added in v1.1.0
ClientFromContext returns the WebSocket client from the given context. Returns nil if not available.
func NewClient ¶ added in v1.6.0
NewClient creates a new Client not associated with any Server. Register event handlers on the returned Client, then call Client.Bind to attach a WebSocket connection and begin processing messages.
Pass a non-nil conn to bind immediately (useful when no "open" handler is needed):
client := wrapper.NewClient(coder.Wrap(wsConn))
Pass nil when you want to register handlers first — the recommended pattern because it ensures no inbound message can arrive before a handler is in place:
client := wrapper.NewClient(nil)
client.On("open", func(c *wrapper.Client) { /* ... */ })
client.On("news", func(headline string) error { /* ... */ return nil })
conn, _ := websocket.Dial(ctx, "ws://example.com/ws", nil)
client.Bind(coder.Wrap(conn))
Example ¶
ExampleNewClient shows how to create a standalone WebSocket client. Handlers are registered before Bind is called, so no inbound message can arrive before the handlers are in place. The same client can be reused for reconnection by calling Bind again with a new connection.
client := NewClient(nil)
client.On("open", func(c *Client) {
log.Println("connected to server")
})
client.On("close", func(c *Client, status StatusCode, reason string, userClosed bool) {
if userClosed {
return // explicit close, don't reconnect
}
log.Println("disconnected:", reason)
// To reconnect, dial a new connection and call Bind again:
// conn, _ := websocket.Dial(ctx, "ws://example.com/ws", nil)
// c.Bind(coder.Wrap(conn))
})
client.On("news", func(headline string) error {
log.Println("breaking news:", headline)
return nil
})
// For this runnable example we use a mockConn in place of a real dial.
conn := newMockConn()
client.Bind(conn) // "open" fires here; reading starts here
_ = client.Emit(context.Background(), "subscribe", "sports")
conn.Close(StatusNormalClosure, "done")
func (*Client) Bind ¶ added in v1.6.0
Bind attaches conn to the Client and starts reading inbound messages. It can be called on a freshly created Client to establish the initial connection or called again after a disconnect to reconnect; all registered event handlers are preserved across calls.
If the Client already has an active connection, it is closed with StatusGoingAway before the new connection is attached, and any pending outbound requests are cancelled.
Bind fires the "open"/"connect" event handlers synchronously before returning. This guarantees that any handlers registered inside the "open" callback are registered before any inbound messages are processed.
To implement reconnection, call Bind again inside the "close" handler. Use the userClosed parameter to distinguish a user-initiated close from a connection drop — only reconnect when userClosed is false:
client.On("close", func(
c *wrapper.Client,
status wrapper.StatusCode,
reason string,
userClosed bool,
) {
if userClosed {
return // don't reconnect when the user explicitly closed
}
conn, err := websocket.Dial(ctx, "ws://example.com/ws", nil)
if err == nil {
c.Bind(coder.Wrap(conn))
}
})
func (*Client) Close ¶
func (c *Client) Close(status StatusCode, reason string) error
Close closes the active connection, aborts pending requets, and fires the "close"/"disconnect" event handlers synchronously before returning. If this Client is associated with a Server, Close removes it from the Server's set of connected clients.
func (*Client) Of ¶
func (c *Client) Of(name string) ClientChannel
Of returns a channel for the given name
type ClientChannel ¶
type ClientChannel struct {
// contains filtered or unexported fields
}
ClientChannel is a channel on which events can be sent and received. Events emitted or requests sent are sent to the specific client's channel on the remote end. See ClientChannel.On for more information about how received events are handled.
func (*ClientChannel) Close ¶ added in v1.6.0
func (ch *ClientChannel) Close() error
Close removes all event handlers for this channel.
Close returns nil.
func (ClientChannel) Emit ¶
func (ch ClientChannel) Emit(ctx context.Context, arguments ...any) error
Emit sends an event to the client on the specified channel. The passed context can be used to cancel writing the message to the client. The first argument must be the event name that tells the remote end which event handler to call. Returns an error if there was an error sending the message to the client.
func (ClientChannel) Name ¶
func (ch ClientChannel) Name() string
Name returns the name of the channel
func (ClientChannel) On ¶
func (ch ClientChannel) On(eventName string, handler any) ClientChannel
On adds an event handler for the specified event to the channel. When an event or request is received from a client, only a single handler is called. The priority of handlers called is as follows:
1. Handlers added via ClientChannel.Once
2. Handlers added via ClientChannel.On
3. Handlers added via ServerChannel.Once
4. Handlers added via ServerChannel.On
Therefore, if a handler is added to a ClientChannel, the corresponding handler on the ServerChannel will never be called for that particular client.
handler must be a function with arbitrary parameters, but it must return one or two values: a request result and an error. The request result is optional and may be of any type, but the error is required and must implement the error interface. When the event handler is called, the arguments of the event are converted to the types expected by handler. If an argument is not convertible to its parameter type (see reflect.Type.ConvertibleTo), the handler is not called and an error is returned to the client; however, there are some notable exceptions to this rule:
if the argument is a `nil` interface type and the parameter is a type that can accept `nil`, then `nil` is supplied as the argument
if the parameter is a pointer type, the address of the argument will be taken after type conversion
if the argument and parameters are slices of convertible types, each element in the slice will be converted into a new slice and supplied as the argument
Optionally, the handler can provide an additional parameter for the context.Context of the request. Call ClientFromContext(ctx) to return the *Client object for the client that emitted the event.
There are also reserved events can occur on the main channel of a client:
"error" - called when an error occurs on the client. The handler is passed the error as a single argument and has the form `func(*Client, error)`
"message" - called when a message is received from the client. The handler is passed the Message as a single argument and has the form `func(*Client, Message)`. The handler may not modify the message; this event is primarily for logging purposes.
"close" or "disconnect" - called when the client disconnects. The handler is passed the status code, reason string, and a boolean indicating whether the close was user-initiated (i.e. Client.Close was called). The handler has the form `func(*Client, StatusCode, string, bool)`
The reserved events that can occur on the main channel of a server include:
"open" or "connect" - called when a new client connects. The handler is passed the client and has the form `func(*Client)`
"error"
"close" or "disconnect" - called when any client disconnects.
If event handlers do not conform to the expected function signature, On will panic.
If On is called multiple times for the same event name, the last handler will be used. If handler is nil, the event handler is removed.
func (ClientChannel) Once ¶
func (ch ClientChannel) Once(eventName string, handler any) ClientChannel
Once adds a one-time event handler for the specified event to the channel. See ClientChannel.On for more information about how event handlers are called.
type ClientError ¶
type ClientError struct {
Client *Client
// contains filtered or unexported fields
}
ClientError is an error for a specific client
func (ClientError) Error ¶
func (ce ClientError) Error() string
Error returns the error message as a string
func (ClientError) Unwrap ¶ added in v1.6.0
func (ce ClientError) Unwrap() error
Unwrap returns the underlying error.
type CloseHandler ¶
type CloseHandler = func(*Client, StatusCode, string, bool)
type CloseHandlerOld ¶ added in v1.6.0
type CloseHandlerOld = func(*Client, StatusCode, string)
type Conn ¶
type Conn interface {
// ReadMessage reads a message from the WebSocket connection. The context
// is used to cancel the read operation.
ReadMessage(ctx context.Context, msg *Message) error
// WriteMessage writes a message to the WebSocket connection. The context
// is used to cancel the write operation.
WriteMessage(ctx context.Context, msg *Message) error
// Close closes the WebSocket connection with the given status code and
// reason.
Close(statusCode StatusCode, reason string) error
// CloseNow closes the WebSocket connection immediately without waiting for
// any pending operations to complete.
CloseNow() error
}
Conn represents a WebSocket connection that can read and write messages. ReadMessage may not be called concurrently, and one must always read from the connection to properly handle control frames.
type ErrorHandler ¶
type HandlerContextFunc ¶
type HandlerContextFunc func( ctx context.Context, channel string, eventName string, ) context.Context
HandlerContextFunc is a function that can modify the context passed to every registered event handler before it is called.
type Message ¶
type Message struct {
Channel string `json:"c,omitempty"`
AnonymousChannel int `json:"h,omitempty"`
Arguments []json.RawMessage `json:"a,omitempty"` // Arguments[0] is the event name
RequestID *int `json:"i,omitempty"`
ResponseData any `json:"d,omitempty"`
ResponseError any `json:"e,omitempty"`
ResponseJSError weakBool `json:"_,omitempty"`
CancelReason any `json:"x,omitempty"` // Request cancellation signal
IgnoreIfFalse *weakBool `json:"ws-wrapper,omitempty"`
// contains filtered or unexported fields
}
Message is a ws-wrapper JSON-encoded message. See https://github.com/bminer/ws-wrapper/blob/master/README.md#protocol
func (Message) CancelCause ¶ added in v1.4.0
CancelCause returns the cancel/abort reason from this message as an error. Works for both inbound request cancellations ({i, x}) and anonymous channel aborts ({h, x}). Returns "message is not a cancellation" if CancelReason is nil; returns context.Canceled if the reason cannot be parsed.
func (Message) EventName ¶
EventName returns the name of the event or empty string if the message is invalid
func (Message) HandlerArguments ¶
func (m Message) HandlerArguments() []json.RawMessage
HandlerArguments returns the arguments for the event handler
type MessageHandler ¶
type Server ¶
type Server struct {
ServerChannel // the "main" server channel with no name
// contains filtered or unexported fields
}
Server represents a server that accepts WebSocket connections, handles inbound messages, and sends messages to connected clients. Rather than listening for connections itself, the Accept method takes any connection that implements the Conn interface. This allows the server to be used with any WebSocket library. Various adapter libraries are available in the adapters subdirectory.
Example (Echo) ¶
This example shows how to use create a ws-server-wrapper that echoes a string sent to the "echo" event handler.
// Create ws-wrapper-server and "echo" event handler. Note that the event
// handler function may optionally have a context as its first argument,
// it must always return 2 values, and the second value must implement
// error.
wsServer := NewServer()
wsServer.On("echo", func(s string) (string, error) {
// If a ws-server client sends a "echo" request, it will receive the
// echoed response.
return s, nil
})
// Create HTTP handler function that upgrades the HTTP connection to a
// WebSocket using the github.com/coder/websocket package.
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Use your favorite WebSocket library to upgrade the HTTP request to
// a WebSocket connection...
// 2. Use an adapter (or write your own) to write and parse ws-wrapper
// messages.
// 3. Call `wsServer.Accept` to attach the underlying WebSocket
// connection to `wsServer`. It will start listening for inbound
// messages.
// See full example in the coder adapter.
})
// Start the HTTP server
log.Fatal(http.ListenAndServe("localhost:8080", h))
func (*Server) Accept ¶
Accept adds a new client connection to the server. conn only needs to implement the Conn interface. If the server has been closed, Accept will return an error and close conn.
func (*Server) Close ¶
Close closes the server and all connected clients. Returns the first error encountered while closing clients.
func (*Server) Of ¶
func (s *Server) Of(name string) ServerChannel
Of returns a channel for the given name
func (*Server) SetHandlerContext ¶
func (s *Server) SetHandlerContext(f HandlerContextFunc)
SetHandlerContext sets the handler context function. This function is called before every event handler is called and is used to modify the context passed to every event handler. It can be used to implement timeouts for individual event handlers, for example.
type ServerChannel ¶
type ServerChannel struct {
// contains filtered or unexported fields
}
ServerChannel is a channel on which events can be sent and received. Events emitted or requests sent are sent to all connected clients to the channel of the same name on the remote end. See ClientChannel.On for more information about how received events are handled.
func (*ServerChannel) Close ¶ added in v1.6.0
func (c *ServerChannel) Close() error
Close removes all event handlers for this channel.
Close returns nil.
func (ServerChannel) Emit ¶
func (c ServerChannel) Emit( ctx context.Context, arguments ...any, ) (errs []ClientError)
Emit sends an event to all clients on the specified channel. The passed context can be used to cancel writing the message to the client. The second argument is the event name that tells the remote end which event handler to call. Returns a slice of ClientErrors that contains an entry if an error occurred when sending the message to a specific client.
func (ServerChannel) Name ¶
func (c ServerChannel) Name() string
Name returns the name of the channel
func (ServerChannel) On ¶
func (c ServerChannel) On(eventName string, handler any) ServerChannel
On adds an event handler for the specified event to the channel. See ClientChannel.On for more information about how event handlers are called.
Example ¶
Demonstrates how to add various event handlers.
wsServer := NewServer()
// Simple event handler that does not return a response
// Note: Since no channel name is provided, this handler is attached to the
// "main" channel with no name.
wsServer.On("print", func(s string) (struct{}, error) {
fmt.Println(s)
return struct{}{}, nil
})
// Simple request handler that echoes whatever was received
wsServer.On("echo", func(s string) (string, error) {
return s, nil
})
// Request handler that reads a file from disk. CAUTION: Be sure to sanitize
// user input.
// Note: json.Marshall will return a base64 string to the client because
// `[]byte` is returned by the handler.
wsServer.On("readFile", func(filename string) ([]byte, error) {
return os.ReadFile(filename)
})
Example (Channel) ¶
Demonstrates adding event handlers to a particular channel.
wsServer := NewServer()
// Adds a "readFile" event handler to the "io" channel.
wsServer.Of("io").On("readFile", func(filename string) ([]byte, error) {
return os.ReadFile(filename)
})
func (ServerChannel) Once ¶
func (c ServerChannel) Once(eventName string, handler any) ServerChannel
Once adds a one-time event handler for the specified event to the channel. See ClientChannel.On for more information about how event handlers are called.
type StatusCode ¶
type StatusCode int
StatusCode represents a WebSocket status code. https://tools.ietf.org/html/rfc6455#section-7.4
const ( StatusNormalClosure StatusCode = 1000 StatusGoingAway StatusCode = 1001 StatusProtocolError StatusCode = 1002 StatusUnsupportedData StatusCode = 1003 StatusInvalidFramePayloadData StatusCode = 1007 StatusPolicyViolation StatusCode = 1008 StatusMessageTooBig StatusCode = 1009 StatusMandatoryExtension StatusCode = 1010 StatusInternalError StatusCode = 1011 StatusServiceRestart StatusCode = 1012 StatusTryAgainLater StatusCode = 1013 StatusBadGateway StatusCode = 1014 )