Help us shape the future of Go! Take the 2026 Go Developer Survey

websocket

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 21 Imported by: 0

README

deps.dev License License Stay with Ukraine

websocket

websocket implements the WebSocket protocol (RFC 6455) on top of Go's standard library, with no third-party dependencies. It gives you a server-side upgrade, a client-side dial, the permessage-deflate extension (RFC 7692) and subprotocol negotiation.

Because the package is named websocket, the natural name for a connection is ws:

ws, err := websocket.Upgrade(w, r)

It composes with the rest of net/http, including the goloop/mux router and goloop/middlewares (whose Compress correctly leaves upgrade requests alone).

Features

  • Server Upgrade (one-off or a reusable Upgrader) and client Dial.
  • Text and binary messages, with ReadMessage/WriteMessage and streaming NextReader/NextWriter.
  • Automatic ping/pong and a proper closing handshake, with overridable handlers.
  • permessage-deflate compression with a decompression-bomb guard.
  • Subprotocol negotiation, per-message size limits, deadlines.
  • ReadJSON/WriteJSON convenience.
  • Same-origin by default, to block cross-site WebSocket hijacking.

Installation

go get -u github.com/goloop/websocket
import "github.com/goloop/websocket"

Requires Go 1.25 or newer.

Quick start

Server (echo):

func handler(w http.ResponseWriter, r *http.Request) {
    ws, err := websocket.Upgrade(w, r)
    if err != nil {
        return // an HTTP error has already been written
    }
    defer ws.Close()
    for {
        mt, data, err := ws.ReadMessage()
        if err != nil {
            break
        }
        if err := ws.WriteMessage(mt, data); err != nil {
            break
        }
    }
}

Reusable configuration:

up := websocket.NewUpgrader(
    websocket.WithReadLimit(1<<20),
    websocket.WithCompression(),
    websocket.WithSubprotocols("chat"),
)
ws, err := up.Upgrade(w, r)

Client:

ws, resp, err := websocket.Dial(ctx, "wss://example.com/ws")
if err != nil {
    log.Fatal(err)
}
defer ws.Close()
ws.WriteMessage(websocket.TextMessage, []byte("hello"))

Security notes

  • Origin. The default Upgrader accepts only same-origin requests. To serve a public endpoint or a browser on another origin, pass WithOriginChecker with your own policy.
  • Message size. WithReadLimit (default 32 MiB) caps a single message; the limit applies to the decompressed size, so it also bounds permessage-deflate.
  • Compression. permessage-deflate uses "no context takeover" per message.
  • Deadlines. Set SetReadDeadline/SetWriteDeadline to protect against slow peers.

Concurrency

A connection supports one concurrent reader and one concurrent writer: ReadMessage may run in one goroutine while WriteMessage runs in another. WriteControl may be called concurrently with a writer. Do not run two readers or two writers at once.

Documentation

Contributing

Contributions are welcome. Please run go test ./..., go test -race ./..., go vet ./... and gofmt -l . before submitting a pull request.

License

websocket is released under the MIT License. See LICENSE.

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

Examples

Constants

This section is empty.

Variables

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

View Source
var ErrCloseSent = errors.New("websocket: close sent")

ErrCloseSent is returned by a write after the closing handshake has started.

Functions

func IsCloseError

func IsCloseError(err error, codes ...CloseCode) bool

IsCloseError reports whether err is a *CloseError with one of the given codes.

func IsUnexpectedCloseError

func IsUnexpectedCloseError(err error, expected ...CloseCode) bool

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

func IsWebSocketUpgrade(r *http.Request) bool

IsWebSocketUpgrade reports whether r is a WebSocket upgrade request.

func Subprotocols

func Subprotocols(r *http.Request) []string

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

type CloseError struct {
	Code CloseCode
	Text string
}

CloseError records the close code and reason received from the peer.

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.

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

func Dial(ctx context.Context, urlStr string, opts ...DialOption) (*Conn, *http.Response, error)

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 Upgrade

func Upgrade(w http.ResponseWriter, r *http.Request, opts ...Option) (*Conn, error)

Upgrade is a convenience wrapper that upgrades r with a one-off configuration.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the underlying network connection without performing the closing handshake. For a graceful shutdown, call CloseWithStatus first, then Close.

func (*Conn) CloseWithStatus

func (c *Conn) CloseWithStatus(code CloseCode, reason string) error

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

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Conn) NetConn

func (c *Conn) NetConn() net.Conn

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

func (c *Conn) ReadJSON(v any) error

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

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*Conn) SetCloseHandler

func (c *Conn) SetCloseHandler(fn func(code CloseCode, text string) error)

SetCloseHandler sets the handler for received close frames. The default sends a close frame back. A custom handler replaces that behaviour.

func (*Conn) SetPingHandler

func (c *Conn) SetPingHandler(fn func(appData string) error)

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

func (c *Conn) SetPongHandler(fn func(appData string) error)

SetPongHandler sets the handler for received pong frames. The default ignores them.

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future reads. A zero value clears it.

func (*Conn) SetReadLimit

func (c *Conn) SetReadLimit(n int64)

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

func (c *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future writes. 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.

func (*Conn) Subprotocol

func (c *Conn) Subprotocol() string

Subprotocol returns the negotiated subprotocol, or an empty string if none was selected.

func (*Conn) WriteControl

func (c *Conn) WriteControl(mt MessageType, data []byte, deadline time.Time) error

WriteControl sends a control frame (close, ping or pong). The deadline bounds the write; a zero deadline means no timeout. WriteControl may be called from a goroutine other than the one writing messages.

func (*Conn) WriteJSON

func (c *Conn) WriteJSON(v any) error

WriteJSON marshals v to JSON and sends it as a text message.

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 WithDialHandshakeTimeout

func WithDialHandshakeTimeout(d time.Duration) DialOption

WithDialHandshakeTimeout bounds the time spent on the WebSocket handshake (writing the request and reading the response) when the context carries no deadline of its own. The default is 45s; a value <= 0 disables the bound.

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

HandshakeError describes a failed WebSocket upgrade. When Upgrade returns one, it has already written an HTTP error response to the client.

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

func WithCompressionLevel(level int) Option

WithCompressionLevel sets the flate level used for outgoing messages (flate.HuffmanOnly to flate.BestCompression).

func WithHandshakeTimeout

func WithHandshakeTimeout(d time.Duration) Option

WithHandshakeTimeout bounds the time spent writing the upgrade response.

func WithOriginChecker

func WithOriginChecker(fn func(*http.Request) bool) Option

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

func WithReadLimit(bytes int64) Option

WithReadLimit sets the maximum size of a single received message.

func WithSubprotocols

func WithSubprotocols(names ...string) Option

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

func NewUpgrader(opts ...Option) *Upgrader

NewUpgrader returns an Upgrader configured with the given options. By default it accepts only same-origin requests and does not enable compression.

func (*Upgrader) Upgrade

func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request) (*Conn, error)

Upgrade completes the WebSocket handshake and returns a Conn. On failure it writes an HTTP error response and returns a *HandshakeError (or the hijack error).

Jump to

Keyboard shortcuts

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