Documentation
¶
Overview ¶
Package httputils provides reusable HTTP utility components.
Index ¶
- Constants
- func DrainBody(r *http.Request)
- func EchoError(ctx context.Context, c *echo.Context, code int, message string, err error) error
- func ExtractAccessKeyFromRequest(r *http.Request) string
- func ExtractRegionFromRequest(r *http.Request, defaultRegion string) string
- func ExtractServiceFromRequest(r *http.Request) string
- func GetBuffer() *bytes.Buffer
- func GetCRC32() hash.Hash32
- func GetCRC32C() hash.Hash32
- func GetMD5() hash.Hash
- func GetOperation(ctx context.Context) string
- func GetResource(ctx context.Context) string
- func GetSHA256() hash.Hash
- func MatchesTaggedResourceARN(path, serviceName string) bool
- func PutBuffer(buf *bytes.Buffer)
- func PutCRC32(h hash.Hash32)
- func PutCRC32C(h hash.Hash32)
- func PutMD5(h hash.Hash)
- func PutSHA256(h hash.Hash)
- func ReadBody(r *http.Request) ([]byte, error)
- func RequestIDMiddleware() echo.MiddlewareFunc
- func SanitizeHeaderString(s string) string
- func ScopedPrefixMatch(r *http.Request, path, prefix, serviceName string) bool
- func SetOperation(ctx context.Context, operation string) context.Context
- func SetOperationAndResource(ctx context.Context, operation, resource string) context.Context
- func SetResource(ctx context.Context, resource string) context.Context
- func WriteDynamoDBResponse(ctx context.Context, w http.ResponseWriter, code int, payload any)
- func WriteError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error, ...)
- func WriteJSON(ctx context.Context, w http.ResponseWriter, code int, payload any)
- func WriteS3ErrorResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, s3Err any, ...)
- func WriteXML(ctx context.Context, w http.ResponseWriter, code int, payload any)
- type OperationKey
- type ResponseWriter
- type SigV4Error
- type SigV4Validator
Constants ¶
const MaxRequestBodyBytes int64 = 16 * 1024 * 1024
MaxRequestBodyBytes caps every body read through ReadBody. AWS API request payloads top out at 6 MiB (Lambda synchronous invoke) or 5 GiB streamed (S3 PutObject, which uses its own streaming path and does not call ReadBody). 16 MiB leaves headroom for unusually large API requests while preventing unbounded memory growth from attacker-controlled bodies.
const TagsPathPrefix = "/tags/"
TagsPathPrefix is the shared "/tags/{resourceArn}" prefix multiple services expose for TagResource/UntagResource/ListTagsForResource.
Variables ¶
This section is empty.
Functions ¶
func DrainBody ¶
DrainBody reads and discards the request body. This is important for HTTP keep-alive, as the server needs to know the request body has been fully consumed before reusing the connection.
func ExtractAccessKeyFromRequest ¶ added in v1.3.1
ExtractAccessKeyFromRequest extracts the AWS access key ID from an HTTP request. It checks the SigV4 Authorization header credential scope first, then the X-Amz-Credential query parameter, and returns an empty string if none is found.
func ExtractRegionFromRequest ¶
ExtractRegionFromRequest extracts the AWS region from an HTTP request. It checks the SigV4 Authorization header credential scope first, then query credential, then the X-Amz-Region header, then falls back to defaultRegion.
func ExtractServiceFromRequest ¶
ExtractServiceFromRequest extracts the AWS service name from the SigV4 Authorization header credential scope or X-Amz-Credential query parameter. Returns an empty string if the service name cannot be determined.
func GetCRC32C ¶ added in v1.3.1
GetCRC32C retrieves a pooled CRC32C (Castagnoli) hasher with state reset.
func GetOperation ¶
GetOperation retrieves the operation name from context, or "Unknown" if not set.
func GetResource ¶
GetResource retrieves the resource identifier from context, or "" if not set.
func MatchesTaggedResourceARN ¶ added in v1.3.1
MatchesTaggedResourceARN reports whether path is a "/tags/{resourceArn}" request whose ARN names serviceName (arn:{partition}:{serviceName}:...). Several services share the "/tags/" prefix; only the ARN's own service segment reliably disambiguates the true owner -- a bare prefix match steals every other service's tag requests too (see gopherstack-sokq).
func PutBuffer ¶ added in v1.3.1
PutBuffer returns a *bytes.Buffer to the pool. Buffers larger than maxPooledBufferSize are discarded to bound memory retention.
func ReadBody ¶
ReadBody reads the request body and returns it as a byte slice. It handles cases where r.Body might be nil (e.g. in some test environments). It re-seeds the request body so it can be read multiple times and ensures the original request body is closed. It uses a custom ReadCloser to avoid redundant reads and allocations if called multiple times. The body is capped at MaxRequestBodyBytes; reads that exceed the cap return an `*http.MaxBytesError` (use errors.As to detect and translate to a 413 response). Because this helper has no access to the originating ResponseWriter, it cannot auto-write the 413 itself — callers must handle the size-cap error and respond appropriately.
func RequestIDMiddleware ¶
func RequestIDMiddleware() echo.MiddlewareFunc
RequestIDMiddleware returns an Echo middleware that injects an x-amz-request-id header (a new UUID) into every HTTP response.
func SanitizeHeaderString ¶
SanitizeHeaderString removes all characters except alphanumeric, hyphens, underscores, and periods. This breaks the taint for static analysis tools like CodeQL which flag raw header values in logs.
func ScopedPrefixMatch ¶ added in v1.3.1
ScopedPrefixMatch reports whether path has the given prefix AND the request's SigV4 signing scope, if present, permits serviceName to claim it: an unsigned request (no Authorization header, or none carrying a recognizable scope) still matches, but a request signed for a different, known service does not. Use this in a RouteMatcher instead of a bare strings.HasPrefix whenever the path shape is one another service's real wire API could also produce -- a bare prefix match steals that service's requests (see gopherstack-vpoh: iotdataplane's own "/connections/{id}" swallowed Outposts' GetConnection).
func SetOperation ¶
SetOperation returns a new context with the operation name updated. This follows the idiomatic context pattern - immutable values.
func SetOperationAndResource ¶
SetOperationAndResource returns a new context with both operation and resource set. This is a convenience function to set both at once without intermediate contexts.
func SetResource ¶
SetResource returns a new context with the resource identifier updated. This follows the idiomatic context pattern - immutable values.
func WriteDynamoDBResponse ¶
WriteDynamoDBResponse writes a DynamoDB-style JSON response with CRC32 checksum. Sets Content-Type to "application/x-amz-json-1.0" and X-Amz-Crc32.
func WriteError ¶
WriteError writes an error response with structured logging. Uses the logger from ctx to record the error with context. Drains the request body to ensure connection reuse.
func WriteJSON ¶
WriteJSON marshals the payload to JSON, sets standard headers, and writes the response. Sets Content-Type to "application/json" and Content-Length.
func WriteS3ErrorResponse ¶
func WriteS3ErrorResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, s3Err any, code int)
WriteS3ErrorResponse writes an S3-compatible XML error response. Drains the request body and writes the error as XML.
Types ¶
type OperationKey ¶
type OperationKey struct{}
OperationKey is a type-safe context key for storing operation metadata.
type ResponseWriter ¶
type ResponseWriter struct {
http.ResponseWriter
// contains filtered or unexported fields
}
ResponseWriter wraps http.ResponseWriter and tracks the HTTP status code. Use this when you need to inspect the status after WriteHeader is called.
func NewResponseWriter ¶
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter
NewResponseWriter creates a ResponseWriter that wraps the given http.ResponseWriter.
func (*ResponseWriter) StatusCode ¶
func (w *ResponseWriter) StatusCode() int
StatusCode returns the HTTP status code that was written.
func (*ResponseWriter) Write ¶
func (w *ResponseWriter) Write(b []byte) (int, error)
Write sets status to http.StatusOK if not already set, then delegates to wrapped ResponseWriter.
func (*ResponseWriter) WriteHeader ¶
func (w *ResponseWriter) WriteHeader(code int)
WriteHeader writes the status code and delegates to the wrapped ResponseWriter.
type SigV4Error ¶
SigV4Error is the AWS error returned when validation fails. The Code field drives the X-Amzn-Errortype header / error code clients expect.
type SigV4Validator ¶
type SigV4Validator struct {
// contains filtered or unexported fields
}
SigV4Validator cryptographically verifies AWS Signature Version 4 on incoming requests. It is OFF by default; the caller opts in via NewSigV4Validator and the EchoMiddleware. When enabled, requests whose recomputed signature does not match the Authorization header are rejected with the AWS-accurate error.
Verification re-derives the signing key from a single configured secret key (the access-key-id in the request is informational only — gopherstack is a single-tenant simulator). This mirrors how AWS validates: only the secret is secret; everything else is reconstructed from the request.
func NewSigV4Validator ¶
func NewSigV4Validator(secretKey string) *SigV4Validator
NewSigV4Validator builds a validator that checks signatures against secretKey. A blank secretKey is treated as "test" — the common AWS dummy credential — so the default localstack-style client (AWS_SECRET_ACCESS_KEY=test) validates.
func (*SigV4Validator) EchoMiddleware ¶
func (v *SigV4Validator) EchoMiddleware() echo.MiddlewareFunc
EchoMiddleware returns Echo middleware that validates SigV4 on every request. Requests without an Authorization header are passed through unchanged (many gopherstack internal/health/dashboard calls are unsigned); only requests that present a SigV4 Authorization header are verified. This keeps anonymous and presigned-URL flows working while still rejecting tampered signed requests.
func (*SigV4Validator) Verify ¶
func (v *SigV4Validator) Verify(r *http.Request) *SigV4Error
Verify recomputes the SigV4 signature for r and compares it to the signature in the Authorization header. It returns nil on a match, or a *SigV4Error describing the AWS-accurate rejection otherwise. Verify reads and restores the request body so downstream handlers still see it.