chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Full-sync HTTP control API
|
||||
|
||||
The handler imports no queue or storage implementations. Inject the five
|
||||
contract interfaces in `Dependencies`: `RunLister`, `RunReader`,
|
||||
`FailurePager`, `ReplayRequester`, and `ActionReader`. The replay service owns durable
|
||||
idempotency and execution; the HTTP layer does not cache requests or launch work.
|
||||
|
||||
Use `NewRunReader(live, archive, rounds)` to compose `LiveRunReader`,
|
||||
`ArchivedRunReader`, and `RoundStatsReader` into a `RunReader`. Live metadata
|
||||
wins and returns `source: "live"`; only `ErrNotFound` from live metadata permits
|
||||
archive fallback, returning `source: "archive"`. Live storage errors and round
|
||||
lookup errors propagate. Archive and round readers may be nil to disable those
|
||||
lookups. A failure listing always calls `FailurePager` directly, never archives.
|
||||
|
||||
## Routes and responses
|
||||
|
||||
All routes require `Authorization: Bearer <configured token>`. Missing, wrong,
|
||||
or ambiguous credentials return 401 with a `WWW-Authenticate` challenge. An
|
||||
empty configured token is a startup configuration error. Responses use JSON and
|
||||
`Cache-Control: no-store`.
|
||||
|
||||
| Method | Route | Success body |
|
||||
| --- | --- | --- |
|
||||
| GET | `/internal/v1/full-sync-runs?syncKind=&limit=` | 200 `contract.RunList` |
|
||||
| GET | `/internal/v1/full-sync-runs/{runId}` | 200 `contract.RunView` |
|
||||
| GET | `/internal/v1/full-sync-runs/{runId}/failures?cursor=&limit=` | 200 `contract.FailurePage` |
|
||||
| POST | `/internal/v1/full-sync-runs/{runId}/replay` | 202 `contract.ReplayAccepted` |
|
||||
| GET | `/internal/v1/full-sync-actions/{actionId}` | 200 `contract.ReplayAction` |
|
||||
|
||||
Run list results merge active Redis metadata with flat `run-<runId>.json`
|
||||
archives under the configured archive root. Active Redis metadata wins when an
|
||||
ID exists in both sources. `syncKind` accepts `traffic_usage` or `sim_status`;
|
||||
`limit` defaults to 50 and is capped at 100.
|
||||
|
||||
Failure limits use the contract rules: absent, empty, or zero selects 100;
|
||||
positive values up to 500 are accepted; other values are rejected. Repeated
|
||||
cursor/limit parameters and malformed query encoding are rejected. Cursors are
|
||||
opaque and passed unchanged after URL decoding. The pager validates cursor
|
||||
contents and run binding. Empty pages serialize `items: []` and preserve
|
||||
`nextCursor`; only an empty next cursor signals completion.
|
||||
|
||||
Replay requires a single `Idempotency-Key` header and a JSON body such as
|
||||
`{"requestedBy":"operator"}`. The body is limited to 64 KiB, must contain one
|
||||
JSON object with no unknown fields, and must satisfy `ReplayRequest.Validate`.
|
||||
If supplied, Content-Type must be `application/json` (parameters are allowed).
|
||||
The exact `requestedBy` value is preserved for fingerprinting. Repeated accepted
|
||||
requests return the service's existing action/target and current status with
|
||||
202. The adapter constructs `statusUrl` and the `Location` header from actionId.
|
||||
|
||||
All application errors use this envelope, without storage error details:
|
||||
|
||||
```json
|
||||
{"error":{"code":"invalid_argument","message":"Invalid request"}}
|
||||
```
|
||||
|
||||
| Condition | HTTP status | Error code |
|
||||
| --- | --- | --- |
|
||||
| Invalid path, query, header, body, or `ErrInvalidArgument` | 400 | `invalid_argument` |
|
||||
| Missing/wrong bearer credentials | 401 | `unauthorized` |
|
||||
| Unknown route/resource or `ErrNotFound` | 404 | `not_found` |
|
||||
| Unsupported route method | 405 | `method_not_allowed` |
|
||||
| `ErrConflict` (including changed idempotent request), `ErrLeaseLost` | 409 | `conflict` |
|
||||
| `ErrFailureDetailsUnavailable` | 410 | `failure_details_unavailable` |
|
||||
| Oversized replay body | 413 | `body_too_large` |
|
||||
| `ErrBusy` | 503 | `busy` |
|
||||
| Other service errors or unencodable service output | 500 | `internal_error` |
|
||||
|
||||
## Lifecycle
|
||||
|
||||
`NewHandler(token, deps)` can be mounted by an existing HTTP server. For an
|
||||
independent listener, construct `NewServer(Config{Addr: address, BearerToken:
|
||||
token}, deps)` and call `Start(workerCtx)`. `Start` binds synchronously and
|
||||
returns address-in-use and other startup errors directly. `Addr()` exposes the
|
||||
bound address, including an OS-assigned port when configured with port zero.
|
||||
|
||||
Call `Wait()` after Start, normally in the worker's supervision goroutine. It
|
||||
returns serving or shutdown failures; it also returns startup failures if Start
|
||||
failed. The server is single-use and rejects a second Start. Wait can have
|
||||
multiple callers but must not be called before Start.
|
||||
|
||||
Canceling `workerCtx` closes the listener and drains active HTTP requests.
|
||||
Active request contexts stay usable during the drain. `ShutdownTimeout` defaults
|
||||
to ten seconds; if it expires, active connections are closed and Wait returns
|
||||
an error wrapping `context.DeadlineExceeded`. Normal cancellation with a
|
||||
successful drain returns nil. No worker wiring or configuration-file changes
|
||||
are part of this package.
|
||||
|
||||
## Verification
|
||||
|
||||
```text
|
||||
gofmt -l internal/fullsync/httpapi
|
||||
go test ./internal/fullsync/...
|
||||
go test ./internal/fullsync/httpapi -cover
|
||||
go vet ./internal/fullsync/...
|
||||
```
|
||||
|
||||
Tests use contract mocks and local HTTP listeners, requiring no Redis or archive
|
||||
files. `go test -race ./internal/fullsync/...` additionally requires a supported
|
||||
C compiler and CGO enabled.
|
||||
Reference in New Issue
Block a user