F-06: HTTP server + middleware (bookshelf-qqz.6) #6

Merged
zombor merged 2 commits from bd-bookshelf-qqz.6 into main 2026-05-20 17:28:39 +00:00
Owner

Summary

  • Add internal/httpserver.NewServer with production-ready timeouts (Read: 5s, Write: 10s, Idle: 120s) and BaseContext wiring for graceful shutdown
  • Add internal/middleware package: TraceID → RequestLogger → MetricsRecorder → ErrorMapper → Recoverer composable chain
  • Add internal/handler.WantsJSON content-negotiation helper
  • Wire full middleware stack in cmd/bookshelf/main.go with /healthz, /metrics, and GET / (HTML or JSON based on Accept)

Test plan

  • go vet ./... — clean
  • go test -race ./internal/... — all pass
  • make coverage — 100% on internal/httpserver, internal/middleware, internal/handler
  • TraceID: generates ID when absent, honours valid inbound header, rejects invalid chars/oversized
  • RequestLogger: logs method/path/status/duration_ms/trace_id/bytes_written; idempotent WriteHeader guard
  • MetricsRecorder: increments http_requests_total, observes http_request_duration_seconds; uses route pattern not resolved path; registers metrics once per registry
  • ErrorMapper: maps ErrNotFound→404, ErrValidation→400, ErrUnauthorized→401, ErrForbidden→403, unknown→500; JSON vs plain text based on Accept
  • Recoverer: catches panics, logs Error with stack trace, returns 500
  • Chain integration test: full middleware stack exercised end-to-end with httptest.NewServer

Closes bead bookshelf-qqz.6 on merge.

## Summary - Add `internal/httpserver.NewServer` with production-ready timeouts (Read: 5s, Write: 10s, Idle: 120s) and BaseContext wiring for graceful shutdown - Add `internal/middleware` package: TraceID → RequestLogger → MetricsRecorder → ErrorMapper → Recoverer composable chain - Add `internal/handler.WantsJSON` content-negotiation helper - Wire full middleware stack in `cmd/bookshelf/main.go` with `/healthz`, `/metrics`, and `GET /` (HTML or JSON based on Accept) ## Test plan - [x] `go vet ./...` — clean - [x] `go test -race ./internal/...` — all pass - [x] `make coverage` — 100% on internal/httpserver, internal/middleware, internal/handler - [x] TraceID: generates ID when absent, honours valid inbound header, rejects invalid chars/oversized - [x] RequestLogger: logs method/path/status/duration_ms/trace_id/bytes_written; idempotent WriteHeader guard - [x] MetricsRecorder: increments http_requests_total, observes http_request_duration_seconds; uses route pattern not resolved path; registers metrics once per registry - [x] ErrorMapper: maps ErrNotFound→404, ErrValidation→400, ErrUnauthorized→401, ErrForbidden→403, unknown→500; JSON vs plain text based on Accept - [x] Recoverer: catches panics, logs Error with stack trace, returns 500 - [x] Chain integration test: full middleware stack exercised end-to-end with httptest.NewServer Closes bead bookshelf-qqz.6 on merge.
- internal/httpserver: NewServer with ReadTimeout/WriteTimeout/IdleTimeout
  and BaseContext wiring for graceful shutdown
- internal/middleware: TraceID, RequestLogger, MetricsRecorder,
  ErrorMapper (HandlerFunc + Wrap + sentinels), Recoverer
- internal/handler: WantsJSON content-negotiation helper
- cmd/bookshelf/main.go: wire full middleware chain, register /healthz,
  /metrics, and GET / with JSON/HTML content negotiation
- 100% coverage on internal/httpserver, internal/middleware, internal/handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
go build ./cmd/bookshelf/... produces a bare binary in the working
directory when run outside the Makefile. Add it to .gitignore so
worktree builds don't leave untracked files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zombor merged commit c8e08f9a37 into main 2026-05-20 17:28:39 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!6
No description provided.