Routing: use /v1/<svc> prefix, never /api/ (house convention)

The zip front router mounted service reverse-proxies under /api/<prefix>; renamed
to /v1/<prefix> per convention (we never use a /api/ path prefix). Upstream
service-internal /api/v1/... paths (behind the proxy) are unchanged.
This commit is contained in:
zooqueen
2026-06-23 21:26:39 -07:00
parent 7156dfdd7a
commit 72ef1420e9
7 changed files with 24 additions and 24 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ cgo, keeping their own HTTP servers on loopback.
Go-native binary, no cgo — `ffi_off.go` stub). Scope services with
`make single FFI_FEATURES=sig-provider,visualizer`.
- **Front router (`front.go`):** zip owns the public listener. Routes, in order:
`/api/<prefix>/*` → `httputil.ReverseProxy` → `127.0.0.1:<svc_port>`
`/v1/<prefix>/*` → `httputil.ReverseProxy` → `127.0.0.1:<svc_port>`
(prefix stripped); `/*` → the existing net/http explorer mux via
`zip.AdaptNetHTTP`. Security headers + CORS are zip middleware.
- **Config (`services:` in chains.yaml, `ServicesConfig`/`resolveServices`):**
@@ -72,7 +72,7 @@ cgo, keeping their own HTTP servers on loopback.
HTTP=base+2n/gRPC=+1), prefixes, and per-service settings JSON. The FFI
launcher (`ffi_on.go startFFIServices`) and the proxy both read it.
- **Status:** sig-provider WIRED + PROVEN end-to-end (one process owns :8090
and :8050; `curl /api/sig/health → {"status":"SERVING"}`). visualizer +
and :8050; `curl /v1/sig/health → {"status":"SERVING"}`). visualizer +
smart-contract-verifier are stateless and ready to wire (recipe in
`ffi/README.md`). stats + multichain-aggregator stay DISABLED:
`blockscout-service-launcher` DB layer is Postgres-only (no SQLite without a
+4 -4
View File
@@ -265,19 +265,19 @@ chains:
# In-process Blockscout-rs services (single-binary `-tags ffi` build only).
# Each runs as a Tokio thread inside the explorer process; the zip front router
# reverse-proxies /api/<prefix>/* -> http://127.0.0.1:<http_port>. In the
# reverse-proxies /v1/<prefix>/* -> http://127.0.0.1:<http_port>. In the
# default (non-FFI) build this whole block is inert. See ffi/README.md.
services:
enabled: false # master switch; true only in the `make single` binary
list:
# sig-provider — stateless 4byte/ABI signature lookup. WIRED + PROVEN.
- name: sig-provider
prefix: sig # -> /api/sig/* (e.g. /api/sig/health, /api/sig/api/v1/abi/function)
prefix: sig # -> /v1/sig/* (e.g. /v1/sig/health, /v1/sig/api/v1/abi/function)
http_port: 8050 # loopback; gRPC auto = http_port+1
# --- ready to wire (stateless, no DB) — add the dep+feature+service! in ffi/ ---
# - name: visualizer # -> /api/visualizer/*
# - name: visualizer # -> /v1/visualizer/*
# http_port: 8052
# - name: smart-contract-verifier # -> /api/verifier/*
# - name: smart-contract-verifier # -> /v1/verifier/*
# http_port: 8054
# --- Postgres-only (NOT sqlite-able via blockscout-service-launcher) ---
# stats + multichain-aggregator need a real Postgres (stats also needs a
+2 -2
View File
@@ -22,7 +22,7 @@ type Config struct {
// ServicesConfig configures the in-process Blockscout-rs services that the
// `-tags ffi` build runs as goroutine-driven Tokio threads inside this one
// binary. The zip front router reverse-proxies each enabled service under
// /api/<prefix>/* → http://127.0.0.1:<port>. In the default (non-FFI) build
// /v1/<prefix>/* → http://127.0.0.1:<port>. In the default (non-FFI) build
// nothing is launched and these proxies are not mounted (see services.go).
type ServicesConfig struct {
// Enabled is the master switch. When false (default) no in-process
@@ -41,7 +41,7 @@ type ServiceConfig struct {
// passed to it.
Name string `yaml:"name"`
// Prefix is the URL path segment the front router proxies under
// (/api/<prefix>/*). Empty => derived from Name (sig-provider => "sig").
// (/v1/<prefix>/*). Empty => derived from Name (sig-provider => "sig").
Prefix string `yaml:"prefix"`
// HTTPPort is the localhost port the service's own HTTP server binds.
// Empty => a default is assigned per service (see servicePort).
+5 -5
View File
@@ -114,13 +114,13 @@ own HTTP server bound to **loopback** (`127.0.0.1:<port>`, assigned from
exposed. Routing, in registration order:
```
/api/<prefix>/* → httputil.ReverseProxy → http://127.0.0.1:<svc_http_port> (mountServiceProxies, services.go)
/v1/<prefix>/* → httputil.ReverseProxy → http://127.0.0.1:<svc_http_port> (mountServiceProxies, services.go)
/* → the existing Go-native explorer *http.ServeMux (zip.AdaptNetHTTP, front.go)
```
The proxy strips the `/api/<prefix>` mount prefix, so the upstream sees its own
native path (e.g. `/api/sig/health` → sig-provider `/health`;
`/api/sig/api/v1/abi/function` → `/api/v1/abi/function`). Enabled services +
The proxy strips the `/v1/<prefix>` mount prefix, so the upstream sees its own
native path (e.g. `/v1/sig/health` → sig-provider `/health`;
`/v1/sig/api/v1/abi/function` → `/api/v1/abi/function`). Enabled services +
ports are configured under `services:` in chains.yaml
(`ServicesConfig` → `resolveServices` is the single source of truth both the
proxy and this FFI launcher read). Default prefixes:
@@ -128,7 +128,7 @@ proxy and this FFI launcher read). Default prefixes:
`multichain-aggregator→multichain`, `visualizer→visualizer`.
Proven end-to-end (sig-provider, one process owning both :8090 and the service
port): `curl /api/sig/health → {"status":"SERVING"}`, matching a direct hit on
port): `curl /v1/sig/health → {"status":"SERVING"}`, matching a direct hit on
the in-process service port.
## Database: SQLite status per service (verified against explorer-rs)
+2 -2
View File
@@ -4,7 +4,7 @@ package main
//
// One zip.App owns the public listener. It composes two things, in order:
//
// 1. /api/<prefix>/* reverse-proxies to each in-process Blockscout-rs
// 1. /v1/<prefix>/* reverse-proxies to each in-process Blockscout-rs
// service (mountServiceProxies, services.go). Registered FIRST so Fiber's
// in-order route matching sends these to the Rust services.
//
@@ -53,7 +53,7 @@ func buildFrontApp(base http.Handler, svcs []resolvedService) *zip.App {
})
// (1) Per-service reverse proxies — registered before the catch-all so
// /api/<prefix>/* wins over /*.
// /v1/<prefix>/* wins over /*.
mountServiceProxies(app, svcs)
// (2) The whole existing explorer mux as the catch-all. zip.AdaptNetHTTP
+1 -1
View File
@@ -192,7 +192,7 @@ func main() {
supervisor.MountRoutes(mux)
frontend.Mount(mux)
// zip is the FRONT router (front.go): it reverse-proxies /api/<prefix>/*
// zip is the FRONT router (front.go): it reverse-proxies /v1/<prefix>/*
// to each in-process service and serves everything else from the explorer
// mux built above. When no services are configured the front app is just
// the mux behind zip's security middleware — identical behaviour, one
+8 -8
View File
@@ -11,7 +11,7 @@ package main
// 2. SETTINGS — build the JSON each service's upstream `Settings` deserializes
// (server.http.addr / server.grpc.addr, and DB url where applicable).
// 3. PROXY — mount a reverse-proxy on the zip front App at
// /api/<prefix>/* → http://127.0.0.1:<http_port>.
// /v1/<prefix>/* → http://127.0.0.1:<http_port>.
//
// The actual in-process LAUNCH (cgo → liblux_explorer_ffi.a) is the ONLY part
// that differs by build tag and lives in ffi_on.go / ffi_off.go. That split is
@@ -38,7 +38,7 @@ import (
const servicePortBase = 8050
// defaultPrefix maps an FFI service name to its default URL prefix. The prefix
// is the path segment the SPA / clients hit: /api/<prefix>/*. Kept short and
// is the path segment the SPA / clients hit: /v1/<prefix>/*. Kept short and
// stable so front-end calls don't churn when a service is renamed upstream.
var defaultPrefix = map[string]string{
"sig-provider": "sig",
@@ -159,24 +159,24 @@ func buildSettingsJSON(s ServiceConfig, httpPort, grpcPort int) string {
}
// mountServiceProxies registers a reverse-proxy on the zip front App for each
// resolved in-process service: /api/<prefix>/* → http://127.0.0.1:<http_port>.
// resolved in-process service: /v1/<prefix>/* → http://127.0.0.1:<http_port>.
//
// We strip the /api/<prefix> mount prefix before forwarding so the upstream
// We strip the /v1/<prefix> mount prefix before forwarding so the upstream
// service sees the path it expects (e.g. /api/v1/abi/... not
// /api/sig/api/v1/abi/...). zip.AdaptNetHTTP wraps the stdlib
// /v1/sig/api/v1/abi/...). zip.AdaptNetHTTP wraps the stdlib
// httputil.ReverseProxy onto a zip route — the simplest thing that works, per
// the brief: no bespoke fasthttp proxy, no escape hatch.
//
// Mounted via app.Mount which registers `All(prefix+"/*")`. These are
// registered BEFORE the catch-all explorer mux (see buildFrontApp) so Fiber's
// in-order matching routes /api/<prefix>/* to the service and everything else
// in-order matching routes /v1/<prefix>/* to the service and everything else
// to the explorer.
func mountServiceProxies(app *zip.App, svcs []resolvedService) {
for _, s := range svcs {
target := &url.URL{Scheme: "http", Host: fmt.Sprintf("127.0.0.1:%d", s.HTTPPort)}
mountPrefix := "/api/" + s.Prefix
mountPrefix := "/v1/" + s.Prefix
rp := httputil.NewSingleHostReverseProxy(target)
// Strip the /api/<prefix> mount prefix so the upstream sees its own
// Strip the /v1/<prefix> mount prefix so the upstream sees its own
// native path. NewSingleHostReverseProxy's default Director only sets
// the host; we extend it to rewrite the path.
base := rp.Director