Handling JSON Post Request в Go: 5 фатальных ошибок и как их избежать

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Handling JSON Post Request в Go: почему даже опытные разработчики попадают в ловушки

Вы когда-нибудь писали на Go сервер, который должен принимать JSON через POST запрос, и вдруг получали пустую структуру или фатальную ошибку? Если да — вы не одиноки. Вопрос "Handling JSON Post Request in Go" на Stack Overflow собрал десятки тысяч просмотров и сотни ответов. Это настоящая "болевая точка" сообщества.

В этой статье мы не просто разберем, как правильно обрабатывать JSON POST запросы. Мы пройдемся по топовым граблям, на которые наступают новички и даже профи. Вы узнаете, как избежать типичных ошибок, написать чистый код и не тратить часы на дебаг. Поехали!

Почему JSON и Go — это любовь с первого байта?

Go (или Golang) создавался для современных веб-сервисов. Встроенный пакет encoding/json — мощный инструмент. Но, как и любой мощный инструмент, он требует правильного обращения. Самая частая проблема — несоответствие между JSON, который присылает клиент, и структурой, которую ожидает сервер.

Давайте начнем с базы — правильной структуры обработчика.

Ошибка №1: Забыть закрыть тело запроса

Первое, что вы должны сделать в любом обработчике POST — обязательно закрыть тело запроса. Это не просто хороший тон, это гарантия, что ваш сервер не утечет памятью.

package main

import (
    "encoding/json"
    "net/http"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func handler(w http.ResponseWriter, r *http.Request) {
    // ВАЖНО: закрываем Body
    defer r.Body.Close()

    // ... остальной код
}

Многие новички пропускают эту строку, и сервер начинает "кушать" память. Всегда используйте defer r.Body.Close() сразу после получения запроса.

Ошибка №2: Игнорировать проверку метода запроса

Ваш обработчик должен явно проверять, что пришел именно POST, а не GET или PUT. Иначе вы рискуете получить панику или странное поведение.

func handler(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    // ... дальше работаем с JSON
}

Это простое правило спасет вас от множества головных болей. Всегда проверяйте метод — это базовая гигиена кода.

Ошибка №3: Использовать нестрогий JSON декодер

По умолчанию json.Decoder в Go не выбрасывает ошибку, если в JSON есть лишние поля, которые не описаны в вашей структуре. Это может привести к тому, что вы "проглотите" невалидные данные.

Рецепт спасения — использовать json.Decoder с опцией DisallowUnknownFields:

func handler(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    var user User

    decoder := json.NewDecoder(r.Body)
    // Включаем строгий режим
    decoder.DisallowUnknownFields()

    err := decoder.Decode(&user)
    if err != nil {
        http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
        return
    }

    // Если в JSON были лишние поля — получим ошибку
    // Это защита от случайных опечаток в API

    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(user)
}

Этот подход особенно полезен, когда вы разрабатываете публичное API. Клиенты будут благодарны за четкие ошибки.

Ошибка №4: Не проверять, что JSON не пустой

Представьте: клиент отправляет пустое тело запроса. Ваш код без проблем декодирует пустой JSON {} и создаст структуру с нулевыми значениями. Это может быть не тем, что вы ожидаете.

Лучшая практика — проверять, что тело не пустое, и что все обязательные поля заполнены:

func handler(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    // Проверяем, что тело не пустое
    if r.Body == http.NoBody {
        http.Error(w, "Request body is empty", http.StatusBadRequest)
        return
    }

    var user User
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()

    err := decoder.Decode(&user)
    if err != nil {
        http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
        return
    }

    // Дополнительная валидация полей
    if user.Name == "" || user.Email == "" {
        http.Error(w, "Name and Email are required", http.StatusBadRequest)
        return
    }

    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{
        "message": "User created successfully",
    })
}

Обратите внимание: мы используем map[string]string для ответа. Это гибкий способ вернуть произвольный JSON без создания дополнительных структур.

Ошибка №5: Забывать про лимиты размера тела

Если вы не ограничите размер входящего запроса, злоумышленник может завалить ваш сервер гигантским JSON. Используйте http.MaxBytesReader:

func handler(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    // Ограничиваем размер тела до 1MB
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

    // ... дальше ваш обычный код
}

Это простое действие защитит ваш сервер от атак типа "отказ в обслуживании" (DoS).

Полный рабочий пример обработчика

Соберем все вместе в один чистый, готовый к использованию код:

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()

    // Проверка метода
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    // Лимит размера
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

    // Проверка на пустое тело
    if r.Body == http.NoBody {
        http.Error(w, "Request body is empty", http.StatusBadRequest)
        return
    }

    var user User
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()

    if err := decoder.Decode(&user); err != nil {
        http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
        return
    }

    // Валидация полей
    if user.Name == "" || user.Email == "" {
        http.Error(w, "Name and Email are required", http.StatusBadRequest)
        return
    }

    // Успешный ответ
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(map[string]string{
        "status": "success",
        "name":   user.Name,
    })
}

func main() {
    http.HandleFunc("/user", createUserHandler)
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Тестируем наш обработчик

Чтобы проверить, что все работает, использу

Блоги

Рекомендаций по книгам