http

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

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

View Source
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

View Source
var (
	// ErrControlUnavailable is returned by ControlClient when no service is
	// 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")
)
View Source
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

func (c *Control) Serve(ctx context.Context) error

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

func (c *Control) SocketPath() string

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.

func (*ControlClient) Status

func (c *ControlClient) Status(ctx context.Context) ([]scheduler.TaskState, error)

Status fetches the running service's scheduler task-health snapshot.

func (*ControlClient) Stop

func (c *ControlClient) Stop(ctx context.Context) error

Stop asks the running service to shut down. The server enforces the peer-credential gate; an unauthorized caller receives the server's ErrStopNotAuthorized text inside the returned error.

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

func (s *Server) Addr() string

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

func (s *Server) Bus() events.EventBus

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

func (s *Server) Handler() nethttp.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

func (s *Server) RegisterMount(prefix string, handler nethttp.Handler) error

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

func (s *Server) Serve(ctx context.Context) error

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.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully drains the underlying net/http server, honouring ctx as the deadline. It is the mechanism the runtime wires to context cancellation.

type StateProvider

type StateProvider interface {
	State() []scheduler.TaskState
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL