net/http.MaxBytesReader în Go

Limitează mărimea corpului unei cereri: citirea peste n octeți returnează eroare *http.MaxBytesError.

Sintaxă
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser

Ce face net/http.MaxBytesReader

Fără limită, un client poate trimite un corp de câțiva gigaocteți și serverul îl citește tot în memorie. r.Body = http.MaxBytesReader(w, r.Body, 1<<20) înlocuiește corpul cu unul care permite cel mult 1 MB; la depășire, citirea returnează o eroare de tip *http.MaxBytesError, iar serverul închide conexiunea după răspuns.

Se pune la începutul handler-ului, înainte de ParseForm, FormValue sau json.NewDecoder(r.Body). Eroarea se recunoaște cu errors.As (sau errors.AsType) și se răspunde de obicei cu 413 Request Entity Too Large.

Parametri

Parametru Descriere
w Răspunsul (folosit ca să închidă conexiunea la depășire).
r Corpul original, r.Body.
n Numărul maxim de octeți permis.

Ce returnează

Un io.ReadCloser limitat, care se pune în locul lui r.Body.

Exemple

Exemplul 1: Comentarii de maximum 50 de octeți

package main

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
)

func comentariu(w http.ResponseWriter, r *http.Request) {
	r.Body = http.MaxBytesReader(w, r.Body, 50)
	text, err := io.ReadAll(r.Body)
	if mbe, ok := errors.AsType[*http.MaxBytesError](err); ok {
		http.Error(w, fmt.Sprintf("comentariu prea lung (maximum %d octeți)", mbe.Limit), http.StatusRequestEntityTooLarge)
		return
	}
	fmt.Fprintf(w, "comentariu salvat: %s", text)
}

func main() {
	for _, text := range []string{"Articol foarte util, mulțumesc!", strings.Repeat("bla ", 30)} {
		rec := httptest.NewRecorder()
		comentariu(rec, httptest.NewRequest("POST", "/comentarii", strings.NewReader(text)))
		fmt.Printf("%d %s\n", rec.Code, strings.TrimSpace(rec.Body.String()))
	}
}

Rezultat

200 comentariu salvat: Articol foarte util, mulțumesc!
413 comentariu prea lung (maximum 50 octeți)

Exemplul 2: Limită pentru un corp JSON

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

func main() {
	h := func(w http.ResponseWriter, r *http.Request) {
		r.Body = http.MaxBytesReader(w, r.Body, 64)
		var date map[string]any
		if err := json.NewDecoder(r.Body).Decode(&date); err != nil {
			http.Error(w, "cerere invalidă: "+err.Error(), http.StatusBadRequest)
			return
		}
		fmt.Fprint(w, "primit: ", date)
	}
	for _, corp := range []string{`{"nume":"Ana"}`, `{"descriere":"` + strings.Repeat("x", 100) + `"}`} {
		rec := httptest.NewRecorder()
		h(rec, httptest.NewRequest("POST", "/", strings.NewReader(corp)))
		fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))
	}
}

Rezultat

200 primit: map[nume:Ana]
400 cerere invalidă: http: request body too large

Greșeli frecvente

  • Creezi lectorul limitat, dar citești tot din r.Body original: limita nu se aplică. Atribuie rezultatul înapoi lui r.Body.
  • Folosești io.LimitReader: taie corpul în tăcere, fără eroare, și procesezi date trunchiate ca și cum ar fi complete.

Comenzi înrudite

  • net/http.Request.FormValue: FormValue citește un câmp dintr-un formular sau din query string; ParseForm le citește pe toate în r.Form și r.PostForm.
  • encoding/json.NewDecoder: Creează un Decoder care citește JSON dintr-un io.Reader (corpul unei cereri, un fișier); Decode citește o valoare.
  • net/http.Error: Error trimite un răspuns de eroare cu cod și mesaj text; NotFound trimite 404; StatusText dă textul unui cod (404 → Not Found).