Все главы учебника
Содержание учебника
Глава 02 / Бесплатный backend-путь

HTTP: принять задание и показать результат

30 мин чтенияКонтент v0.8.0

От функции к серверу

Сервер — запущенная программа, которая принимает запросы и отправляет ответы. HTTP задаёт форму этих сообщений: метод, путь, заголовки и тело. POST /jobs создаёт новое задание, GET /jobs/{id} читает существующее. Тело JSON — текст с именованными полями; сервер должен разобрать его и проверить до изменения состояния.

В этом этапе храните задания в памяти одного процесса. Модель задания содержит id, исходный text, status, result, attempts, error. Возможные состояния: queued — ожидает, running — выполняется, done — успешно завершено, failed — завершилось ошибкой. Настоящий фон и границы параллелизма появятся в следующем этапе. Пока обработка может быть последовательной: не делайте долгую работу внутри HTTP-обработчика окончательной архитектурой.

Что прочитать

Нужны структуры, map, ошибки и тесты из основ Go. Прочитайте из чего состоит приложение и HTTP и договор API. Перед практикой разберите net/http, encoding/json, http.Handler, http.ResponseWriter и *http.Request: ServeHTTP получает запрос и записывает статус, заголовки и тело ответа. В тестах httptest.NewRequest создаёт запрос без сети, а httptest.NewRecorder сохраняет ответ обработчика. Их роль — проверить ваш API без свободного порта и отдельного процесса.

Реализуйте договор

Найдите New(Config) и HTTP-обработчик приложения в стартовом проекте. New возвращает приложение или ошибку подготовки. Добавьте создание задания и чтение по id. Успешный POST получает JSON {"text":"hello go"} и возвращает 202 с записью задания. К моменту чтения результат может ещё отсутствовать: 202 сообщает о принятии, а не гарантирует done.

Для неизвестного id верните 404. Неверный JSON, отсутствующее поле text, значение другого типа и текст, пустой после удаления краевых пробелов, получают 400. На первом этапе функция Analyze принимала пустую строку; API сознательно задаёт более узкий допустимый вход. Неподдерживаемый метод также не должен давать успешное создание. Если вы уже начали записывать тело ответа, поздно менять его статус; задавайте заголовки и статус до тела.

В ServeHTTP метод доступен как r.Method, путь — как r.URL.Path, а тело — как r.Body. Тело читается последовательно; для JSON нужен декодер, который читает его поток. Вот отдельная функция разбора входа. Поместите её вне main и вне ServeHTTP; подключите encoding/json, errors, net/http и strings:

func readText(r *http.Request) (string, error) {
    var input struct {
        Text string `json:"text"`
    }
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        return "", err
    }
    if strings.TrimSpace(input.Text) == "" {
        return "", errors.New("text must not be empty")
    }
    return input.Text, nil
}

&input позволяет декодеру заполнить структуру. Функция сохраняет исходный текст: это понадобится для ключа повторного запроса. Вы сами добавляете вызов в обработчик, запись задания и ответ. Для записи JSON подключите encoding/json, задайте w.Header().Set("Content-Type", "application/json"), затем статус через w.WriteHeader и кодирование записи через json.NewEncoder(w).Encode. Ошибку кодирования тоже обработайте: повторно отправить другой статус после записи ответа уже нельзя.

Для самостоятельного теста создайте запрос и вызовите приложение через httptest. Фрагмент размещается внутри функции TestStage02... файла _test.go; app здесь — уже созданное приложение. Подключите net/http/httptest, net/http и strings:

request := httptest.NewRequest(http.MethodPost, "/jobs", strings.NewReader(`{"text":"hello go"}`))
recorder := httptest.NewRecorder()
app.ServeHTTP(recorder, request)

Проверьте recorder.Code и JSON в recorder.Body.Bytes(). Такой тест вызывает реальный обработчик, но не поднимает сервер на порту. Создание приложения и его остановку после теста вы добавляете по публичным методам из каркаса.

Запустите сервер:

go run ./cmd/server

По умолчанию он слушает 127.0.0.1:8080. 127.0.0.1 означает этот компьютер. Оставьте сервер в первом терминале; во втором отправьте запросы. В PowerShell используйте curl.exe, чтобы вызвать именно curl:

curl -i -X POST http://127.0.0.1:8080/jobs -H 'Content-Type: application/json' -d '{"text":"hello go"}'
curl -i http://127.0.0.1:8080/jobs/ID

Замените ID номером из ответа. Если curl недоступен, проверяйте API через httptest и включённый в проект локальный клиент нагрузки на последнем этапе. Пример JSON здесь записан для оболочек с одинарными кавычками; в PowerShell при проблемах с кавычками сохраните тело в файл и передайте --data-binary @request.json.

Проверьте API

go test ./... -run '^TestStage0[12]' -count=1

Дополните свой тест: создайте задание, извлеките id из JSON-ответа, прочитайте его; затем запросите неизвестный id. Проверьте статус и поля, а не только отсутствие ошибки. Объясните, почему повтор одного POST пока создаёт второе задание. Защита от повторов появится на пятом этапе.

Типичные ошибки: отдавать 200 на любое обращение; писать JSON с помощью склейки строк; принимать сломанный JSON как пустой текст; считать id результатом вычисления; запускать два сервера на одном порту. После остановки и нового запуска данные в памяти пропадают — это ожидаемое ограничение текущего этапа.

Когда идти дальше

Покажите успешный запрос, неверный JSON и неизвестный id. Проследите один запрос от метода и пути до записи в памяти и ответа. Автотест проверяет договор, но объяснение последовательности остаётся вашей задачей. Дальше перенесём обработку в ограниченный пул.

Самопроверка этапа

Отмечайте результат после своей проверки. Открытие главы ничего не завершает. Отметки сохраняются в этом браузере и не являются независимой аттестацией.

Что выполнено

Для завершения отметьте все критерии и добавьте объяснение.