websocket

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2023 License: MIT Imports: 28 Imported by: 0

README

websocket

godoc coverage

websocket is a minimal and idiomatic WebSocket library for Go.

note: I haven't been responsive for questions/reports on the issue tracker but I do read through and there are no outstanding bugs. There are certainly some nice to haves that I should merge in/figure out but nothing critical. I haven't given up on adding new features and cleaning up the code further, just been busy. Should anything critical arise, I will fix it.

Install

go get github.com/intercom/websocket

Highlights

Roadmap

  • HTTP/2 #4

Examples

For a production quality example that demonstrates the complete API, see the echo example.

For a full stack example, see the chat example.

Server
http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
	c, err := websocket.Accept(w, r, nil)
	if err != nil {
		// ...
	}
	defer c.Close(websocket.StatusInternalError, "the sky is falling")

	ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
	defer cancel()

	var v interface{}
	err = wsjson.Read(ctx, c, &v)
	if err != nil {
		// ...
	}

	log.Printf("received: %v", v)

	c.Close(websocket.StatusNormalClosure, "")
})
Client
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
if err != nil {
	// ...
}
defer c.Close(websocket.StatusInternalError, "the sky is falling")

err = wsjson.Write(ctx, c, "hi")
if err != nil {
	// ...
}

c.Close(websocket.StatusNormalClosure, "")

Comparison

gorilla/websocket

Advantages of gorilla/websocket:

Advantages of github.com/intercom/websocket:

golang.org/x/net/websocket

golang.org/x/net/websocket is deprecated. See golang/go/issues/18152.

The net.Conn can help in transitioning to github.com/intercom/websocket.

gobwas/ws

gobwas/ws has an extremely flexible API that allows it to be used in an event driven style for performance. See the author's blog post.

However when writing idiomatic Go, github.com/intercom/websocket will be faster and easier to use.

Documentation

Overview

Example (CrossOrigin)
package main

import (
	"log"
	"net/http"

	"intercom/websocket"
)

func main() {
	// This handler demonstrates how to safely accept cross origin WebSockets
	// from the origin example.com.
	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
			OriginPatterns: []string{"example.com"},
		})
		if err != nil {
			log.Println(err)
			return
		}
		c.Close(websocket.StatusNormalClosure, "cross origin WebSocket accepted")
	})

	err := http.ListenAndServe("localhost:8080", fn)
	log.Fatal(err)
}
Output:

Example (Echo)

This example demonstrates a echo server.

package main

import ()

func main() {
	// https://github.com/nhooyr/websocket/tree/master/examples/echo
}
Output:

Example (FullStackChat)

This example demonstrates full stack chat with an automated test.

package main

import ()

func main() {
	// https://github.com/nhooyr/websocket/tree/master/examples/chat
}
Output:

Example (WriteOnly)
package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"intercom/websocket"
	"intercom/websocket/wsjson"
)

func main() {
	// This handler demonstrates how to correctly handle a write only WebSocket connection.
	// i.e you only expect to write messages and do not expect to read any messages.
	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c, err := websocket.Accept(w, r, nil)
		if err != nil {
			log.Println(err)
			return
		}
		defer c.Close(websocket.StatusInternalError, "the sky is falling")

		ctx, cancel := context.WithTimeout(r.Context(), time.Minute*10)
		defer cancel()

		ctx = c.CloseRead(ctx)

		t := time.NewTicker(time.Second * 30)
		defer t.Stop()

		for {
			select {
			case <-ctx.Done():
				c.Close(websocket.StatusNormalClosure, "")
				return
			case <-t.C:
				err = wsjson.Write(ctx, c, "hi")
				if err != nil {
					log.Println(err)
					return
				}
			}
		}
	})

	err := http.ListenAndServe("localhost:8080", fn)
	log.Fatal(err)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NetConn

func NetConn(ctx context.Context, c *Conn, msgType MessageType) net.Conn

NetConn converts a *websocket.Conn into a net.Conn.

It's for tunneling arbitrary protocols over WebSockets. Few users of the library will need this but it's tricky to implement correctly and so provided in the library. See https://github.com/nhooyr/websocket/issues/100.

Every Write to the net.Conn will correspond to a message write of the given type on *websocket.Conn.

The passed ctx bounds the lifetime of the net.Conn. If cancelled, all reads and writes on the net.Conn will be cancelled.

If a message is read that is not of the correct type, the connection will be closed with StatusUnsupportedData and an error will be returned.

Close will close the *websocket.Conn with StatusNormalClosure.

When a deadline is hit, the connection will be closed. This is different from most net.Conn implementations where only the reading/writing goroutines are interrupted but the connection is kept alive.

The Addr methods will return a mock net.Addr that returns "websocket" for Network and "websocket/unknown-addr" for String.

A received StatusNormalClosure or StatusGoingAway close frame will be translated to io.EOF when reading.

Types

type AcceptOptions

type AcceptOptions struct {
	Subprotocols         []string
	InsecureSkipVerify   bool
	OriginPatterns       []string
	CompressionMode      CompressionMode
	CompressionThreshold int
}

AcceptOptions represents Accept's options.

type CloseError

type CloseError struct {
	Code   StatusCode
	Reason string
}

CloseError is returned when the connection is closed with a status and reason.

Use Go 1.13's errors.As to check for this error. Also see the CloseStatus helper.

func (CloseError) Error

func (ce CloseError) Error() string

type CompressionMode

type CompressionMode int

CompressionMode represents the modes available to the deflate extension. See https://tools.ietf.org/html/rfc7692

A compatibility layer is implemented for the older deflate-frame extension used by safari. See https://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06 It will work the same in every way except that we cannot signal to the peer we want to use no context takeover on our side, we can only signal that they should. It is however currently disabled due to Safari bugs. See https://github.com/nhooyr/websocket/issues/218

const (
	// CompressionNoContextTakeover grabs a new flate.Reader and flate.Writer as needed
	// for every message. This applies to both server and client side.
	//
	// This means less efficient compression as the sliding window from previous messages
	// will not be used but the memory overhead will be lower if the connections
	// are long lived and seldom used.
	//
	// The message will only be compressed if greater than 512 bytes.
	CompressionNoContextTakeover CompressionMode = iota

	// CompressionContextTakeover uses a flate.Reader and flate.Writer per connection.
	// This enables reusing the sliding window from previous messages.
	// As most WebSocket protocols are repetitive, this can be very efficient.
	// It carries an overhead of 8 kB for every connection compared to CompressionNoContextTakeover.
	//
	// If the peer negotiates NoContextTakeover on the client or server side, it will be
	// used instead as this is required by the RFC.
	CompressionContextTakeover

	// CompressionDisabled disables the deflate extension.
	//
	// Use this if you are using a predominantly binary protocol with very
	// little duplication in between messages or CPU and memory are more
	// important than bandwidth.
	CompressionDisabled
)

type Conn

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

Conn provides a wrapper around the browser WebSocket API.

func Accept

func Accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn, error)

Accept is stubbed out for Wasm.

Example
package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"intercom/websocket"
	"intercom/websocket/wsjson"
)

func main() {
	// This handler accepts a WebSocket connection, reads a single JSON
	// message from the client and then closes the connection.

	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c, err := websocket.Accept(w, r, nil)
		if err != nil {
			log.Println(err)
			return
		}
		defer c.Close(websocket.StatusInternalError, "the sky is falling")

		ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
		defer cancel()

		var v interface{}
		err = wsjson.Read(ctx, c, &v)
		if err != nil {
			log.Println(err)
			return
		}

		c.Close(websocket.StatusNormalClosure, "")
	})

	err := http.ListenAndServe("localhost:8080", fn)
	log.Fatal(err)
}
Output:

func Dial

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

Dial creates a new WebSocket connection to the given url with the given options. The passed context bounds the maximum time spent waiting for the connection to open. The returned *http.Response is always nil or a mock. It's only in the signature to match the core API.

Example
package main

import (
	"context"
	"log"
	"time"

	"intercom/websocket"
	"intercom/websocket/wsjson"
)

func main() {
	// Dials a server, writes a single JSON message and then
	// closes the connection.

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close(websocket.StatusInternalError, "the sky is falling")

	err = wsjson.Write(ctx, c, "hi")
	if err != nil {
		log.Fatal(err)
	}

	c.Close(websocket.StatusNormalClosure, "")
}
Output:

func (*Conn) Close

func (c *Conn) Close(code StatusCode, reason string) error

Close closes the WebSocket with the given code and reason. It will wait until the peer responds with a close frame or the connection is closed. It thus performs the full WebSocket close handshake.

func (*Conn) CloseRead

func (c *Conn) CloseRead(ctx context.Context) context.Context

CloseRead implements *Conn.CloseRead for wasm.

func (*Conn) Ping

func (c *Conn) Ping(ctx context.Context) error

Ping is mocked out for Wasm.

func (*Conn) Read

func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error)

Read attempts to read a message from the connection. The maximum time spent waiting is bounded by the context.

func (*Conn) Reader

func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error)

Reader attempts to read a message from the connection. The maximum time spent waiting is bounded by the context.

func (*Conn) SetReadLimit

func (c *Conn) SetReadLimit(n int64)

SetReadLimit implements *Conn.SetReadLimit for wasm.

func (*Conn) Subprotocol

func (c *Conn) Subprotocol() string

Subprotocol returns the negotiated subprotocol. An empty string means the default protocol.

func (*Conn) Write

func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error

Write writes a message of the given type to the connection. Always non blocking.

func (*Conn) Writer

func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, error)

Writer returns a writer to write a WebSocket data message to the connection. It buffers the entire message in memory and then sends it when the writer is closed.

type DialOptions

type DialOptions struct {
	// Subprotocols lists the subprotocols to negotiate with the server.
	Subprotocols []string
}

DialOptions represents the options available to pass to Dial.

type MessageType

type MessageType int

MessageType represents the type of a WebSocket message. See https://tools.ietf.org/html/rfc6455#section-5.6

const (
	// MessageText is for UTF-8 encoded text messages like JSON.
	MessageText MessageType = iota + 1
	// MessageBinary is for binary messages like protobufs.
	MessageBinary
)

MessageType constants.

func (MessageType) String

func (i MessageType) String() string

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

	// StatusNoStatusRcvd cannot be sent in a close message.
	// It is reserved for when a close message is received without
	// a status code.
	StatusNoStatusRcvd StatusCode = 1005

	// StatusAbnormalClosure is exported for use only with Wasm.
	// In non Wasm Go, the returned error will indicate whether the
	// connection was closed abnormally.
	StatusAbnormalClosure StatusCode = 1006

	StatusInvalidFramePayloadData StatusCode = 1007
	StatusPolicyViolation         StatusCode = 1008
	StatusMessageTooBig           StatusCode = 1009
	StatusMandatoryExtension      StatusCode = 1010
	StatusInternalError           StatusCode = 1011
	StatusServiceRestart          StatusCode = 1012
	StatusTryAgainLater           StatusCode = 1013
	StatusBadGateway              StatusCode = 1014

	// StatusTLSHandshake is only exported for use with Wasm.
	// In non Wasm Go, the returned error will indicate whether there was
	// a TLS handshake failure.
	StatusTLSHandshake StatusCode = 1015
)

https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number

These are only the status codes defined by the protocol.

You can define custom codes in the 3000-4999 range. The 3000-3999 range is reserved for use by libraries, frameworks and applications. The 4000-4999 range is reserved for private use.

func CloseStatus

func CloseStatus(err error) StatusCode

CloseStatus is a convenience wrapper around Go 1.13's errors.As to grab the status code from a CloseError.

-1 will be returned if the passed error is nil or not a CloseError.

Example
package main

import (
	"context"
	"log"
	"time"

	"intercom/websocket"
)

func main() {
	// Dials a server and then expects to be disconnected with status code
	// websocket.StatusNormalClosure.

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close(websocket.StatusInternalError, "the sky is falling")

	_, _, err = c.Reader(ctx)
	if websocket.CloseStatus(err) != websocket.StatusNormalClosure {
		log.Fatalf("expected to be disconnected with StatusNormalClosure but got: %v", err)
	}
}
Output:

func (StatusCode) String

func (i StatusCode) String() string

Directories

Path Synopsis
examples
internal
test
Package test contains subpackages only used in tests.
Package test contains subpackages only used in tests.
wsjs
Package wsjs implements typed access to the browser javascript WebSocket API.
Package wsjs implements typed access to the browser javascript WebSocket API.
Package wsjson provides helpers for reading and writing JSON messages.
Package wsjson provides helpers for reading and writing JSON messages.
Package wspb provides helpers for reading and writing protobuf messages.
Package wspb provides helpers for reading and writing protobuf messages.

Jump to

Keyboard shortcuts

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