Documentation
¶
Overview ¶
Package http is the transport layer of the `da service` runtime (spec §2A, OQ5 option B). It hosts the service's two listeners, one per arm of the §2A surface→transport map:
Server — the HTTP/SSE edge, serving ONLY the browser/dashboard surface and the explicit external bind (OQ3: default loopback 127.0.0.1:7878; anything wider is a deliberate --addr opt-in). It ships two built-in routes — GET /healthz (liveness) and GET /api/tasks (the scheduler State() projection as JSON) — plus the RegisterMount reservation point that lets sibling plans (R2's dashboard, R5's review queue) stitch handlers under arbitrary prefixes without modifying this package (D5).
Control — the local control plane, a Unix-domain socket (named pipe on Windows; see control_windows.go) answering `da service status`/`stop` and the §D6 CLI-routes-when-present path. Same-host traffic pays no TCP/TLS/HTTP overhead, and stop is authorized by socket file permission plus a kernel peer-credential check (SO_PEERCRED / LOCAL_PEERCRED) — replacing the earlier "POST /admin/stop gated on loopback" shape (OQ4).
The §2A selection rule decides which listener a future surface belongs on: HTTP for browser-facing, cross-machine, or external-HTTP-tool surfaces; the local high-efficiency transport otherwise.
Scope: this package ships only the transport machinery plus the built-in routes and control ops. It deliberately owns no R2/R5 endpoints and no authn/authz — RBAC is R5's plan. Handlers that need the event stream bind to the events.EventBus interface (spec D4.1), never the concrete builtin bus. The package name collides with the standard library, so net/http is imported here under the alias nethttp.
Index ¶
Constants ¶
const ( // ControlOpStatus asks the running service for its scheduler task-health // snapshot (the same projection the HTTP edge serves at /api/tasks). ControlOpStatus = "status" // ControlOpStop asks the running service to shut down. It is authorized // by the socket file permission plus a peer-credential check (spec OQ4 as // reshaped by §2A) — NOT by a network ACL. ControlOpStop = "stop" )
Control-plane wire protocol (spec §2A, "local control plane" row): the client writes exactly one JSON request object on the connection, the server answers with exactly one JSON response object and closes. No HTTP, no TLS, no framing beyond JSON's own delimiting — the peer is same-host by construction (Unix-domain socket / named pipe), so the §2A selection rule resolves this surface to the local high-efficiency transport.
Variables ¶
var ( // listening on the control socket — the §D6 "CLI-routes-when-present" // signal that callers use to fall back to direct cold-file operation. ErrControlUnavailable = errors.New("service/http: control socket unavailable (is da service running?)") // ErrStopNotAuthorized is returned when a stop request's peer credential // does not match the service owner (spec OQ4 / §2A: filesystem-permission // + peer-uid check replaces the old loopback network ACL). ErrStopNotAuthorized = errors.New("service/http: stop refused, peer not authorized") )
var ( // ErrInvalidMountPrefix is returned when a mount prefix is empty or does // not begin with a leading slash. ErrInvalidMountPrefix = errors.New("service/http: mount prefix must be a non-empty rooted path") // ErrNilMountHandler is returned when RegisterMount is given a nil handler. ErrNilMountHandler = errors.New("service/http: mount handler must not be nil") // ErrOverlappingMount is returned when a mount prefix exactly matches a // path already claimed as an exact route — a built-in (/healthz, // /api/tasks) or a previously registered mount. Nested and sibling mounts // are NOT rejected: net/http's most-specific-wins routing lets a parent // mount (e.g. "/api") coexist with a built-in sub-route ("/api/tasks") and // with a nested mount ("/api/reviews"). Only a duplicate exact claim, which // the mux itself would reject, is refused here. ErrOverlappingMount = errors.New("service/http: mount prefix already claimed by an existing route") )
Errors returned by RegisterMount.
Functions ¶
This section is empty.
Types ¶
type Control ¶
type Control struct {
// contains filtered or unexported fields
}
Control is the local control-plane listener of the service transport layer (spec §2A "local control plane" row): a Unix-domain socket (named pipe on Windows — not yet implemented, see control_windows.go) that answers `da service status` / `da service stop` and the §D6 CLI-routes-when-present path. Stop is authorized by socket file permission plus a peer-credential check (SO_PEERCRED on Linux, LOCAL_PEERCRED on macOS), never by a network ACL. Construct with NewControl.
func NewControl ¶
func NewControl(socketPath string, provider StateProvider, requestStop func()) *Control
NewControl builds the control-plane listener for socketPath, projecting provider.State() for status requests and invoking requestStop when an authorized peer asks the service to shut down. requestStop is expected to cancel the runtime context that Serve (and the HTTP edge) run under.
func (*Control) Serve ¶
Serve binds the control socket and answers requests until ctx is cancelled, then closes the listener (which unlinks the socket file) and waits for in-flight exchanges — each bounded by controlIOTimeout — to finish. It returns nil on a clean ctx-driven shutdown.
func (*Control) SocketPath ¶
SocketPath returns the socket path the control plane binds on Serve.
type ControlClient ¶
type ControlClient struct {
// contains filtered or unexported fields
}
ControlClient is the CLI-side half of the control plane: `da service status`/`stop` and any §D6 routes-when-present command dial the socket through it. A dial failure surfaces as ErrControlUnavailable so callers can distinguish "service not running — fall back to cold files" from a protocol error.
func NewControlClient ¶
func NewControlClient(socketPath string) *ControlClient
NewControlClient builds a client for the control socket at socketPath.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the service HTTP/SSE edge (spec §2A "browser/dashboard" row). Construct it with New. It is safe for concurrent use: RegisterMount may be called from any goroutine before or after Serve, and Serve/Shutdown coordinate the underlying net/http server.
func New ¶
func New(addr string, sched StateProvider, bus events.EventBus) *Server
New builds a Server bound (on Serve) to addr, projecting sched.State() at /api/tasks and carrying bus for sibling plans that mount bus-backed handlers. bus is the D4.1 EventBus interface — mounts stay backend-agnostic and hold only the G1-G4 delivery floor, never a concrete bus's stronger guarantees. The two built-in routes are registered immediately.
func (*Server) Addr ¶
Addr returns the bound listen address once Serve is listening; before that it returns the configured address (which may use a :0 wildcard port).
func (*Server) Bus ¶
Bus returns the EventBus interface the server was constructed with, so a sibling plan's mount handler can be wired to it by the composing runtime.
func (*Server) Handler ¶
Handler returns the underlying request multiplexer. It is exposed so tests (and embedders) can drive the routes via httptest without binding a socket.
func (*Server) RegisterMount ¶
RegisterMount stitches handler under prefix, serving both the exact prefix path and its subtree. R2 mounts its dashboard at "/api", R5 its review queue at "/api/reviews", through this call. The prefix must be rooted (start with "/"); a trailing slash is normalized away.
Coexistence is by design: net/http's most-specific-wins routing means a mount at "/api" and the built-in "/api/tasks" (and a nested mount at "/api/reviews") all live together — the more specific pattern serves each request, so "/api/tasks" hits the built-in while "/api/foo" hits the mount. The only rejected case is a duplicate exact claim on a path already owned by a built-in or a prior mount (which the mux itself would reject), returned as ErrOverlappingMount.
func (*Server) Serve ¶
Serve binds the configured address and serves until ctx is cancelled, at which point it gracefully shuts the server down within the configured shutdown timeout. It returns nil on a clean shutdown, the listen error if the address cannot be bound, or a non-ErrServerClosed serve error otherwise.
type StateProvider ¶
StateProvider is the read-only slice of the scheduler the HTTP surface needs: a snapshot of task health for /api/tasks. Depending on the interface rather than *scheduler.Scheduler keeps the server trivially testable and documents that the server never mutates the scheduler. *scheduler.Scheduler satisfies it.