middleware

package
v0.0.1-alpha.4 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 27 Imported by: 0

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

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

func CORS(next http.Handler) http.Handler

CORS returns a middleware that sets permissive CORS headers on every response and handles preflight OPTIONS requests.

func ContextWithRegion

func ContextWithRegion(ctx context.Context, region string) context.Context

ContextWithRegion returns a child context carrying region, suitable for background goroutines that need to access region-scoped stores outside a request context.

func DrainBody

func DrainBody(next http.Handler) http.Handler

DrainBody returns a middleware that:

  1. Drains and closes the request body after the handler returns.
  2. 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

func IAMEnforce(enabled bool, st state.Store, logger *zap.Logger) func(http.Handler) http.Handler

IAMEnforce enforces opt-in IAM authorization.

func InvalidateIAMEnforceCache

func InvalidateIAMEnforceCache()

func Logger

func Logger(logger *zap.Logger, clk clock.Clock) func(http.Handler) http.Handler

Logger logs every request at INFO level with structured fields. 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.

func Protocol

func Protocol(identifiers []codec.Identifier) func(http.Handler) http.Handler

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

func Recovery(logger *zap.Logger) func(http.Handler) http.Handler

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

func Region(next http.Handler) http.Handler

Region extracts the AWS region from each request and stores it in the context. Resolution order (first non-empty wins):

  1. X-Overcast-Region header (internal override used by the CloudFormation provisioner)
  2. SigV4 Authorization header Credential scope: AKID/DATE/REGION/SERVICE/aws4_request
  3. 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

func RegionFromContext(ctx context.Context, fallback string) string

RegionFromContext returns the per-request region stored by the Region middleware. If absent, returns fallback.

func RequestEvents

func RequestEvents(busPtr **events.Bus, clk clock.Clock) func(http.Handler) http.Handler

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

func RequestID(next http.Handler) http.Handler

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

func S3VirtualHost(next http.Handler) http.Handler

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

func S3VirtualHostFor(hostname string) func(http.Handler) http.Handler

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

func ServiceFromCredential(r *http.Request) string

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").

Jump to

Keyboard shortcuts

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