package httpx
import (
"net/http"
"strconv"
)
// MaxBytes returns middleware that caps the request body at limit bytes.
func MaxBytes(limit int64) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cl := r.Header.Get("Content-Length"); cl != "" {
if n, err := strconv.ParseInt(cl, 10, 64); err == nil && n > limit {
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
return
}
}
// Enforce the real limit even without a trustworthy Content-Length.
r.Body = http.MaxBytesReader(w, r.Body, limit)
next.ServeHTTP(w, r)
})
}
}
package main
import (
"encoding/json"
"errors"
"net/http"
)
type uploadRequest struct {
Name string `json:"name"`
Data string `json:"data"`
}
func UploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var req uploadRequest
if err := dec.Decode(&req); err != nil {
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) {
http.Error(w, "upload exceeds size limit", http.StatusRequestEntityTooLarge)
return
}
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
if req.Name == "" {
http.Error(w, "name is required", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(map[string]string{"status": "stored", "name": req.Name})
}
package main
import (
"log"
"net/http"
"time"
"example.com/app/httpx"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/uploads", UploadHandler)
// Cap every request body at 1 MiB before it reaches a handler.
handler := httpx.MaxBytes(1 << 20)(mux)
srv := &http.Server{
Addr: ":8080",
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
}
log.Printf("listening on %s", srv.Addr)
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Accepting request bodies without a size cap is a classic denial-of-service vector: a single client can stream gigabytes into memory or disk and exhaust the process. This snippet shows the idiomatic Go defense, http.MaxBytesReader, wired into reusable middleware and then consumed by a JSON handler that reports oversized payloads cleanly.
In maxbytes.go, the MaxBytes middleware wraps a limit around every request. It first performs a cheap short-circuit on the Content-Length header — if a client honestly advertises a body larger than the limit, the request is rejected with 413 Request Entity Too Large before any bytes are read. That header cannot be trusted on its own, so the real enforcement is http.MaxBytesReader(w, r.Body, limit), which replaces r.Body with a reader that returns an error once more than limit bytes have been consumed. Because it is a decorator over the standard library, it composes with any http.Handler and works for chunked transfers where Content-Length is absent.
The subtlety MaxBytesReader handles is that it also calls the underlying ResponseWriter to close the connection, preventing a client from continuing to push data after the limit is hit. The error it produces is the sentinel *http.MaxBytesError, which downstream code inspects rather than treating every read failure as a client's fault.
In upload_handler.go, UploadHandler reads the now-capped body. It uses errors.As to distinguish a *http.MaxBytesError from generic I/O or JSON errors, returning 413 for the former and 400 for malformed JSON. This separation matters: an oversized body is a policy violation, while a truncated JSON document is a syntax problem, and clients need different signals. The handler also disables DisallowUnknownFields deliberately kept strict to reject junk.
In server.go, the middleware is applied once with a 1 MiB cap via MaxBytes(1 << 20), demonstrating that the limit is a single tunable knob at the edge. A key pitfall worth noting: MaxBytesReader only guards a single body, so per-route overrides (a 10 MiB image endpoint, say) should re-wrap with a larger value rather than relying on the global default. The pattern keeps memory bounded, fails fast, and returns precise status codes.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
Share this code
Here's the card — post it anywhere.