mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
zip.Mount is a proxy, not a mount — the name now says which of the two it is
`Mount` meant two unrelated things across the fleet at once: zip/remote.go func Mount(prefix, addr string, decl ...Declaration) (*App, error) 112 subsystems func Mount(app cloud.Router, deps cloud.Deps) error The second is the fleet's registration contract, stated in hanzoai/cloud's package doc and checked by cloud.MountFunc. The first is a leftover: Mount was one of five composition verbs (Listen/Mount/Add/Graft/Use), the other four were dissolved into Use, and this one survived by being the only one that pointed at another process. A reader seeing `Mount` in a diff could not tell which was meant without counting arguments. Renamed to Proxy, which is what it BUILDS. Every route it registers runs the one handler this file calls `proxy`, whose whole body is forward() to the address given; "proxy" is the term of art for a local stand-in whose handlers hand the request to someone else, so it needs no gloss. It is also a noun, which a function returning a value should be — composition is Use's job now, and the doc has said so since the four verbs were dissolved. NOT Remote: [Remote] already exists here and is the CALLER's side, a client executing commands against a service elsewhere. This is the SERVER's side, a definition standing in for that service inside this program's routing table. Two sides of one wire are two things. Also renamed the tests that named it, and mount_test.go -> adapt_net_http_test.go with it: those pin AdaptNetHTTP, a THIRD sense of "mount", and TestMount_* named none of the three. Regression set empty: TestServePathAllocsAreChainDepthInvariant fails identically on the parent commit (an allocation bound, untouched here). Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -19,7 +19,7 @@ import (
|
||||
//
|
||||
// app.Group("/legacy").All("/*", zip.AdaptNetHTTP(httpHandler))
|
||||
//
|
||||
// Group is what carries the prefix; Mount does not do this. Mount delegates a
|
||||
// Group is what carries the prefix; Proxy does not do this. Proxy delegates a
|
||||
// prefix to a REMOTE address over a transport, so it takes a string, not a
|
||||
// handler. A local handler is composed, not delegated.
|
||||
//
|
||||
|
||||
@@ -51,9 +51,9 @@ func echoHandler() http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// TestMount_NewForm_Serves proves the recommended form —
|
||||
// TestAdaptNetHTTP_NewForm_Serves proves the recommended form —
|
||||
// app.All(prefix+"/*", zip.AdaptNetHTTP(h)) — serves the mounted subtree.
|
||||
func TestMount_NewForm_Serves(t *testing.T) {
|
||||
func TestAdaptNetHTTP_NewForm_Serves(t *testing.T) {
|
||||
app := zip.New(zip.Config{DisableStartupMessage: true})
|
||||
app.All("/api/*", zip.AdaptNetHTTP(echoHandler()))
|
||||
|
||||
@@ -66,12 +66,12 @@ func TestMount_NewForm_Serves(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMount_StaticBeatsMount is the load-bearing proof: a static route
|
||||
// TestAdaptNetHTTP_StaticRouteWins is the load-bearing proof: a static route
|
||||
// registered AFTER the wildcard mount still wins for its exact path, while
|
||||
// every other subpath falls through to the mount. This only holds because
|
||||
// the mount is an ordinary route participating in specificity precedence —
|
||||
// registration order is inverted on purpose.
|
||||
func TestMount_StaticBeatsMount(t *testing.T) {
|
||||
func TestAdaptNetHTTP_StaticRouteWins(t *testing.T) {
|
||||
app := zip.New(zip.Config{DisableStartupMessage: true})
|
||||
|
||||
// Wildcard mount FIRST, static route SECOND. Specificity, not order,
|
||||
@@ -91,9 +91,9 @@ func TestMount_StaticBeatsMount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMount_BodyAndPathIntact proves the adapted handler receives the request
|
||||
// TestAdaptNetHTTP_BodyAndPathIntact proves the adapted handler receives the request
|
||||
// body and the full subpath + query unchanged through the wildcard mount.
|
||||
func TestMount_BodyAndPathIntact(t *testing.T) {
|
||||
func TestAdaptNetHTTP_BodyAndPathIntact(t *testing.T) {
|
||||
app := zip.New(zip.Config{DisableStartupMessage: true})
|
||||
app.All("/api/*", zip.AdaptNetHTTP(echoHandler()))
|
||||
|
||||
@@ -187,7 +187,7 @@ type Conn struct {
|
||||
}
|
||||
|
||||
// Dial returns a Conn to the app at addr. The address scheme selects the
|
||||
// transport exactly as it does for [App.Listen] and [Mount] — one registry,
|
||||
// transport exactly as it does for [App.Listen] and [Proxy] — one registry,
|
||||
// one vocabulary — so a bare path is ZAP over a unix socket and a bare host:port
|
||||
// is ZAP over tcp:
|
||||
//
|
||||
@@ -286,7 +286,7 @@ func Call[In, Out any](ctx context.Context, c *Conn, op string, in *In) (*Out, e
|
||||
forwardIdentity(ctx, req)
|
||||
|
||||
if err := c.client.Do(req, resp); err != nil {
|
||||
// The far end, not this hop, is what failed — same reading Mount gives a
|
||||
// The far end, not this hop, is what failed — same reading Proxy gives a
|
||||
// broken upstream.
|
||||
return nil, Errorf(502, "zip: call %s at %s: %v", op, c.addr, err)
|
||||
}
|
||||
|
||||
+6
-6
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Two definitions cannot own one address. That rule used to live in a private
|
||||
// prefix-claim ledger (App.claim/release) that only Mount and Load consulted;
|
||||
// prefix-claim ledger (App.claim/release) that only Proxy and Load consulted;
|
||||
// it now lives where every OTHER address collision is already decided — the
|
||||
// walk, at build. One mechanism, so there is no second answer to disagree with
|
||||
// the first, and the report is strictly better: every collision at once, with
|
||||
@@ -71,19 +71,19 @@ func TestARefusedCompositionIsRolledBack(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mount and Load are the same kind of thing now — definition constructors — so
|
||||
// Proxy and Load are the same kind of thing now — definition constructors — so
|
||||
// they collide with each other exactly as two Loads do. There is no separate
|
||||
// register for either.
|
||||
func TestMountAndLoadCollideThroughOneMechanism(t *testing.T) {
|
||||
remote, err := zip.Mount("/v1/y", "/run/zip/y.sock")
|
||||
func TestProxyAndLoadCollideThroughOneMechanism(t *testing.T) {
|
||||
remote, err := zip.Proxy("/v1/y", "/run/zip/y.sock")
|
||||
if err != nil {
|
||||
t.Fatalf("Mount: %v", err)
|
||||
t.Fatalf("Proxy: %v", err)
|
||||
}
|
||||
app := zip.New(zip.Config{AppName: "host", DisableStartupMessage: true})
|
||||
app.Use(remote, plugin(t, "z", "/v1/y"))
|
||||
|
||||
if err := app.Build(); err == nil {
|
||||
t.Fatal("a Mount and a Load claiming /v1/y were both accepted")
|
||||
t.Fatal("a Proxy and a Load claiming /v1/y were both accepted")
|
||||
} else if !strings.Contains(err.Error(), "/v1/y") {
|
||||
t.Errorf("refusal does not name the address: %v", err)
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ func LocalInvoke(ctx context.Context, c Command, path map[string]string, body []
|
||||
|
||||
// Remote executes a command against a running zip service, and reads that
|
||||
// service's registry off the document it derives from it. Base is an address in
|
||||
// the same form Mount takes — the scheme selects the transport, so a command
|
||||
// the same form Proxy takes — the scheme selects the transport, so a command
|
||||
// runs over ZAP, HTTP or anything else RegisterTransport'd without the CLI
|
||||
// knowing which.
|
||||
type Remote struct {
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import (
|
||||
// REFERENCE to a definition. At build time one interpreter walks one program;
|
||||
// the child's entries are read in place, exactly as if they had been written
|
||||
// there. There is no boundary at run time to cross, so there is nothing for a
|
||||
// request to hop through: those words are reserved for [Mount], the one
|
||||
// request to hop through: those words are reserved for [Proxy], the one
|
||||
// operation where another runtime genuinely exists.
|
||||
//
|
||||
// The same definition may be referenced from any number of places. Each
|
||||
|
||||
@@ -5,7 +5,7 @@ import "github.com/zap-proto/zip"
|
||||
// must unwraps a definition constructor's (*App, error) so it can be composed
|
||||
// inline: app.Use(must(zip.Load(...))).
|
||||
//
|
||||
// zip.Load and zip.Mount return an error because BUILDING a definition can fail
|
||||
// zip.Load and zip.Proxy return an error because BUILDING a definition can fail
|
||||
// — a binary that will not start, a scheme with no transport. Use cannot fail,
|
||||
// because appending a reference to a program is not a fallible act. The error
|
||||
// belongs at construction, and composition stays one verb.
|
||||
|
||||
+1
-1
@@ -443,7 +443,7 @@ func TestFreeze_MutateAfterBuildPanics_IncludeAfterBuildSucceeds(t *testing.T) {
|
||||
child.Get("/sneak", func(c *Ctx) error { return nil })
|
||||
}()
|
||||
|
||||
// Mount-after-seal: allowed. A second host includes the same sealed
|
||||
// Proxy-after-seal: allowed. A second host includes the same sealed
|
||||
// definition, and gets its own occurrence of it.
|
||||
second := quiet("second")
|
||||
second.Group("/v2").Use(child)
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ type Route struct {
|
||||
Pattern string `json:"pattern"`
|
||||
|
||||
// Op is the operation id this pattern answers under, empty when the route is
|
||||
// untyped. It is what makes a remote [Mount] able to contribute OPS and
|
||||
// untyped. It is what makes a remote [Proxy] able to contribute OPS and
|
||||
// not merely addresses: the mounting app is handed the declaration inline and
|
||||
// reads the ids out of it, instead of asking the remote what it serves.
|
||||
Op string `json:"op,omitempty"`
|
||||
|
||||
@@ -133,7 +133,7 @@ func (h *Host) Include(cs ...Component) error {
|
||||
// is referenced by the GROUP, so dropping it means dropping the group.
|
||||
//
|
||||
// Routing-level only. Go's plugin package has no Close and never unloads a .so,
|
||||
// so to reclaim memory run the subsystem out of process behind [Mount].
|
||||
// so to reclaim memory run the subsystem out of process behind [Proxy].
|
||||
func (h *Host) Drop(defs ...*App) error {
|
||||
site := here(1)
|
||||
return h.app.transact(site, func() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// A remote service is a LEAF, not a verb.
|
||||
//
|
||||
// [Mount] used to be one of five composition verbs and the only one that
|
||||
// [Proxy] used to be one of five composition verbs and the only one that
|
||||
// pointed at another process. Under one program it stops being special: it
|
||||
// appends an App like any other inclusion, whose routes proxy and whose ops
|
||||
// forward. Every projection then reads it through the same walk — a mounted
|
||||
@@ -35,13 +35,13 @@ import (
|
||||
// depend on what the network said at the moment it was asked.
|
||||
//
|
||||
// The spec is a build input; the server is not asked what it serves. Without a
|
||||
// declaration a mount contributes its two proxy addresses and no ops, which is
|
||||
// declaration a proxy contributes its two proxy addresses and no ops, which is
|
||||
// exactly what it contributed before and says honestly that nothing is known
|
||||
// about the shape behind it.
|
||||
// Mount returns another service, running elsewhere, as a DEFINITION the host
|
||||
// Proxy returns another service, running elsewhere, as a DEFINITION the host
|
||||
// composes with Use like any other:
|
||||
//
|
||||
// ledger, err := zip.Mount("/v1/ledger", "ledger.hanzo.svc:9653", decl)
|
||||
// ledger, err := zip.Proxy("/v1/ledger", "ledger.hanzo.svc:9653", decl)
|
||||
// if err != nil { return err }
|
||||
// app.Use(ledger)
|
||||
//
|
||||
@@ -51,6 +51,30 @@ import (
|
||||
// projection because it is in the registry, not because five projections each
|
||||
// learned what mounting meant.
|
||||
//
|
||||
// # Why the name changed
|
||||
//
|
||||
// This was Mount, and Mount already meant something else to everyone reading it:
|
||||
// 118 of the fleet's ~140 subsystems export
|
||||
// `func Mount(app cloud.Router, deps cloud.Deps) error`, the function by which an
|
||||
// app registers ITSELF. One name, two unrelated shapes — a caller who knows one
|
||||
// learns nothing about the other, and a reader seeing `Mount` in a diff cannot
|
||||
// tell which is meant without checking the arity.
|
||||
//
|
||||
// The verb was also wrong on its own terms, which is what makes the rename a
|
||||
// correction and not a coat of paint. The doc above already says composition
|
||||
// went to Use; a function that returns a value is a NOUN.
|
||||
//
|
||||
// Proxy is what it builds. Every route it registers runs the handler this file
|
||||
// calls `proxy`, and that handler's whole body is `forward(...)` to the address
|
||||
// given. The term is the term of art for a local stand-in whose handlers hand
|
||||
// the request to someone else, so it needs no gloss.
|
||||
//
|
||||
// Not Remote, which was the first choice and is wrong: [Remote] already exists
|
||||
// here, and correctly — it is the CALLER's side, a client that executes commands
|
||||
// against a service running elsewhere. This is the SERVER's side: a definition
|
||||
// that stands in for that service inside this program's own routing table. Two
|
||||
// sides of one wire are two things, and the compiler said so.
|
||||
//
|
||||
// The address scheme selects the transport exactly as [App.Listen]'s does, so a
|
||||
// bare address is ZAP and one registry serves both directions.
|
||||
//
|
||||
@@ -58,7 +82,7 @@ import (
|
||||
// contributes its two proxy addresses and no ops, which honestly says nothing
|
||||
// is known about the shape behind it. With it, the remote's ops join the
|
||||
// document, the tool list, the commands and the call plane.
|
||||
func Mount(prefix, addr string, decl ...Declaration) (*App, error) {
|
||||
func Proxy(prefix, addr string, decl ...Declaration) (*App, error) {
|
||||
var d Declaration
|
||||
if len(decl) > 0 {
|
||||
d = decl[0]
|
||||
@@ -85,7 +109,7 @@ func remoteApp(parent *App, prefix, addr string, d Declaration) (*App, error) {
|
||||
}
|
||||
if len(d.Routes) == 0 {
|
||||
// Nothing declared: the prefix and everything under it, which is what a
|
||||
// bare Mount has always registered.
|
||||
// bare Proxy has always registered.
|
||||
p := trimPrefix(prefix)
|
||||
r.All(p, proxy)
|
||||
r.All(p+"/*", proxy)
|
||||
@@ -100,7 +124,7 @@ func remoteApp(parent *App, prefix, addr string, d Declaration) (*App, error) {
|
||||
// program poisoned. Validate at the boundary the input crosses, so a bad
|
||||
// declaration is an error like every other bad declaration.
|
||||
if !knownMethod(rt.Method) {
|
||||
return nil, Errorf(400, "zip: Mount(%s): %q is not an HTTP method zip can route (route %q)",
|
||||
return nil, Errorf(400, "zip: Proxy(%s): %q is not an HTTP method zip can route (route %q)",
|
||||
prefix, rt.Method, rt.Pattern)
|
||||
}
|
||||
if rt.Op == "" {
|
||||
|
||||
+6
-6
@@ -48,7 +48,7 @@ func remoteService(t *testing.T) (string, Declaration) {
|
||||
return sock, svc.Declaration()
|
||||
}
|
||||
|
||||
// TestMount_DeclarationIsAnInputNotAFetch is the rule the whole design hangs
|
||||
// TestProxy_DeclarationIsAnInputNotAFetch is the rule the whole design hangs
|
||||
// on: building a program does no I/O. The address here goes nowhere, and the
|
||||
// registry, the document and the tool list are still complete — because the
|
||||
// declaration was handed in.
|
||||
@@ -56,10 +56,10 @@ func remoteService(t *testing.T) (string, Declaration) {
|
||||
// If the walk dialled instead, this test could not exist: Registry() would be
|
||||
// fallible and slow, and an OpenAPI document would depend on some other process
|
||||
// being up at boot.
|
||||
func TestMount_DeclarationIsAnInputNotAFetch(t *testing.T) {
|
||||
func TestProxy_DeclarationIsAnInputNotAFetch(t *testing.T) {
|
||||
host := quiet("cloud")
|
||||
dead := "/nonexistent/nothing-listens-here.sock"
|
||||
host.Use(mustApp(Mount("/v1/ledger", dead, Declaration{
|
||||
host.Use(mustApp(Proxy("/v1/ledger", dead, Declaration{
|
||||
Name: "ledger",
|
||||
Routes: []Route{
|
||||
{Method: "GET", Pattern: "/v1/ledger/entries/:id", Op: "getEntry"},
|
||||
@@ -89,9 +89,9 @@ func TestMount_DeclarationIsAnInputNotAFetch(t *testing.T) {
|
||||
// And nothing was dialled: an unreachable address built a complete program.
|
||||
}
|
||||
|
||||
// TestMount_ServesAndCallsTheRemote: the leaf's routes proxy and its ops
|
||||
// TestProxy_ServesAndCallsTheRemote: the leaf's routes proxy and its ops
|
||||
// forward, over the same transport, to a service that really is somewhere else.
|
||||
func TestMount_ServesAndCallsTheRemote(t *testing.T) {
|
||||
func TestProxy_ServesAndCallsTheRemote(t *testing.T) {
|
||||
sock, decl := remoteService(t)
|
||||
if len(decl.Routes) != 2 {
|
||||
t.Fatalf("the service declared %d routes, want 2: %+v", len(decl.Routes), decl.Routes)
|
||||
@@ -107,7 +107,7 @@ func TestMount_ServesAndCallsTheRemote(t *testing.T) {
|
||||
}
|
||||
|
||||
host := quiet("cloud")
|
||||
host.Use(mustApp(Mount("/v1/ledger", sock, decl)))
|
||||
host.Use(mustApp(Proxy("/v1/ledger", sock, decl)))
|
||||
if err := host.Build(); err != nil {
|
||||
t.Fatalf("Build: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// TestMount_OverUnixSocket proves the plumbing is read off the address shape
|
||||
// TestProxy_OverUnixSocket proves the plumbing is read off the address shape
|
||||
// rather than off a second scheme: the SAME "zap" transport serves and dials a
|
||||
// unix socket when the address is a path. This is the colocated plugin case —
|
||||
// no port to allocate, filesystem permissions as the ACL — carrying byte-identical
|
||||
// ZAP frames to the tcp case.
|
||||
func TestMount_OverUnixSocket(t *testing.T) {
|
||||
func TestProxy_OverUnixSocket(t *testing.T) {
|
||||
sock := filepath.Join(t.TempDir(), "billing.sock")
|
||||
|
||||
plugin := zip.New(zip.Config{AppName: "billing", DisableStartupMessage: true})
|
||||
@@ -28,7 +28,7 @@ func TestMount_OverUnixSocket(t *testing.T) {
|
||||
waitSocket(t, sock)
|
||||
|
||||
core := zip.New(zip.Config{AppName: "core", DisableStartupMessage: true})
|
||||
core.Use(must(zip.Mount("/v1/billing", sock)))
|
||||
core.Use(must(zip.Proxy("/v1/billing", sock)))
|
||||
|
||||
status, body := call(t, core, "GET", "/v1/billing/invoices", "")
|
||||
if status != 200 || !strings.Contains(body, `"servedBy":"over-uds"`) {
|
||||
@@ -49,17 +49,17 @@ func waitSocket(t *testing.T, path string) {
|
||||
t.Fatalf("unix socket %s never became reachable", path)
|
||||
}
|
||||
|
||||
// These tests pin the plugin mechanism: Listen serves here, Mount delegates
|
||||
// These tests pin the plugin mechanism: Listen serves here, Proxy delegates
|
||||
// there, and both read the transport off the address scheme through one
|
||||
// registry. The property that matters is that the mounting app holds no
|
||||
// reference whatsoever to the mounted app's code — that is what lets a service
|
||||
// ship, rebuild, and redeploy as its own binary without relinking its host.
|
||||
|
||||
// TestMount_OverZAP is the end-to-end proof. The "plugin" is a real, separate
|
||||
// TestProxy_OverZAP is the end-to-end proof. The "plugin" is a real, separate
|
||||
// zip.App with its own routes, listening on the real ZAP transport. The "core"
|
||||
// mounts it by address alone — a bare address, so DefaultScheme (ZAP) — and
|
||||
// serves its routes as if they were local. Nothing is shared but the address.
|
||||
func TestMount_OverZAP(t *testing.T) {
|
||||
func TestProxy_OverZAP(t *testing.T) {
|
||||
plugin := zip.New(zip.Config{AppName: "billing", DisableStartupMessage: true})
|
||||
plugin.Get("/v1/billing/invoices", func(c *zip.Ctx) error {
|
||||
return c.JSON(200, map[string]string{"servedBy": "billing-plugin"})
|
||||
@@ -74,7 +74,7 @@ func TestMount_OverZAP(t *testing.T) {
|
||||
waitReachable(t, pluginAddr)
|
||||
|
||||
core := zip.New(zip.Config{AppName: "core", DisableStartupMessage: true})
|
||||
core.Use(must(zip.Mount("/v1/billing", pluginAddr)))
|
||||
core.Use(must(zip.Proxy("/v1/billing", pluginAddr)))
|
||||
|
||||
// GET travels core -> ZAP -> plugin and the plugin's body comes back.
|
||||
status, body := call(t, core, "GET", "/v1/billing/invoices", "")
|
||||
@@ -96,11 +96,11 @@ func TestMount_OverZAP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMount_StaticBeatsMount pins the same precedence property the local mount
|
||||
// TestProxy_StaticBeatsRemoteProxy pins the same precedence property the local mount
|
||||
// path pins: a mount is an ORDINARY wildcard route, so a more specific route
|
||||
// registered afterwards still wins. A mount that short-circuited the router
|
||||
// would silently shadow the host's own routes.
|
||||
func TestMount_StaticBeatsRemoteMount(t *testing.T) {
|
||||
func TestProxy_StaticBeatsRemoteProxy(t *testing.T) {
|
||||
plugin := zip.New(zip.Config{AppName: "plug", DisableStartupMessage: true})
|
||||
plugin.Get("/v1/billing/*", func(c *zip.Ctx) error {
|
||||
return c.JSON(200, map[string]string{"servedBy": "plugin"})
|
||||
@@ -112,7 +112,7 @@ func TestMount_StaticBeatsRemoteMount(t *testing.T) {
|
||||
waitReachable(t, pluginAddr)
|
||||
|
||||
core := zip.New(zip.Config{AppName: "core", DisableStartupMessage: true})
|
||||
core.Use(must(zip.Mount("/v1/billing", pluginAddr)))
|
||||
core.Use(must(zip.Proxy("/v1/billing", pluginAddr)))
|
||||
// Registered AFTER the mount, and still wins for its exact path.
|
||||
core.Get("/v1/billing/health", func(c *zip.Ctx) error {
|
||||
return c.JSON(200, map[string]string{"servedBy": "core"})
|
||||
@@ -128,27 +128,27 @@ func TestMount_StaticBeatsRemoteMount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMount_UnknownScheme proves Mount refuses an unregistered scheme loudly
|
||||
// TestProxy_UnknownScheme proves Proxy refuses an unregistered scheme loudly
|
||||
// rather than silently falling back to a default wire.
|
||||
func TestMount_UnknownScheme(t *testing.T) {
|
||||
_, err := zip.Mount("/v1/x", "carrier-pigeon://somewhere:1")
|
||||
func TestProxy_UnknownScheme(t *testing.T) {
|
||||
_, err := zip.Proxy("/v1/x", "carrier-pigeon://somewhere:1")
|
||||
if err == nil {
|
||||
t.Fatal("Mount with an unregistered scheme returned nil, want an error")
|
||||
t.Fatal("Proxy with an unregistered scheme returned nil, want an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "carrier-pigeon") {
|
||||
t.Fatalf("error = %v, want it to name the unknown scheme", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMount_DialOnlyAndServeOnly proves a transport may implement one
|
||||
// direction: Listen reports a dial-only scheme and Mount reports a serve-only
|
||||
// TestProxy_DialOnlyAndServeOnly proves a transport may implement one
|
||||
// direction: Listen reports a dial-only scheme and Proxy reports a serve-only
|
||||
// one, each naming which half was missing.
|
||||
func TestMount_ServeOnlyTransportCannotDial(t *testing.T) {
|
||||
func TestProxy_ServeOnlyTransportCannotDial(t *testing.T) {
|
||||
zip.RegisterTransport("serveonly", zip.Transport{
|
||||
Serve: func(addr string, h fasthttp.RequestHandler) zip.Server { return nil },
|
||||
})
|
||||
_, err := zip.Mount("/v1/x", "serveonly://host:1")
|
||||
_, err := zip.Proxy("/v1/x", "serveonly://host:1")
|
||||
if err == nil || !strings.Contains(err.Error(), "cannot dial") {
|
||||
t.Fatalf("Mount on a serve-only transport: err=%v, want a 'cannot dial' error", err)
|
||||
t.Fatalf("Proxy on a serve-only transport: err=%v, want a 'cannot dial' error", err)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func WithStripPrefix(prefix string) StaticOption {
|
||||
return func(c *staticConfig) { c.stripPrefix = prefix }
|
||||
}
|
||||
|
||||
// Static returns a leaf Handler that serves files from fsys. Mount it on a
|
||||
// Static returns a leaf Handler that serves files from fsys. Register it on a
|
||||
// wildcard route; the "*" capture selects the file:
|
||||
//
|
||||
// app.Get("/assets/*", zip.Static(assets)) // embed.FS
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@ type Client interface {
|
||||
// Transport is one address scheme in both directions: Serve terminates bytes
|
||||
// arriving here, Dial originates bytes going there. They are the same concept
|
||||
// — a wire — so they live in one value rather than two registries that drift.
|
||||
// A scheme may leave either half nil; Listen and Mount each say which they
|
||||
// A scheme may leave either half nil; Listen and Proxy each say which they
|
||||
// needed.
|
||||
type Transport struct {
|
||||
Serve func(addr string, handler fasthttp.RequestHandler) Server
|
||||
@@ -76,7 +76,7 @@ var (
|
||||
},
|
||||
// Dial-only: terminating TLS is the ingress's job, so nothing in the
|
||||
// fleet serves https directly — but reaching something that does (an
|
||||
// api.* host, a third party) is an ordinary Mount, and a CLI talking to
|
||||
// api.* host, a third party) is an ordinary Proxy, and a CLI talking to
|
||||
// a published API is the same call. One registry entry, both directions
|
||||
// covered by the half that exists.
|
||||
"https": {
|
||||
@@ -97,8 +97,8 @@ func withPort(addr, port string) string {
|
||||
}
|
||||
|
||||
// RegisterTransport adds (or replaces) a transport keyed by address scheme, so
|
||||
// any future protocol slots into both Listen and Mount with ZERO change to
|
||||
// either API. Call before Listen or Mount.
|
||||
// any future protocol slots into both Listen and Proxy with ZERO change to
|
||||
// either API. Call before Listen or Proxy.
|
||||
//
|
||||
// zip.RegisterTransport("quic", zip.Transport{
|
||||
// Serve: func(addr string, h fasthttp.RequestHandler) zip.Server {
|
||||
@@ -239,7 +239,7 @@ func (a *App) listenOn(addrs []string) error {
|
||||
}
|
||||
|
||||
// mount registers prefix onto a dialled address. The prefix is already CLAIMED
|
||||
// by the caller — [Mount] claims its one, [App.load] claims a plugin's whole set
|
||||
// by the caller — [Proxy] claims its one, [App.load] claims a plugin's whole set
|
||||
// all-or-nothing before spawning anything — so this does not claim again and
|
||||
// there is exactly one claim per prefix however the composition came in.
|
||||
func (a *App) mount(prefix, addr string) error {
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// ops; nothing dedupes middleware, which is also why [Handler] being a
|
||||
// non-comparable func type costs nothing.
|
||||
// - PURE. No global state, no projection-specific logic, no I/O. In
|
||||
// particular a remote [Mount] contributes its declaration INLINE and is
|
||||
// particular a remote [Proxy] contributes its declaration INLINE and is
|
||||
// never asked over the network what it serves: a walk that did I/O would
|
||||
// make the document fallible, slow, untestable, and a function of whether
|
||||
// some other process happened to be up at boot.
|
||||
|
||||
Reference in New Issue
Block a user