Documentation
¶
Overview ¶
Package websocket implements the WebSocket protocol (RFC 6455) on top of the standard library, with no third-party dependencies. It provides a server-side upgrade, a client-side dial, the permessage-deflate extension (RFC 7692) and subprotocol negotiation.
A connection is represented by Conn. Because the package name is websocket, the natural variable name for a connection is ws:
ws, err := websocket.Upgrade(w, r)
if err != nil {
return
}
defer ws.Close()
for {
mt, data, err := ws.ReadMessage()
if err != nil {
break
}
if err := ws.WriteMessage(mt, data); err != nil {
break
}
}
Server upgrade. Use the package Upgrade for a one-off, or NewUpgrader for a reusable configuration:
up := websocket.NewUpgrader(websocket.WithReadLimit(1 << 20)) ws, err := up.Upgrade(w, r)
By default the upgrade only accepts same-origin requests, which guards against cross-site WebSocket hijacking. Allow other origins explicitly with WithOriginChecker.
Client dial. Dial connects to a server, negotiating TLS for wss URLs:
ws, resp, err := websocket.Dial(ctx, "wss://example.com/ws")
Concurrency. A connection supports one concurrent reader and one concurrent writer. That is, ReadMessage or NextReader may run in one goroutine while WriteMessage or NextWriter runs in another, but you must not call two readers or two writers at the same time. WriteControl may be called concurrently with a writer.
Control frames. Ping, pong and close frames are handled by the reader: a ping is answered with a pong automatically, and a close starts the closing handshake. Install SetPingHandler, SetPongHandler or SetCloseHandler to observe or override this.
Example ¶
Example runs a tiny echo server and a client against it.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/goloop/websocket"
)
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ws, err := websocket.Upgrade(w, r,
websocket.WithOriginChecker(func(*http.Request) bool { return true }))
if err != nil {
return
}
defer ws.Close()
mt, data, err := ws.ReadMessage()
if err != nil {
return
}
_ = ws.WriteMessage(mt, data)
}))
defer srv.Close()
url := "ws" + strings.TrimPrefix(srv.URL, "http")
ws, _, err := websocket.Dial(context.Background(), url)
if err != nil {
fmt.Println("dial:", err)
return
}
defer ws.Close()
if err := ws.WriteMessage(websocket.TextMessage, []byte("ping")); err != nil {
fmt.Println("write:", err)
return
}
_, data, err := ws.ReadMessage()
if err != nil {
fmt.Println("read:", err)
return
}
fmt.Println(string(data))
}
Output: ping
Index ¶
- Variables
- func IsCloseError(err error, codes ...CloseCode) bool
- func IsUnexpectedCloseError(err error, expected ...CloseCode) bool
- func IsWebSocketUpgrade(r *http.Request) bool
- func Subprotocols(r *http.Request) []string
- type CloseCode
- type CloseError
- type Conn
- func (c *Conn) Close() error
- func (c *Conn) CloseWithStatus(code CloseCode, reason string) error
- func (c *Conn) LocalAddr() net.Addr
- func (c *Conn) NetConn() net.Conn
- func (c *Conn) NextReader() (MessageType, io.Reader, error)
- func (c *Conn) NextWriter(mt MessageType) (io.WriteCloser, error)
- func (c *Conn) ReadJSON(v any) error
- func (c *Conn) ReadMessage() (MessageType, []byte, error)
- func (c *Conn) RemoteAddr() net.Addr
- func (c *Conn) SetCloseHandler(fn func(code CloseCode, text string) error)
- func (c *Conn) SetPingHandler(fn func(appData string) error)
- func (c *Conn) SetPongHandler(fn func(appData string) error)
- func (c *Conn) SetReadDeadline(t time.Time) error
- func (c *Conn) SetReadLimit(n int64)
- func (c *Conn) SetWriteDeadline(t time.Time) error
- func (c *Conn) SetWriteLimit(n int64)
- func (c *Conn) Subprotocol() string
- func (c *Conn) WriteControl(mt MessageType, data []byte, deadline time.Time) error
- func (c *Conn) WriteJSON(v any) error
- func (c *Conn) WriteMessage(mt MessageType, data []byte) error
- type DialOption
- func WithDialCompression() DialOption
- func WithDialHandshakeLimit(bytes int64) DialOption
- func WithDialHandshakeTimeout(d time.Duration) DialOption
- func WithDialHeader(h http.Header) DialOption
- func WithDialNetDialer(d *net.Dialer) DialOption
- func WithDialSubprotocols(names ...string) DialOption
- func WithDialTLSConfig(cfg *tls.Config) DialOption
- type HandshakeError
- type MessageType
- type Option
- type Upgrader
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrReadLimit means a message exceeded [Conn.SetReadLimit]. The // connection is closed with 1009. ErrReadLimit = errors.New("websocket: read limit exceeded") // ErrProtocol means the peer broke the framing protocol. Every such // violation matches it, and the connection is closed with 1002; the // error's own text says which rule was broken. ErrProtocol = errors.New("websocket: protocol error") // The rest are programming errors: the arguments of a call were wrong, // so nothing is written and the connection is left as it was. ErrWriteClosed = errors.New("websocket: write to closed message writer") ErrBadControl = errors.New("websocket: not a control frame type") ErrControlTooBig = errors.New("websocket: control frame payload too large") ErrBadWriteType = errors.New("websocket: not a data message type") ErrBadClosePayload = errors.New( "websocket: close payload has a reserved code or invalid reason") )
Errors a caller can act on. They are values rather than messages so that a read or write loop can tell a message that was too big from a peer that broke the protocol from a call that was simply wrong, with errors.Is and without matching on text.
var ErrBadHandshake = errors.New("websocket: bad handshake")
ErrBadHandshake is returned by Dial when the server does not complete the WebSocket handshake. The returned *http.Response carries the server's reply so the caller can inspect the status and headers.
var ErrCloseSent = errors.New("websocket: close sent")
ErrCloseSent is returned by a write after the closing handshake has started.
var ErrHandshakeTooLarge = errors.New("websocket: handshake response too large")
ErrHandshakeTooLarge is returned by Dial when the server's handshake response exceeds the byte budget set by WithDialHandshakeLimit. Nothing bounds an HTTP response read this way on its own, so without a budget a server could spend the client's memory before the handshake timeout was anywhere near expiring.
var ErrWriteLimit = errors.New("websocket: write limit exceeded")
ErrWriteLimit is returned when a message would exceed the limit set by Conn.SetWriteLimit. Nothing is written and the connection stays usable, so a caller can send something smaller instead.
Functions ¶
func IsCloseError ¶
IsCloseError reports whether err is a *CloseError with one of the given codes.
func IsUnexpectedCloseError ¶
IsUnexpectedCloseError reports whether err is a *CloseError whose code is not one of the expected ones. It is the usual way to tell a clean shutdown from a surprising one in a read loop.
func IsWebSocketUpgrade ¶
IsWebSocketUpgrade reports whether r is a WebSocket upgrade request.
func Subprotocols ¶
Subprotocols returns the subprotocols listed in the request's Sec-WebSocket-Protocol header. The header may be split across several field lines, all of which are considered.
Types ¶
type CloseCode ¶
type CloseCode uint16
CloseCode is a WebSocket close status code (RFC 6455 section 7.4).
const ( CloseNormalClosure CloseCode = 1000 CloseGoingAway CloseCode = 1001 CloseProtocolError CloseCode = 1002 CloseUnsupportedData CloseCode = 1003 CloseNoStatusReceived CloseCode = 1005 CloseAbnormalClosure CloseCode = 1006 CloseInvalidFramePayloadData CloseCode = 1007 ClosePolicyViolation CloseCode = 1008 CloseMessageTooBig CloseCode = 1009 CloseMandatoryExtension CloseCode = 1010 CloseInternalServerErr CloseCode = 1011 CloseServiceRestart CloseCode = 1012 CloseTryAgainLater CloseCode = 1013 CloseBadGateway CloseCode = 1014 CloseTLSHandshake CloseCode = 1015 )
Close status codes defined by RFC 6455. Codes 1005, 1006 and 1015 are reserved and must not be sent on the wire; they only appear as locally generated values.
type CloseError ¶
CloseError records how the connection ended.
Usually it is the close frame the peer sent, with its code and reason. It is also how an abnormal end is reported: a connection that dropped without a closing handshake yields code 1006 with the underlying error kept as the cause, so errors.Is still finds io.EOF or io.ErrUnexpectedEOF beneath it. 1006 is a local observation and is never put on the wire.
func (*CloseError) Error ¶
func (e *CloseError) Error() string
Error implements the error interface, formatting the close code and, when present, the reason text the peer sent with the close frame.
func (*CloseError) Unwrap ¶ added in v0.5.0
func (e *CloseError) Unwrap() error
Unwrap returns the error behind an abnormal close, so a caller can still match io.EOF or io.ErrUnexpectedEOF through it.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a WebSocket connection. It carries a single logical stream of messages in each direction over a hijacked net.Conn.
A Conn supports one concurrent reader and one concurrent writer; see the package documentation. WriteControl may be called concurrently with a writer.
func Dial ¶
Dial connects to a WebSocket server. The URL scheme must be ws or wss; wss connections are negotiated over TLS. On success it returns the connection and the server's handshake response. On a non-101 reply it returns ErrBadHandshake together with the response.
func (*Conn) Close ¶
Close closes the underlying network connection without performing the closing handshake. For a graceful shutdown, call CloseWithStatus first, then Close.
func (*Conn) CloseWithStatus ¶
CloseWithStatus sends a close frame with the given code and reason. It does not close the network connection; the peer is expected to answer with its own close, after which the reader returns a *CloseError and the caller should call Close. A code of 0 sends an empty close payload.
func (*Conn) NetConn ¶
NetConn returns the underlying network connection. Reading from or writing to it directly will corrupt the WebSocket stream; it is an escape hatch for deadlines, addresses and similar low-level needs.
func (*Conn) NextReader ¶
func (c *Conn) NextReader() (MessageType, io.Reader, error)
NextReader returns a reader for the next data message. Only one reader may be active at a time; opening a new one discards any unread bytes of the previous message. A compressed message is fully inflated and returned as an in-memory reader; an uncompressed one is streamed.
func (*Conn) NextWriter ¶
func (c *Conn) NextWriter(mt MessageType) (io.WriteCloser, error)
NextWriter returns a writer for the next message of the given data type. The message is sent when the writer is closed. Only one writer may be open at a time.
func (*Conn) ReadJSON ¶
ReadJSON reads the next data message and unmarshals it into v. Control frames are handled transparently by the underlying read.
func (*Conn) ReadMessage ¶
func (c *Conn) ReadMessage() (MessageType, []byte, error)
ReadMessage reads the next complete data message. It transparently handles interleaved control frames (answering pings, tracking pongs and closes) and, if negotiated, decompresses the message. A text message is validated as UTF-8.
func (*Conn) RemoteAddr ¶
RemoteAddr returns the remote network address.
func (*Conn) SetCloseHandler ¶
SetCloseHandler sets the handler for received close frames. The default sends a close frame back. A custom handler replaces that behaviour.
func (*Conn) SetPingHandler ¶
SetPingHandler sets the handler for received ping frames. The default sends a pong with the same payload. Setting a handler disables the automatic pong, so a custom handler that wants to reply must do so itself.
func (*Conn) SetPongHandler ¶
SetPongHandler sets the handler for received pong frames. The default ignores them.
func (*Conn) SetReadDeadline ¶
SetReadDeadline sets the deadline for future reads. A zero value clears it.
func (*Conn) SetReadLimit ¶
SetReadLimit sets the maximum size in bytes of a single received message. A message that would exceed it fails the read and closes the connection with 1009. A value <= 0 restores the default.
func (*Conn) SetWriteDeadline ¶
SetWriteDeadline sets the deadline for future writes and for a write already in progress, as on a net.Conn. A zero value clears it. The deadline is remembered so that the connection's own control writes (auto-pong, close echo) can restore it instead of leaving a stale deadline.
It does not wait for the write lock: a write that is stuck on a peer that stopped reading is exactly what a deadline is for.
func (*Conn) SetWriteLimit ¶ added in v0.3.0
SetWriteLimit sets the maximum size in bytes of a single outgoing message. A write past it fails with ErrWriteLimit and nothing is sent; the connection stays usable. A value <= 0, the default, means no bound.
It exists because a message is built in memory before any of it goes out: Conn.NextWriter buffers everything written to it until Close, so io.Copy from an unbounded source is bounded by nothing but available memory. A limit turns that into an error the application can handle.
Like SetReadLimit it is not synchronized with writes in progress: set it before the connection is handed to the code that writes.
func (*Conn) Subprotocol ¶
Subprotocol returns the negotiated subprotocol, or an empty string if none was selected.
func (*Conn) WriteControl ¶
WriteControl sends a control frame (close, ping or pong). The deadline bounds the whole call, including the wait for a data write in progress to finish; when it passes first the frame is not sent and the error reports a timeout. A zero deadline means no timeout. WriteControl may be called from a goroutine other than the one writing messages.
func (*Conn) WriteMessage ¶
func (c *Conn) WriteMessage(mt MessageType, data []byte) error
WriteMessage sends data as a single, unfragmented message of the given type. If write compression is enabled, a data message is compressed with permessage-deflate. For control types it is equivalent to WriteControl with the write deadline already set on the connection.
type DialOption ¶
type DialOption func(*dialConfig)
DialOption configures a client Dial.
func WithDialCompression ¶
func WithDialCompression() DialOption
WithDialCompression offers the permessage-deflate extension to the server.
func WithDialHandshakeLimit ¶ added in v0.2.0
func WithDialHandshakeLimit(bytes int64) DialOption
WithDialHandshakeLimit bounds how many bytes of the server's handshake response Dial reads before failing with ErrHandshakeTooLarge. The default is 64 KiB; a value <= 0 removes the bound. It applies only to the handshake, so it does not limit messages: use Conn.SetReadLimit for those.
func WithDialHandshakeTimeout ¶
func WithDialHandshakeTimeout(d time.Duration) DialOption
WithDialHandshakeTimeout bounds the time spent establishing the connection once the TCP dial has succeeded: the TLS handshake for wss, writing the request and reading the response. The default is 45s; a value <= 0 leaves the context as the only bound.
When the context carries a deadline of its own, the earlier of the two applies, so a long-lived context cannot quietly replace a short timeout.
func WithDialHeader ¶
func WithDialHeader(h http.Header) DialOption
WithDialHeader adds extra HTTP headers to the client handshake request (for example Authorization or Cookie).
func WithDialNetDialer ¶
func WithDialNetDialer(d *net.Dialer) DialOption
WithDialNetDialer sets the net.Dialer used to establish the TCP connection.
func WithDialSubprotocols ¶
func WithDialSubprotocols(names ...string) DialOption
WithDialSubprotocols sets the subprotocols the client offers.
func WithDialTLSConfig ¶
func WithDialTLSConfig(cfg *tls.Config) DialOption
WithDialTLSConfig sets the TLS configuration used for wss URLs.
type HandshakeError ¶
type HandshakeError struct {
Status int
// contains filtered or unexported fields
}
HandshakeError describes a failed WebSocket upgrade. Upgrade returns it as a *HandshakeError, so errors.As(err, &he) with a *HandshakeError finds it, and it has already written an HTTP error response to the client by then.
Status is the HTTP status that was written, which classifies the failure without reading the message: 405 for the wrong method, 400 for a malformed request, 426 for the wrong version, 403 for a rejected origin and 500 for a response writer that cannot be hijacked.
func (*HandshakeError) Error ¶
func (e *HandshakeError) Error() string
Error implements the error interface, returning the reason the upgrade failed.
type MessageType ¶
type MessageType int
MessageType identifies a WebSocket frame opcode as defined in RFC 6455 section 5.2. The data types TextMessage and BinaryMessage are what an application normally sends and receives; the control types are handled by the connection but are also accepted by WriteControl.
const ( // TextMessage denotes a UTF-8 encoded text message. TextMessage MessageType = 1 // BinaryMessage denotes a binary message. BinaryMessage MessageType = 2 // CloseMessage denotes a close control message. CloseMessage MessageType = 8 // PingMessage denotes a ping control message. PingMessage MessageType = 9 // PongMessage denotes a pong control message. PongMessage MessageType = 10 )
type Option ¶
type Option func(*Upgrader)
Option configures an Upgrader (server side).
func WithCompression ¶
func WithCompression() Option
WithCompression enables the permessage-deflate extension when the client offers it.
func WithCompressionLevel ¶
WithCompressionLevel sets the flate level used for outgoing messages (flate.HuffmanOnly to flate.BestCompression).
func WithHandshakeTimeout ¶
WithHandshakeTimeout bounds the time spent writing the upgrade response.
func WithOriginChecker ¶
WithOriginChecker sets the function that decides whether a request's Origin is allowed. The default accepts only same-origin requests, which guards against cross-site WebSocket hijacking. To allow any origin (for example a public API or local development) pass a function that always returns true.
func WithReadLimit ¶
WithReadLimit sets the maximum size of a single received message.
func WithSubprotocols ¶
WithSubprotocols sets the subprotocols the server supports, in order of preference. The first one the client also offers is selected.
type Upgrader ¶
type Upgrader struct {
// contains filtered or unexported fields
}
Upgrader holds a reusable server-side upgrade configuration.
func NewUpgrader ¶
NewUpgrader returns an Upgrader configured with the given options. By default it accepts only same-origin requests and does not enable compression.