HTTP-сервер, JSON API и итоговый проект

HTTP-сервер на net/http: регистрация обработчиков и формирование ответа

Содержание курса

HTTP-сервис приветствия

Небольшой веб-сервис на Go — это, пожалуй, самый наглядный способ увидеть, как стандартная библиотека превращает несколько десятков строк кода в живую сетевую программу. Здесь нет ничего лишнего: маршрутизатор, обработчики, порт — и сервер готов принимать запросы.

В центре задачи стоит функция newMux, которая собирает и возвращает свежий мультиплексор с двумя маршрутами. Первый отвечает за здоровье сервиса и позволяет внешним наблюдателям убедиться, что процесс жив. Второй — дружелюбно приветствует любого, кто обратится по нужному адресу. Важно понимать, что каждый обработчик обязан вести себя предсказуемо: правильный метод — правильный ответ, неправильный метод — явный отказ, а несуществующий путь должен честно сообщать о своём отсутствии. Порядок выставления заголовков тоже имеет значение: они должны быть готовы прежде, чем клиент увидит статус или тело ответа.

Что нужно сделать

Допишите service.go. Функция newMux должна при каждом вызове возвращать новый *http.ServeMux. На нём нужны два маршрута: GET /health отвечает строкой ok, а GET /hello — строкой Hello, Go!. Для другого метода на /hello верните 405 и текст method not allowed. Неизвестный путь должен остаться обычным 404 от ServeMux.

Перед записью статуса и тела установите Content-Type: text/plain; charset=utf-8. В main запустите сервер на :18080 и обработайте ошибку ListenAndServe. Здесь ещё нет JSON API: задача только про mux, обработчик и HTTP-ответ.

О данных в ответах

Используйте учебные данные. Не вставляйте пароли, токены, ключи доступа, паспортные и банковские данные, а также персональные данные других людей. Политика обработки данных.

Проверяется тестами (5)· скрытых: 2
  • Public go test ./...
  • health
    GET /health200
  • hello
    GET /hello200
Go-проект · service.go

Сначала выполняется go test ./..., затем сервис собирается и проверяется HTTP-запросами. Внешняя сеть и скачивание модулей отключены.

service_test.go
package main

import (
	"context"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"
)

func TestNewMux(t *testing.T) {
	if newMux() == newMux() { t.Fatal("newMux must return a fresh mux") }
	for _, tc := range []struct{method, path string; status int; body string}{
		{http.MethodGet, "/health", http.StatusOK, "ok\n"},
		{http.MethodGet, "/hello", http.StatusOK, "Hello, Go!\n"},
		{http.MethodPost, "/hello", http.StatusMethodNotAllowed, "method not allowed\n"},
		{http.MethodGet, "/missing", http.StatusNotFound, "404 page not found\n"},
	} {
		t.Run(tc.method+tc.path, func(t *testing.T) {
			req := httptest.NewRequest(tc.method, tc.path, nil)
			rr := httptest.NewRecorder()
			newMux().ServeHTTP(rr, req)
			resp := rr.Result(); defer resp.Body.Close()
			body, _ := io.ReadAll(resp.Body)
			if resp.StatusCode != tc.status || string(body) != tc.body { t.Fatalf("status=%d body=%q", resp.StatusCode, body) }
			if tc.path != "/missing" && !strings.HasPrefix(resp.Header.Get("Content-Type"), "text/plain") { t.Fatalf("content-type=%q", resp.Header.Get("Content-Type")) }
		})
	}
}

func TestMainReportsListenError(t *testing.T) {
	listener, err := net.Listen("tcp", "127.0.0.1:18080")
	if err != nil { t.Skipf("cannot reserve port 18080: %v", err) }
	defer listener.Close()
	binary := filepath.Join(t.TempDir(), "service")
	if runtime.GOOS == "windows" { binary += ".exe" }
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil { t.Fatalf("build: %v\n%s", err, output) }
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	run := exec.CommandContext(ctx, binary)
	output, err := run.CombinedOutput()
	if ctx.Err() != nil { t.Fatal("program did not stop after ListenAndServe failed") }
	if err == nil { t.Fatal("program ignored the ListenAndServe error and exited successfully") }
	if strings.TrimSpace(string(output)) == "" { t.Fatal("program did not report the ListenAndServe error") }
}
РешениеGo · Go 1.27 (Modal)

Отправьте решение, чтобы увидеть результаты тестов.