Documentation
¶
Overview ¶
Package middleware provides HTTP middleware for the emulator.
CORS adds Cross-Origin Resource Sharing headers so that browser-based AWS SDK clients (e.g. the Overcast web UI) can talk directly to the emulator without going through a backend-for-frontend proxy.
This is deliberately permissive — the emulator is a local dev tool, not a security boundary. All origins, methods, and headers are allowed.
Package middleware contains HTTP middleware functions for the emulator's request pipeline. Each middleware is a standard net/http middleware — it takes a handler and returns a handler. This is identical to Express middleware in concept: (req, res, next) => void.
In Go:
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do something before
next.ServeHTTP(w, r)
// do something after
})
}
Index ¶
- Constants
- func CORS(next http.Handler) http.Handler
- func ContextWithRegion(ctx context.Context, region string) context.Context
- func DrainBody(next http.Handler) http.Handler
- func IAMEnforce(enabled bool, st state.Store, logger *zap.Logger) func(http.Handler) http.Handler
- func InvalidateIAMEnforceCache()
- func Logger(logger *zap.Logger, clk clock.Clock) func(http.Handler) http.Handler
- func NotReady(store state.Store) func(http.Handler) http.Handler
- func Protocol(identifiers []codec.Identifier) func(http.Handler) http.Handler
- func Recovery(logger *zap.Logger) func(http.Handler) http.Handler
- func Region(next http.Handler) http.Handler
- func RegionFromContext(ctx context.Context, fallback string) string
- func RequestEvents(busPtr **events.Bus, clk clock.Clock) func(http.Handler) http.Handler
- func RequestID(next http.Handler) http.Handler
- func S3VirtualHost(next http.Handler) http.Handler
- func S3VirtualHostFor(hostname string) func(http.Handler) http.Handler
- func ServiceFromCredential(r *http.Request) string
- func SigV4(validate bool, secretResolver SecretResolver, logger *zap.Logger, ...) func(http.Handler) http.Handler
- type SecretResolver
Constants ¶
const DefaultSigV4Secret = defaultSigV4Secret
DefaultSigV4Secret is the fallback signing secret used when no SecretResolver is configured or when the resolver cannot find a secret for the given access key. The value "test" matches the default credentials used by the AWS SDK and CLI in local-dev workflows.
Variables ¶
This section is empty.
Functions ¶
func CORS ¶
CORS returns a middleware that sets permissive CORS headers on every response and handles preflight OPTIONS requests.
func ContextWithRegion ¶
ContextWithRegion returns a child context carrying region, suitable for background goroutines that need to access region-scoped stores outside a request context.
func DrainBody ¶
DrainBody returns a middleware that:
- Drains and closes the request body after the handler returns.
- Buffers the response body so that a Content-Length header is always set before the first byte reaches the client.
Together these prevent the Go AWS SDK v2 warning:
"WARN failed to close HTTP response body, this may affect connection reuse"
The warning fires on the CLIENT side when resp.Body.Close() fails. The root cause is a server response without Content-Length: the client relies on chunked transfer or connection close to detect the end of the body, and any framing issue makes Close() return an error. Setting Content-Length on every response eliminates the problem.
If the handler calls http.Flusher.Flush() (streaming / SSE) or the buffered body exceeds maxResponseBuffer, the middleware switches to direct pass-through so large or streaming responses are never fully buffered.
func IAMEnforce ¶
IAMEnforce enforces opt-in IAM authorization.
func InvalidateIAMEnforceCache ¶
func InvalidateIAMEnforceCache()
func Logger ¶
Logger logs every request with structured fields: real AWS API calls and other requests at INFO, internal health/readiness and /_debug/* polling at TRACE (see isOperationalPollPath). When stdout is a terminal, each line is prefixed with the service badge and (when known) an operation badge so log lines are easy to scan at a glance. Failed requests (5xx) are logged at ERROR level regardless of path.
func NotReady ¶
NotReady rejects a request with a 503 while the storage backend is still completing a one-time startup migration (see internal/state/migrate.go), instead of letting the request observe whatever the store would otherwise do during that window: persistent mode blocks the request indefinitely inside ensureReady, and hybrid mode's TierHot reads silently return "not found" for data that exists once migration finishes, because the post-migration seed hasn't populated memory yet (see state.NotReadyReporter and HybridStore.NotReady for the precise window this covers).
Internal Overcast endpoints (any path starting with "/_" — /_debug, /_health, /_/info, /_overcast/*, ...) are exempt, so operators can still check status, inspect debug state, or poll init-hook progress while a migration is in flight. No real AWS API request path starts with "/_".
store is checked once per request via a non-blocking type assertion to state.NotReadyReporter — stores that don't implement it (MemoryStore, WALStore) are always treated as ready, the same convention state.ReadyAwaiter already uses.
func Protocol ¶
Protocol is the wire-protocol detection middleware. It walks a list of codec.Identifiers in precision order; on the first match it stashes the codec and operation name in the request context (retrievable via codec.FromContext) and forwards the request unchanged.
The middleware NEVER:
- consumes the request body,
- rejects a request, or
- alters the request in any way other than adding context values.
On no match it forwards the request unchanged, so legacy handlers continue to function exactly as before. Rejection of unsupported protocols for opted-in services happens at the dispatcher boundary, not here.
This middleware is always-on as of Phase 6 completion.
func Recovery ¶
Recovery catches any panic from a handler, logs it with a stack trace, and returns a 500 InternalError response. Without this, a panic in one handler would crash the entire server process.
In Go, a "panic" is like an uncaught exception — recover() is the equivalent of a catch-all try/catch block.
func Region ¶
Region extracts the AWS region from each request and stores it in the context. Resolution order (first non-empty wins):
- X-Overcast-Region header (internal override used by the CloudFormation provisioner)
- SigV4 Authorization header Credential scope: AKID/DATE/REGION/SERVICE/aws4_request
- Host header subdomain: <id>.execute-api.<region>.<base> — the canonical AWS API Gateway invoke URL shape (also supported by LocalStack).
If none yield a region the context is left unchanged and handlers fall back to cfg.Region (OVERCAST_DEFAULT_REGION, default "us-east-1"). This mirrors how LocalStack resolves region: always from the request, never from a server-wide setting.
func RegionFromContext ¶
RegionFromContext returns the per-request region stored by the Region middleware. If absent, returns fallback.
func RequestEvents ¶
RequestEvents publishes a request:Received event onto the bus for every incoming HTTP request. The bus is injected via a pointer-to-pointer so it can be set after middleware registration (the bus is created late in router.New). If the bus is nil at request time, publishing is skipped.
This middleware intentionally mirrors the Logger middleware's responseWriter + detectService/detectOperation pattern — both intercept the request lifecycle to capture the same metadata for different purposes (logging vs event publishing).
Performance: each event is enqueued on the bus's 4096-capacity buffered worker pool. Publish returns immediately in the common case; it only waits if all 4096 slots are occupied by in-flight work items and 16 workers haven't caught up yet. When no SSE client is connected there are no wildcard subscribers, so zero work items are enqueued (zero overhead).
func RequestID ¶
RequestID attaches a unique request ID to every request context and response header. All subsequent middleware and handlers retrieve it via protocol.RequestIDFromContext(r.Context()).
func S3VirtualHost ¶
S3VirtualHost detects S3 virtual-hosted-style requests and rewrites the URL path to path-style so chi's /{bucket}/* routes match correctly.
Virtual-hosted-style sends the bucket name in the Host header:
Host: mybucket.localhost:4566 → /key rewritten to /mybucket/key Host: mybucket.s3.localhost:4566 → /key rewritten to /mybucket/key Host: mybucket.s3.us-east-1.localhost → /key rewritten to /mybucket/key
Path-style requests (bucket already in the URL path) pass through unchanged. Use S3VirtualHostFor when OVERCAST_HOSTNAME is a wildcard-DNS name (e.g. "localhost.localstack.cloud") so CDK asset-publisher URLs also resolve.
func S3VirtualHostFor ¶
S3VirtualHostFor returns a middleware that recognises S3 virtual-hosted-style requests.
When hostname is non-empty, requests whose Host header ends with ".<hostname>" are treated the same as ".<localhost>" requests — the leading subdomain is the bucket name.
Example with hostname="localhost.localstack.cloud":
Host: cdk-hnb659fds-assets-000000000000-ap-southeast-2.localhost.localstack.cloud:4566
is rewritten to path /cdk-hnb659fds-assets-000000000000-ap-southeast-2/… .
func ServiceFromCredential ¶
ServiceFromCredential extracts the service name (e.g. "appsync", "apigateway") from the SigV4 Authorization header's Credential scope. Returns "" if not parseable.
func SigV4 ¶
func SigV4(validate bool, secretResolver SecretResolver, logger *zap.Logger, clk clock.Clock) func(http.Handler) http.Handler
SigV4 validates AWS SigV4 signed requests when validation is enabled. Unsigned requests still pass through so emulator-internal endpoints and local no-auth workflows remain usable.
secretResolver optionally resolves per-access-key secrets from IAM. When nil or when it returns no match the middleware falls back to DefaultSigV4Secret ("test") for backward compatibility.
Types ¶
type SecretResolver ¶
type SecretResolver interface {
ResolveSecret(ctx context.Context, accessKeyID string) (secret string, found bool, err error)
}
SecretResolver resolves the secret access key for a given access key ID. The default implementation looks up IAM user access keys and STS role session credentials stored in the emulator's state store.
If no secret is found, implementations should return ("", false, nil) so the middleware can fall back to the default secret.
func NewSecretResolver ¶
func NewSecretResolver(st state.Store) SecretResolver
NewSecretResolver returns a SecretResolver backed by the emulator's state store. When st is nil the returned resolver never finds a secret so the middleware falls back to the hardcoded default ("test").