Websocket
Based on Fasthttp WebSocket for Fiber with available fiber.Ctx methods like Locals, Params, Query and Cookies.
For a plain WebSocket event-bus helper, use the event subpackage. It keeps ordinary WebSocket wire compatibility and is separate from the Socket.IO protocol implementation in github.com/gofiber/contrib/v3/socketio.
Compatible with Fiber v3.
Go version supportโ
We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information.
Installโ
go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/contrib/v3/websocket
Signaturesโ
func New(handler func(*websocket.Conn), config ...websocket.Config) fiber.Handler {
Configโ
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Defines a function to skip this middleware when it returns true. | nil |
| HandshakeTimeout | time.Duration | Bounds sending the 101 response, which fasthttp writes once the handler chain returns. The server's WriteTimeout, when set, applies instead. | 0 (No timeout) |
| Subprotocols | []string | Subprotocols this server supports, in order of preference. The first entry the client also offers is negotiated. | nil |
| Origins | []string | Allowed Origins based on the Origin header, compared case-insensitively. If empty, everything is allowed. | nil |
| AllowEmptyOrigin | bool | Allows connections without an Origin header when Origins is configured. Useful for non-browser clients. | false |
| ReadBufferSize | int | ReadBufferSize specifies the I/O buffer size in bytes for incoming messages. | 0 (Use default size) |
| WriteBufferSize | int | WriteBufferSize specifies the I/O buffer size in bytes for outgoing messages. | 0 (Use default size) |
| WriteBufferPool | websocket.BufferPool | WriteBufferPool is a pool of buffers for write operations. | nil |
| EnableCompression | bool | EnableCompression specifies if the client should attempt to negotiate per message compression (RFC 7692). | false |
| RecoverHandler | func(*websocket.Conn) | RecoverHandler is a panic handler function that recovers from panics. | defaultRecover |
Exampleโ
package main
import (
"log"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/contrib/v3/websocket"
)
func main() {
app := fiber.New()
app.Use("/ws", func(c fiber.Ctx) error {
// IsWebSocketUpgrade returns true if the client
// requested upgrade to the WebSocket protocol.
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
return c.Next()
}
return fiber.ErrUpgradeRequired
})
app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
// c.Locals is added to the *websocket.Conn
log.Println(c.Locals("allowed")) // true
log.Println(c.Params("id")) // 123
log.Println(c.Query("v")) // 1.0
log.Println(c.Cookies("session")) // ""
// websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index
var (
mt int
msg []byte
err error
)
for {
if mt, msg, err = c.ReadMessage(); err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", msg)
if err = c.WriteMessage(mt, msg); err != nil {
log.Println("write:", err)
break
}
}
}))
log.Fatal(app.Listen(":3000"))
// Access the websocket server: ws://localhost:3000/ws/123?v=1.0
// https://www.websocket.org/echo.html
}
Handshake rejectionsโ
A request that carries neither an Upgrade header nor an upgrade token in
Connection never asked to switch protocols and is answered with 426 Upgrade Required.
A request that does ask to upgrade but whose handshake is rejected is returned as a
*fiber.Error carrying the status RFC 6455 defines for that failure, so it reaches
the app's ErrorHandler like any other error and takes its body and format from
there:
| Reason | Status |
|---|---|
Origin not in Origins (RFC 6455 section 4.2.2) | 403 Forbidden |
Missing/blank Sec-WebSocket-Key, unsupported version, or only one of the Upgrade/Connection signals | 400 Bad Request |
Request method is not GET | 405 Method Not Allowed |
Rejected handshakes also carry Sec-WebSocket-Version: 13 so a client that asked for
another version learns which one the server speaks (RFC 6455 section 4.4). Headers set
by earlier middleware are left in place.
Reads and writesโ
c.ReadMessage() reads messages up to 64 KiB into a pooled buffer and returns an exact-size
copy; a larger message comes back in its own buffer, as from the library's ReadMessage.
Bound message size with SetReadLimit; for zero-copy reads use NextReader with your own
buffer.
Replies to a burst of pipelined frames leave in one write instead of one per frame: writes made while the handler still has unread frames wait until it asks for the next one, at most 64 KiB or 1 ms. Writes made while nothing is waiting to be read go out immediately, so push-only handlers are unaffected, and frames are never reordered. With 5-byte frames and 16 in flight per connection, server CPU per frame goes from 5.7 ยตs to 1.0 ยตs.
To own the connection the upgrade runs through the library's net/http Upgrader on a
fasthttp-backed hijack. fasthttp still sends the 101, so middleware running after c.Next()
sees the status and headers and can add its own, and Sec-WebSocket-Key is validated as
RFC 6455 requires. c.NetConn() returns the middleware's connection; its UnsafeConn()
is the socket underneath. Close sends what is pending, waiting at most 100 ms for a peer
that is not reading, and interrupts a write stalled on that peer.
Note with cache middlewareโ
If you get the error websocket: bad handshake when using the cache middleware, please use config.Next to skip websocket path.
app := fiber.New()
app.Use(cache.New(cache.Config{
Next: func(c fiber.Ctx) bool {
return strings.Contains(c.Route().Path, "/ws")
},
}))
app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {}))
Note with recover middlewareโ
For internal implementation reasons, currently recover middleware does not work with websocket middleware, please use config.RecoverHandler to add recover handler to websocket endpoints.
By default, config RecoverHandler recovers from panic, writes the value and stack trace to stderr, and sends the peer a fixed {"error":"internal error"}.
Nothing derived from the panic reaches the client: the operator already has the
full detail on stderr, and a panic value can carry internal paths, connection
strings or schema names. If you want the peer told more than that, supply your
own RecoverHandler โ it receives the *websocket.Conn and calls recover()
itself, so it can write whatever the application considers safe.
Once the recover handler returns, the connection is closed: a handler that panicked cannot be assumed to still own it, and nothing else closes a hijacked connection.
A handler that returns normally leaves the socket open, so it may hand the
connection to another goroutine before returning and keep writing after. The
*websocket.Conn it receives is allocated for that upgrade alone and never reused,
so Locals, Params, Query, Cookies, Headers and IP keep answering with
that connection's own data for as long as anything holds it.
app := fiber.New()
app.Use(cache.New(cache.Config{
Next: func(c fiber.Ctx) bool {
return strings.Contains(c.Route().Path, "/ws")
},
}))
cfg := Config{
RecoverHandler: func(conn *Conn) {
if err := recover(); err != nil {
conn.WriteJSON(fiber.Map{"customError": "error occurred"})
}
},
}
app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {}, cfg))
Note for WebSocket subprotocolsโ
The config Subprotocols only helps you negotiate subprotocols and sets a Sec-Websocket-Protocol header if it has a suitable subprotocol. For more about negotiates process, check the comment for Subprotocols in fasthttp.Upgrader .
All connections will be sent to the handler function no matter whether the subprotocol negotiation is successful or not. You can get the selected subprotocol from conn.Subprotocol().
If a connection includes the Sec-Websocket-Protocol header in the request but the protocol negotiation fails, the browser will immediately disconnect the connection after receiving the upgrade response.