Documentation
¶
Overview ¶
Package websocket serves WebSocket connections for Vukem apps on top of github.com/coder/websocket, with secure defaults: same-origin upgrades only (plus an explicit allowlist), a 32 KiB read limit, ping/pong keepalive that drops dead peers, per-message write timeouts and a 1001 "going away" close when the app shuts down.
Inside a handler, Handle reads like c.WebSocket(fn):
r.Get("/chat", func(c *web.Ctx) error {
return websocket.Handle(c, func(conn *websocket.Conn) error {
msg, err := conn.ReadText(conn.Context())
...
})
})
or, for a route that only upgrades, r.Get("/chat", websocket.Handler(chat)). Broadcast serves package broadcast channels over the same connection type.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/frontend.md.
Index ¶
- Constants
- Variables
- func Broadcast(srv *broadcast.Server, opts ...Option) web.HandlerFunc
- func Handle(c *web.Ctx, fn func(*Conn) error, opts ...Option) error
- func Handler(fn func(*Conn) error, opts ...Option) web.HandlerFunc
- func Serve(w http.ResponseWriter, r *http.Request, fn func(*Conn) error, opts ...Option) error
- type Conn
- func (c *Conn) Close(code StatusCode, reason string) error
- func (c *Conn) CloseRead(ctx context.Context) context.Context
- func (c *Conn) Context() context.Context
- func (c *Conn) ReadJSON(ctx context.Context, v any) error
- func (c *Conn) ReadText(ctx context.Context) (string, error)
- func (c *Conn) Subprotocol() string
- func (c *Conn) Unwrap() *websocket.Conn
- func (c *Conn) WriteJSON(ctx context.Context, v any) error
- func (c *Conn) WriteText(ctx context.Context, s string) error
- type Frame
- type Option
- type Request
- type StatusCode
Examples ¶
Constants ¶
const ( TypeSubscribe = "subscribe" TypeUnsubscribe = "unsubscribe" TypeSubscribed = "subscribed" TypeUnsubscribed = "unsubscribed" TypeEvent = "event" TypeHere = "here" TypeJoining = "joining" TypeLeaving = "leaving" TypeError = "error" )
Broadcast protocol message types. Clients send subscribe and unsubscribe; the server sends the others.
const ( DefaultReadLimit = 32 << 10 DefaultPingInterval = 30 * time.Second DefaultPingTimeout = 10 * time.Second DefaultWriteTimeout = 10 * time.Second )
Defaults applied when no Option overrides them.
const ( StatusNormalClosure = websocket.StatusNormalClosure StatusGoingAway = websocket.StatusGoingAway StatusPolicyViolation = websocket.StatusPolicyViolation StatusMessageTooBig = websocket.StatusMessageTooBig StatusInternalError = websocket.StatusInternalError )
Close status codes used by this package.
Variables ¶
var ErrNotText = errors.New("websocket: binary message, want text")
ErrNotText is returned by ReadText and ReadJSON for binary messages.
Functions ¶
func Broadcast ¶
func Broadcast(srv *broadcast.Server, opts ...Option) web.HandlerFunc
Broadcast returns a route handler serving the broadcast WebSocket protocol for srv: every subscribe is authorized by srv (fail closed) in the context of the upgrade request, so mount it behind the auth middleware that identifies the user.
r.Get("/broadcast/ws", websocket.Broadcast(b))
func Handle ¶
Handle upgrades the request of c and runs fn with the connection; it is the Vukem counterpart of c.SSE for WebSockets. A refused upgrade (bad origin, not a WebSocket request) returns an HTTP error with the status already sent to the client.
Example ¶
Handle upgrades from inside a regular handler, after any checks it needs. Cross-origin upgrades are refused unless their origin is allowlisted.
package main
import (
"github.com/AlexGalhardo/vukem/drivers/websocket"
"github.com/AlexGalhardo/vukem/router"
"github.com/AlexGalhardo/vukem/web"
)
func main() {
r := router.New(nil)
r.Get("/chat", func(c *web.Ctx) error {
return websocket.Handle(c, func(conn *websocket.Conn) error {
var msg struct {
Text string `json:"text"`
}
if err := conn.ReadJSON(conn.Context(), &msg); err != nil {
return err
}
return conn.WriteJSON(conn.Context(), map[string]string{"text": msg.Text})
}, websocket.AllowedOrigins("https://app.example.com"))
})
}
Output:
func Handler ¶
func Handler(fn func(*Conn) error, opts ...Option) web.HandlerFunc
Handler returns a route handler that upgrades the request and runs fn.
Example ¶
An echo endpoint. The handler returns when the client closes the connection or the app shuts down.
package main
import (
"context"
"fmt"
"net/http/httptest"
"strings"
cws "github.com/coder/websocket"
"github.com/AlexGalhardo/vukem/drivers/websocket"
"github.com/AlexGalhardo/vukem/router"
)
func main() {
r := router.New(nil)
r.Get("/ws", websocket.Handler(func(conn *websocket.Conn) error {
ctx := conn.Context()
for {
msg, err := conn.ReadText(ctx)
if websocket.CloseStatus(err) == websocket.StatusNormalClosure {
return nil
} else if err != nil {
return err
}
if err := conn.WriteText(ctx, "echo: "+msg); err != nil {
return err
}
}
}))
srv := httptest.NewServer(r)
defer srv.Close()
ctx := context.Background()
c, resp, err := cws.Dial(ctx, "ws"+strings.TrimPrefix(srv.URL, "http")+"/ws", nil)
if err != nil {
panic(err)
}
if resp.Body != nil {
_ = resp.Body.Close()
}
defer c.CloseNow()
if err := c.Write(ctx, cws.MessageText, []byte("hi")); err != nil {
panic(err)
}
_, got, err := c.Read(ctx)
if err != nil {
panic(err)
}
fmt.Println(string(got))
_ = c.Close(cws.StatusNormalClosure, "")
}
Output: echo: hi
func Serve ¶
Serve upgrades r and runs fn until it returns, the client goes away, the peer stops answering pings or the app shuts down; Conn.Context is then done. The connection is closed when Serve returns: StatusNormalClosure when fn returns nil, StatusInternalError when it fails, StatusGoingAway on shutdown.
Types ¶
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is an accepted WebSocket connection. Reads and writes take a context (usually Context()); one reader and one writer may run concurrently.
func (*Conn) Close ¶
func (c *Conn) Close(code StatusCode, reason string) error
Close performs the closing handshake with code and reason. Closing again (or after Serve closed the connection) is a no-op.
func (*Conn) CloseRead ¶
CloseRead discards incoming messages for handlers that only write, so pongs and close frames are still processed; the returned context is done when the connection closes. A data message from the client closes the connection with StatusPolicyViolation.
func (*Conn) Context ¶
Context is done when the client goes away, the handler returns, the peer stops answering pings or the app shuts down.
func (*Conn) Subprotocol ¶
Subprotocol returns the negotiated subprotocol ("" when none).
func (*Conn) Unwrap ¶
Unwrap returns the underlying coder/websocket connection for features this wrapper does not cover (binary messages, streaming readers).
type Frame ¶
type Frame struct {
Type string `json:"type"`
Channel string `json:"channel,omitempty"`
Event string `json:"event,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
Status int `json:"status,omitempty"`
Message string `json:"message,omitempty"`
}
Frame is a server message of the broadcast protocol. Event frames carry Event and Data; here carries the member list, joining/leaving one member; error carries Status (400, 403) and Message.
type Option ¶
type Option func(*options)
Option configures an upgrade.
func AllowedOrigins ¶
AllowedOrigins authorizes cross-origin upgrades from browsers whose Origin host matches one of patterns (path.Match syntax, case-insensitive; a pattern with "://" matches "scheme://host"). The request's own host is always allowed. "*" is refused: use InsecureSkipOriginCheck to say so.
func InsecureSkipOriginCheck ¶
func InsecureSkipOriginCheck() Option
InsecureSkipOriginCheck accepts upgrades from any origin, which lets any website open a socket with the user's cookies (cross-site WebSocket hijacking). Only for endpoints that authenticate without cookies.
func PingInterval ¶
PingInterval sets how often the server pings the client (zero or negative disables keepalive).
func PingTimeout ¶
PingTimeout sets how long a ping waits for its pong before the peer is considered dead and the connection is dropped.
func ReadLimit ¶
ReadLimit caps the size of one incoming message in bytes; a bigger message closes the connection with StatusMessageTooBig.
func Subprotocols ¶
Subprotocols lists the subprotocols the server negotiates, in preference order; Conn.Subprotocol reports the chosen one ("" when none matched).
func WriteTimeout ¶
WriteTimeout bounds every write; a write that times out closes the connection.
type Request ¶
Request is a client message of the broadcast protocol: {"type":"subscribe","channel":"private-user.42"}.
type StatusCode ¶
type StatusCode = websocket.StatusCode
StatusCode is a WebSocket close status code (RFC 6455, section 7.4).
func CloseStatus ¶
func CloseStatus(err error) StatusCode
CloseStatus returns the close code carried by err, or -1 when err did not come from a close frame.