net/http.ServeFile în Go

Trimite ca răspuns conținutul unui fișier anume, cu tipul de conținut și suportul de cache potrivite.

Sintaxă
func ServeFile(w ResponseWriter, r *Request, name string)

Ce face net/http.ServeFile

http.ServeFile(w, r, "rapoarte/octombrie.pdf") răspunde cu fișierul dat. Ca și FileServer, deduce Content-Type din extensie, răspunde la cereri condiționate (304 Not Modified) și parțiale (Range), iar pentru un fișier inexistent trimite 404.

Este potrivit când tu alegi fișierul în cod, de exemplu după verificarea drepturilor utilizatorului. Ca protecție, ServeFile respinge cererile al căror URL conține ... Dacă numele fișierului vine de la utilizator, validează-l tu (sau folosește os.Root/http.FileServer), ca să nu poată cere fișiere din afara directorului.

Pentru ca browserul să descarce fișierul în loc să-l afișeze, setează înainte antetul Content-Disposition: attachment; filename=....

Parametri

Parametru Descriere
name Calea fișierului pe disc (relativă la directorul curent sau absolută).

Ce returnează

Nimic.

Exemple

Exemplul 1: Descărcarea unei facturi

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
)

func main() {
	os.WriteFile("factura-1001.txt", []byte("Factura 1001\nTotal: 249,90 lei\n"), 0o644)

	descarca := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Disposition", `attachment; filename="factura-1001.txt"`)
		http.ServeFile(w, r, "factura-1001.txt")
	}
	rec := httptest.NewRecorder()
	descarca(rec, httptest.NewRequest("GET", "/factura", nil))

	fmt.Println("Cod:", rec.Code)
	fmt.Println("Tip:", rec.Header().Get("Content-Type"))
	fmt.Println("Disposition:", rec.Header().Get("Content-Disposition"))
	fmt.Print(rec.Body.String())
}

Rezultat

Cod: 200
Tip: text/plain; charset=utf-8
Disposition: attachment; filename="factura-1001.txt"
Factura 1001
Total: 249,90 lei

Exemplul 2: Fișier inexistent și cerere cu ..

package main

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

func main() {
	rec := httptest.NewRecorder()
	http.ServeFile(rec, httptest.NewRequest("GET", "/raport", nil), "nu-exista.pdf")
	fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))

	rec = httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/x", nil)
	req.URL.Path = "/../secret.txt"
	http.ServeFile(rec, req, "main.go")
	fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))
}

Rezultat

404 404 page not found
400 invalid URL path

Greșeli frecvente

  • Construiești calea din cererea utilizatorului: http.ServeFile(w, r, "fisiere/" + r.URL.Query().Get("f")). Cu f=../../etc/passwd se poate citi orice fișier; verifică numele (de exemplu cu filepath.IsLocal).

Comenzi înrudite

  • net/http.FileServer: FileServer servește fișierele dintr-un director (CSS, imagini, JS); StripPrefix taie prefixul URL-ului înainte.
  • net/http.ResponseWriter: Interfața prin care handler-ul construiește răspunsul: antete (Header), cod de stare (WriteHeader) și corp (Write).
  • net/http.Header: Antetele unei cereri sau ale unui răspuns: Get citește, Set înlocuiește, Add adaugă încă o valoare, Del șterge.