s3-orchestrator

httputil

import "github.com/afreidah/s3-orchestrator/internal/transport/httputil"

Package httputil houses cross-cutting HTTP helpers that several transport sub-packages share: trusted-proxy-aware client IP extraction, login-attempt throttling, and TLS certificate hot-reloading.

Index

Constants

const (

    // ErrInvalidRequestBody is the user-facing message written when
    // DecodeJSONBody fails to parse the request body.
    ErrInvalidRequestBody = "invalid request body"

    // ErrMethodNotAllowed is the user-facing message written when
    // RequireMethod rejects the request method.
    ErrMethodNotAllowed = "method not allowed"
)

func DecodeJSONBody

func DecodeJSONBody(w http.ResponseWriter, r *http.Request, dst any, maxBytes int64) bool

DecodeJSONBody applies a maxBytes read cap to the request body and decodes it into dst. Returns true on success; on failure it writes a 400 JSON error response and returns false so the caller can return early without duplicating the response boilerplate.

func ExtractClientIP

func ExtractClientIP(r *http.Request, trustedProxies []*net.IPNet) string

ExtractClientIP returns the client’s real IP address. When the direct peer is a trusted proxy and X-Forwarded-For is present, the rightmost untrusted IP is returned. Otherwise the direct peer IP is returned.

func IsTLSRequest

func IsTLSRequest(r *http.Request, trustedProxies []*net.IPNet) bool

IsTLSRequest reports whether the original client connection was over TLS. True when r.TLS is set (TLS terminated at this server) or when the direct peer is a trusted proxy that forwarded X-Forwarded-Proto: https. Returns false when no trusted-proxy chain is configured and r.TLS is nil - callers can opt in to forced-Secure cookies via a separate config flag.

func PanicRecover

func PanicRecover(route string, writeErr ErrorWriter) func(http.Handler) http.Handler

PanicRecover returns middleware that recovers panics from h, translates them into a 500 response via writeErr, and emits the three observability signals (slog.ErrorContext + audit + Prometheus counter) plus OTel span error recording when a span is active on the request context.

route is the metric label and the audit event scope (e.g. “s3”, “admin”, “ui”). It must come from a small fixed set per the cardinality rules in docs/style-guide.md - never derive it from the request URL.

func ParseTrustedProxies

func ParseTrustedProxies(cidrs []string) []*net.IPNet

ParseTrustedProxies parses CIDR strings into net.IPNet slices. Invalid CIDRs are silently skipped (they are caught during config validation).

func RequireMethod

func RequireMethod(w http.ResponseWriter, r *http.Request, methods ...string) bool

RequireMethod verifies r.Method matches one of the allowed methods. Returns true when the request may proceed; on mismatch it sets the Allow header, writes a 405 JSON error, and returns false so the caller can return early.

func WriteJSON

func WriteJSON(w http.ResponseWriter, status int, body any)

WriteJSON serialises body as JSON, sets the JSON content-type header, writes the status code, and emits the encoded payload. Encoder write errors are swallowed because the response is already committed by WriteHeader; logging is the caller’s responsibility when it matters.

func WriteJSONError

func WriteJSONError(w http.ResponseWriter, status int, msg string)

WriteJSONError writes a JSON error response with the correct content type and status. Encodes the payload through encoding/json so quote or backslash characters in msg cannot corrupt the response body.

type CertReloader

CertReloader holds a TLS certificate that can be atomically swapped on reload. Safe for concurrent use by the TLS handshake goroutines and a single reloader (SIGHUP handler).

type CertReloader struct {
    // contains filtered or unexported fields
}

func NewCertReloader

func NewCertReloader(certFile, keyFile string) (*CertReloader, error)

NewCertReloader loads the initial certificate from disk and returns a reloader ready for use as a tls.Config.GetCertificate callback.

func (*CertReloader) GetCertificate

func (cr *CertReloader) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error)

GetCertificate returns the current certificate for TLS handshakes. Intended as the tls.Config.GetCertificate callback.

func (*CertReloader) Reload

func (cr *CertReloader) Reload() error

Reload reads the certificate and key from disk and swaps the current certificate. If the new files are invalid, the existing certificate is preserved and an error is returned. Logs a warning if the leaf certificate expires within 24 hours.

type ErrorWriter

ErrorWriter writes a route-appropriate 500 response. The middleware stays format-agnostic - each route group passes a writer that matches what its clients expect (S3-XML for the s3api surface, JSON for the admin API, plaintext for the UI). The writer MUST set the status code and write any headers before returning; the middleware only invokes it once per recovered panic.

type ErrorWriter func(w http.ResponseWriter, status int, errCode, message string)

type LoginThrottle

LoginThrottle tracks failed login attempts per IP and enforces lockout.

type LoginThrottle struct {
    // contains filtered or unexported fields
}

func NewLoginThrottle

func NewLoginThrottle(maxFailures int, lockoutDuration time.Duration) *LoginThrottle

NewLoginThrottle creates a login throttle with the given limits.

func (*LoginThrottle) Close

func (lt *LoginThrottle) Close()

Close stops the background cleanup goroutine. Safe to call multiple times.

func (*LoginThrottle) IsLockedOut

func (lt *LoginThrottle) IsLockedOut(ip string) bool

IsLockedOut returns true if the given IP is currently locked out. Callers must pass a resolved client IP (e.g. from ExtractClientIP), not a raw remoteAddr.

func (*LoginThrottle) RecordFailure

func (lt *LoginThrottle) RecordFailure(ip string)

RecordFailure increments the failure count for the given IP. If the count reaches MaxFailures, the IP is locked out for LockoutDuration. Callers must pass a resolved client IP (e.g. from ExtractClientIP), not a raw remoteAddr.

func (*LoginThrottle) RecordSuccess

func (lt *LoginThrottle) RecordSuccess(ip string)

RecordSuccess resets the failure counter for the given IP. Callers must pass a resolved client IP (e.g. from ExtractClientIP), not a raw remoteAddr.

Generated by gomarkdoc