Skip to main content
Version: v3_uptime_v0.x.x

Websocket

Release Discord Test

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โ€‹

PropertyTypeDescriptionDefault
Nextfunc(fiber.Ctx) boolDefines a function to skip this middleware when it returns true.nil
HandshakeTimeouttime.DurationBounds sending the 101 response, which fasthttp writes once the handler chain returns. The server's WriteTimeout, when set, applies instead.0 (No timeout)
Subprotocols[]stringSubprotocols this server supports, in order of preference. The first entry the client also offers is negotiated.nil
Origins[]stringAllowed Origins based on the Origin header, compared case-insensitively. If empty, everything is allowed.nil
AllowEmptyOriginboolAllows connections without an Origin header when Origins is configured. Useful for non-browser clients.false
ReadBufferSizeintReadBufferSize specifies the I/O buffer size in bytes for incoming messages.0 (Use default size)
WriteBufferSizeintWriteBufferSize specifies the I/O buffer size in bytes for outgoing messages.0 (Use default size)
WriteBufferPoolwebsocket.BufferPoolWriteBufferPool is a pool of buffers for write operations.nil
EnableCompressionboolEnableCompression specifies if the client should attempt to negotiate per message compression (RFC 7692).false
RecoverHandlerfunc(*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:

ReasonStatus
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 signals400 Bad Request
Request method is not GET405 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.