mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
Two things, both closing a gap between what walk.go states and what it checks. A HANDLER MAY NEVER TERMINATE A REQUEST. Enforced now. The inert check only ever asked something strictly weaker — that a definition carrying middleware has routes beneath it — which app.Use(zip.Static(assets)) passes on any app with a route, and its depth-0 skip meant the app being SERVED was never asked at all. That is exactly where a wiring file writes it, and the static handler then ran before every route in the composition and answered each one with a file, or a 404 where no file existed. walk.go's closing comment said so plainly: the rule condemned no form anyone writes. Terminality is now a property the walk can read. A leaf constructor returns its handler through zip.Terminal — exported, so wsx.Upgrade and third-party leaves come through the same door — and the identity is the closure's code pointer, never a name match. AdaptNetHTTP and AdaptNetHTTPMiddleware return closures with the same body from the same file, and only their authors can know which one answers. The handler comes back unwrapped, so registering a leaf at an address is byte for byte the registration it always was. The depth-0 exemption stays where it belongs, in the inert check alone: root middleware is router middleware and covers requests that match nothing, so it is never inert. ONE INTERPRETER. routesUnder re-walked App.entries per definition with a cycle guard of its own — a second authority that could disagree with the walk about the program it was asked about, and that read a definition's LIVE entry list rather than the one the generation was built from. It is now one pass over the occurrence slice, because a trail cell carries its *App, which makes an occurrence's breadcrumb its ancestor chain; a cyclic composition never reaches a reducer, so there is no guard to need. materialise reads its Handler through the third accessor the other two always documented, instead of asserting the payload. The two AST guards in binding_test.go become one that is strictly stronger: function granularity rather than file, bare type assertions as well as switches, and every allow-listed site must still be HIT — so deleting or renaming one fails the test rather than leaving it quietly vacuous. derived() already owns operation-id collisions; nothing here touches it. Co-authored-by: Hanzo Dev <[email protected]>
75 lines
2.2 KiB
Go
75 lines
2.2 KiB
Go
// Package wsx provides Fiber-v3-compatible WebSocket support via
|
|
// fasthttp/websocket. The public surface is intentionally small:
|
|
//
|
|
// app.Get("/ws", wsx.Upgrade(func(c *wsx.Conn) error {
|
|
// for {
|
|
// _, msg, err := c.ReadMessage()
|
|
// if err != nil { return err }
|
|
// _ = c.WriteMessage(wsx.TextMessage, msg)
|
|
// }
|
|
// }))
|
|
package wsx
|
|
|
|
import (
|
|
"github.com/fasthttp/websocket"
|
|
"github.com/valyala/fasthttp"
|
|
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
// Conn is the WebSocket connection passed to wsx handlers.
|
|
type Conn = websocket.Conn
|
|
|
|
// Message-type constants re-exported for convenience.
|
|
const (
|
|
TextMessage = websocket.TextMessage
|
|
BinaryMessage = websocket.BinaryMessage
|
|
CloseMessage = websocket.CloseMessage
|
|
PingMessage = websocket.PingMessage
|
|
PongMessage = websocket.PongMessage
|
|
)
|
|
|
|
// Handler is the wsx handler signature.
|
|
type Handler func(c *Conn) error
|
|
|
|
// Config configures the WebSocket upgrade.
|
|
type Config struct {
|
|
ReadBufferSize int
|
|
WriteBufferSize int
|
|
Subprotocols []string
|
|
EnableCompression bool
|
|
// CheckOrigin defaults to allow-all (zip is multi-tenant; gate via
|
|
// CORS or auth middleware at the route level instead).
|
|
CheckOrigin func(ctx *fasthttp.RequestCtx) bool
|
|
}
|
|
|
|
// Upgrade returns a zip.Handler that upgrades the HTTP connection to a
|
|
// WebSocket and calls fn with the established *Conn.
|
|
func Upgrade(fn Handler, opts ...Config) zip.Handler {
|
|
var cfg Config
|
|
if len(opts) > 0 {
|
|
cfg = opts[0]
|
|
}
|
|
up := &websocket.FastHTTPUpgrader{
|
|
ReadBufferSize: cfg.ReadBufferSize,
|
|
WriteBufferSize: cfg.WriteBufferSize,
|
|
Subprotocols: cfg.Subprotocols,
|
|
EnableCompression: cfg.EnableCompression,
|
|
}
|
|
if cfg.CheckOrigin != nil {
|
|
up.CheckOrigin = cfg.CheckOrigin
|
|
} else {
|
|
up.CheckOrigin = func(ctx *fasthttp.RequestCtx) bool { return true }
|
|
}
|
|
// TERMINAL: the upgrade takes over the connection, so this answers the
|
|
// address it is registered at and never yields to anything after it.
|
|
// zip.Terminal is what makes app.Use(wsx.Upgrade(…)) a build error instead
|
|
// of a server that tries to upgrade every request it receives.
|
|
return zip.Terminal("wsx.Upgrade", func(c *zip.Ctx) error {
|
|
rc := c.Fiber().RequestCtx()
|
|
return up.Upgrade(rc, func(ws *Conn) {
|
|
_ = fn(ws)
|
|
})
|
|
})
|
|
}
|