GinJuniorCoding
Как обрабатывать загрузку файлов в Gin?
Используйте c.FormFile("field") для одного файла и c.MultipartForm() для нескольких; c.SaveUploadedFile() сохраняет на диск, а header.Open() позволяет читать содержимое как io.Reader.
Загрузка файлов в Gin
Gin предоставляет два метода на объекте *gin.Context для работы с файлами: FormFile для одиночного файла и MultipartForm для нескольких файлов сразу. Под капотом используется стандартный multipart/form-data парсер из пакета mime/multipart.
Одиночный файл
package main
import (
"net/http"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Ограничение размера: по умолчанию 32 МБ
r.MaxMultipartMemory = 8 << 20 // 8 МБ
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file") // "file" — имя поля формы
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Безопасное имя файла — только базовая часть пути
filename := filepath.Base(file.Filename)
dst := "./uploads/" + filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"filename": filename,
"size": file.Size,
})
})
r.Run(":8080")
}
Несколько файлов
r.POST("/upload-multiple", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
files := form.File["files"] // "files" — имя поля
for _, file := range files {
filename := filepath.Base(file.Filename)
if err := c.SaveUploadedFile(file, "./uploads/"+filename); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
c.JSON(http.StatusOK, gin.H{"uploaded": len(files)})
})
Чтение содержимого без сохранения на диск
Если файл нужно обработать в памяти (например, распарсить CSV или загрузить в S3), открывайте multipart.FileHeader напрямую:
r.POST("/parse-csv", func(c *gin.Context) {
header, _ := c.FormFile("csv")
f, err := header.Open()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer f.Close()
// f реализует io.Reader — передавайте куда угодно
// csv.NewReader(f), s3Client.PutObject(..., f, ...)
c.JSON(http.StatusOK, gin.H{"status": "parsed"})
})
Ключевые поля multipart.FileHeader
file.Filename— оригинальное имя файла от клиента (не доверяйте без санитизации).file.Size— размер в байтах (int64).file.Header— MIME-заголовки, включаяContent-Type.
Подводные камни
- Path traversal: клиент может передать
../../etc/passwdв качестве имени файла. Всегда применяйтеfilepath.Base()и дополнительно проверяйте расширение. - MaxMultipartMemory по умолчанию 32 МБ: весь файл помещается в RAM. Для больших файлов уменьшите лимит и стримьте через
header.Open()напрямую в S3/диск. - Content-Type не верифицируется: поле
file.Header.Get("Content-Type")устанавливает клиент — читайте магические байты файла черезhttp.DetectContentType. - Директория назначения должна существовать:
SaveUploadedFileне создаёт промежуточные каталоги — предварительно вызовитеos.MkdirAll. - Коллизии имён: несколько пользователей могут загрузить файл с одинаковым именем. Используйте UUID или хэш содержимого как префикс.
- Отсутствие поля формы:
FormFileвозвращает ошибкуhttp: no such file— обрабатывайте её явно, не игнорируйте через_. - Таймаут соединения: при медленной загрузке большого файла клиент может отвалиться раньше, чем файл запишется. Устанавливайте корректные
ReadTimeoutна сервере.
Common mistakes
- Давать ответ про загрузка файлов в Gin только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «загрузка файлов в Gin»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать загрузка файлов в Gin с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает загрузка файлов в Gin и где это используется в Go-коде.
- Связывает загрузка файлов в Gin с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.