httputil

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 15 Imported by: 0

README

HTTPUtil Module

The httputil module provides HTTP utility functions for the Service Layer.

Overview

This module provides common HTTP handling utilities including:

  • JSON request/response helpers
  • Error response formatting
  • Authentication helpers
  • Path parameter extraction
  • Outbound helpers (base URL normalization, safe transports)

Functions

JSON Helpers
// Write JSON response
httputil.WriteJSON(w, http.StatusOK, data)

// Decode JSON request body
var input MyInput
if !httputil.DecodeJSON(w, r, &input) {
    return // Error already written
}
Error Responses
// 400 Bad Request
httputil.BadRequest(w, "invalid input")

// 401 Unauthorized
httputil.Unauthorized(w, "authentication required")

// 403 Forbidden
httputil.Forbidden(w, "access denied")

// 404 Not Found
httputil.NotFound(w, "resource not found")

// 500 Internal Server Error
httputil.InternalError(w, "something went wrong")
Authentication Helpers
// Require user ID from header
userID, ok := httputil.RequireUserID(w, r)
if !ok {
    return // 401 already written
}

// Get optional user ID
userID := httputil.GetUserID(r)

// Get calling service identity (derived from verified mTLS in strict environments)
serviceID := httputil.GetServiceID(r)
Path Parameters
// Extract path parameter
// For path "/request/123/status", extract "123"
id := httputil.PathParam(r.URL.Path, "/request/", "/status")
Outbound Helpers
// Normalize a base URL (trims trailing slashes, validates scheme/host, enforces
// https in strict identity mode).
baseURL, _, err := httputil.NormalizeServiceBaseURL("https://gateway:8080/")
if err != nil {
    // handle invalid URL
}

// Reuse a safe default transport for outbound HTTPS calls.
client := &http.Client{
    Timeout:   15 * time.Second,
    Transport: httputil.DefaultTransportWithMinTLS12(),
}
_ = baseURL
_ = client

Response Format

All error responses follow a consistent format:

{
    "code": "HTTP_400",
    "message": "error message here",
    "details": {},
    "trace_id": "trace-id-here"
}

Success responses return the data directly:

{
    "id": "123",
    "status": "success",
    ...
}

Usage Example

func (s *Service) handleCreateRequest(w http.ResponseWriter, r *http.Request) {
    // Require authentication
    userID, ok := httputil.RequireUserID(w, r)
    if !ok {
        return
    }

    // Decode request body
    var input CreateRequestInput
    if !httputil.DecodeJSON(w, r, &input) {
        return
    }

    // Validate input
    if input.Amount <= 0 {
        httputil.BadRequest(w, "amount must be positive")
        return
    }

    // Process request...
    result, err := s.processRequest(r.Context(), userID, input)
    if err != nil {
        httputil.InternalError(w, "failed to process request")
        return
    }

    // Return success response
    httputil.WriteJSON(w, http.StatusCreated, result)
}

Headers

Header Purpose
X-User-ID User identifier (set by gateway after auth)
X-Service-ID Optional service identity hint (dev fallback; production uses verified mTLS identity)
Content-Type Must be application/json for POST/PUT
Authorization Bearer token for gateway authentication (not forwarded to internal services)

Trust Model

  • In strict environments (production, SGX hardware, or when MarbleRun injects TLS credentials), GetUserID, GetUserRole, and GetServiceID only trust identity that is protected by verified mTLS.
  • User identity (X-User-ID) is set by the gateway after authentication and forwarded to internal services over the MarbleRun mesh.
  • Service identity is derived from the MarbleRun-issued mTLS certificate to prevent header spoofing.

Testing

go test ./infrastructure/httputil/... -v

Documentation

Overview

Package httputil provides common HTTP utilities for service handlers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BadRequest

func BadRequest(w http.ResponseWriter, message string)

BadRequest writes a 400 Bad Request response.

func CanonicalizeServiceID

func CanonicalizeServiceID(raw string) string

CanonicalizeServiceID maps legacy service IDs and DNS names (e.g. "accountpool") to the canonical service ID used throughout the service layer.

func ClientIP

func ClientIP(r *http.Request) string

ClientIP extracts the best-effort client IP address from the request.

Security model:

  • If the direct peer is on a private network (typical for ingress/proxy), trust X-Forwarded-For / X-Real-IP.
  • If the request comes directly from the internet, ignore spoofable forwarded headers and fall back to RemoteAddr.

func Conflict

func Conflict(w http.ResponseWriter, message string)

Conflict writes a 409 Conflict response.

func CopyHTTPClientWithTimeout

func CopyHTTPClientWithTimeout(base *http.Client, timeout time.Duration, force bool) *http.Client

CopyHTTPClientWithTimeout returns a shallow copy of base with its Timeout set.

It is safe to use with shared clients (e.g., Marble HTTP clients) because it never mutates the caller-provided instance.

If base is nil, it returns a new http.Client. If base.Timeout is zero, the timeout is always set. If force is true, the timeout is set even when base.Timeout is non-zero.

func DecodeJSON

func DecodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) bool

DecodeJSON decodes a JSON request body into the provided struct. Returns false and writes an error response if decoding fails.

func DecodeJSONOptional

func DecodeJSONOptional(w http.ResponseWriter, r *http.Request, v interface{}) bool

DecodeJSONOptional decodes a JSON request body into the provided struct when present. It returns true when the body is empty and no decoding is needed.

func DefaultTransportWithMinTLS12

func DefaultTransportWithMinTLS12() http.RoundTripper

DefaultTransportWithMinTLS12 clones http.DefaultTransport (when possible) and enforces a modern TLS baseline for outbound calls.

This helper is used by multiple clients (Supabase, chain RPC, external API integrations) to avoid duplicating transport-cloning logic and to ensure TLS 1.2+ is consistently enforced.

SECURITY: Restricts cipher suites to AEAD ciphers with forward secrecy.

func Forbidden

func Forbidden(w http.ResponseWriter, message string)

Forbidden writes a 403 Forbidden response.

func GetServiceID

func GetServiceID(r *http.Request) string

GetServiceID returns the authenticated caller service ID. In strict environments (production/SGX/MarbleRun TLS), this is derived from the verified mTLS peer identity to avoid header spoofing.

func GetUserID

func GetUserID(r *http.Request) string

GetUserID extracts the user ID from the X-User-ID header. Returns empty string if not present.

func GetUserRole

func GetUserRole(r *http.Request) string

GetUserRole extracts the user role from the X-User-Role header.

func InternalError

func InternalError(w http.ResponseWriter, message string)

InternalError writes a 500 Internal Server Error response.

func NewClient

func NewClient(cfg ClientConfig, defaults ClientDefaults) (*http.Client, error)

NewClient creates an HTTP client with standardized configuration. It handles: - Base URL normalization (optional) - Timeout handling with defaults - Max body size limits - Service ID trimming

Example:

client, err := NewClient(ClientConfig{
    BaseURL:    cfg.ServiceURL,
    ServiceID:  "my-service",
    HTTPClient: marble.HTTPClient(),
}, ClientDefaults{
    Timeout: 15 * time.Second,
})

func NewClientWithBaseURL

func NewClientWithBaseURL(cfg ClientConfig, defaults ClientDefaults) (*http.Client, string, error)

NewClientWithBaseURL creates a client with base URL normalization. This is the most common pattern for service-to-service clients. Returns the HTTP client and normalized base URL.

func NormalizeBaseURL

func NormalizeBaseURL(raw string, opts BaseURLOptions) (string, *url.URL, error)

NormalizeBaseURL normalizes and validates a base URL used for service-to-service calls.

It trims whitespace, removes trailing slashes, validates scheme/host, disallows user info, and optionally enforces https in strict identity mode.

func NormalizeServiceBaseURL

func NormalizeServiceBaseURL(raw string) (string, *url.URL, error)

NormalizeServiceBaseURL is the standard normalization used by service clients. It enforces https whenever strict identity mode is enabled.

func NotFound

func NotFound(w http.ResponseWriter, message string)

NotFound writes a 404 Not Found response.

func PaginationParams

func PaginationParams(r *http.Request, defaultLimit, maxLimit int) (offset, limit int)

PaginationParams extracts pagination parameters from the request.

func PathParam

func PathParam(path, prefix, suffix string) string

PathParam extracts a path parameter from the URL. Example: PathParam("/users/123/orders", "/users/", "/orders") returns "123"

func PathParamAt

func PathParamAt(path string, index int) string

PathParamAt extracts a path parameter at the given index (0-based). Example: PathParamAt("/users/123/orders/456", 1) returns "123"

func QueryBool

func QueryBool(r *http.Request, key string, defaultVal bool) bool

QueryBool extracts a boolean query parameter with a default value.

func QueryInt

func QueryInt(r *http.Request, key string, defaultVal int) int

QueryInt extracts an integer query parameter with a default value.

func QueryInt64

func QueryInt64(r *http.Request, key string, defaultVal int64) int64

QueryInt64 extracts an int64 query parameter with a default value.

func QueryString

func QueryString(r *http.Request, key, defaultVal string) string

QueryString extracts a string query parameter with a default value.

func ReadAllStrict

func ReadAllStrict(r io.Reader, limit int64) ([]byte, error)

ReadAllStrict reads the full body from r up to limit bytes. If the body exceeds limit, it returns a *BodyTooLargeError.

func ReadAllWithLimit

func ReadAllWithLimit(r io.Reader, limit int64) (body []byte, truncated bool, err error)

ReadAllWithLimit reads up to limit bytes from r. It returns the bytes read, whether the body exceeded the limit, and any I/O error.

This is useful for logging or building error messages without risking OOM.

func RequireAdminRole

func RequireAdminRole(w http.ResponseWriter, r *http.Request) bool

RequireAdminRole verifies the user role is admin or super_admin. Returns false and writes a 403 Forbidden response if the role check fails.

func RequireServiceID

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

RequireServiceID extracts the service ID from the request context. Returns false and writes an error response if not present.

func RequireUserID

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

RequireUserID extracts the user ID from the X-User-ID header. Returns false and writes an error response if not present.

func ResolveMaxBodyBytes

func ResolveMaxBodyBytes(cfg int64, defaultBytes int64) int64

ResolveMaxBodyBytes returns the effective max body size from config and defaults.

func ResolveServiceID

func ResolveServiceID(serviceID string) string

ResolveServiceID returns a trimmed service ID or empty string.

func SecureCipherSuites

func SecureCipherSuites() []uint16

SecureCipherSuites returns the list of secure TLS 1.2 cipher suites. SECURITY: Only includes AEAD ciphers with forward secrecy (ECDHE). TLS 1.3 cipher suites are managed by Go automatically.

func SecureTLSConfig

func SecureTLSConfig() *tls.Config

SecureTLSConfig returns a secure TLS configuration. SECURITY: Enforces TLS 1.2 minimum with secure cipher suites only.

func ServiceUnavailable

func ServiceUnavailable(w http.ResponseWriter, message string)

ServiceUnavailable writes a 503 Service Unavailable response.

func StrictIdentityMode

func StrictIdentityMode() bool

StrictIdentityMode returns true when the service should only trust identity headers that are protected by verified mTLS.

func Unauthorized

func Unauthorized(w http.ResponseWriter, message string)

Unauthorized writes a 401 Unauthorized response.

func WrapError

func WrapError(err error, message string) error

WrapError wraps an error with context.

func WriteError

func WriteError(w http.ResponseWriter, status int, message string)

WriteError writes a JSON error response.

func WriteErrorResponse

func WriteErrorResponse(w http.ResponseWriter, r *http.Request, status int, code, message string, details interface{})

WriteErrorResponse writes a standard JSON error response envelope.

func WriteErrorWithCode

func WriteErrorWithCode(w http.ResponseWriter, status int, code, message string)

WriteErrorWithCode writes a JSON error response with an error code.

func WriteJSON

func WriteJSON(w http.ResponseWriter, status int, data interface{})

WriteJSON writes a JSON response with the given status code.

Types

type BaseURLOptions

type BaseURLOptions struct {
	// RequireHTTPSInStrictMode enforces https URLs whenever runtime.StrictIdentityMode()
	// is enabled (production/SGX/MarbleRun TLS).
	RequireHTTPSInStrictMode bool
}

BaseURLOptions configures NormalizeBaseURL.

type BodyTooLargeError

type BodyTooLargeError struct {
	Limit int64
}

BodyTooLargeError is returned by ReadAllStrict when the body exceeds the limit.

func (*BodyTooLargeError) Error

func (e *BodyTooLargeError) Error() string

type ClientConfig

type ClientConfig struct {
	// BaseURL is the base URL for the service (will be normalized)
	BaseURL string

	// ServiceID identifies the caller for service mesh authentication
	ServiceID string

	// Timeout is the request timeout. Zero means use default.
	Timeout time.Duration

	// HTTPClient is the base HTTP client to use (e.g., Marble mTLS client)
	// If nil, a default client will be created.
	HTTPClient *http.Client

	// MaxBodyBytes caps response body size to prevent memory exhaustion.
	// Zero means use default.
	MaxBodyBytes int64
}

ClientConfig holds standard client configuration used across all service clients. This eliminates duplication of client creation logic.

type ClientDefaults

type ClientDefaults struct {
	Timeout          time.Duration
	MaxBodyBytes     int64
	NormalizeBaseURL bool
	RequireHTTPS     bool
}

ClientDefaults holds default values for client configuration.

func DefaultClientDefaults

func DefaultClientDefaults() ClientDefaults

DefaultClientDefaults returns standard default values.

type ErrorResponse

type ErrorResponse struct {
	Code    string      `json:"code"`
	Message string      `json:"message"`
	Details interface{} `json:"details,omitempty"`
	TraceID string      `json:"trace_id,omitempty"`
}

ErrorResponse represents a standard error response.

Jump to

Keyboard shortcuts

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