HTTP клиент и сервер в Go — фундаментальные навыки для создания современных веб-приложений и API. Go предоставляет исключительно мощный и элегантный пакет net/http, который делает создание производительных HTTP сервисов простым и интуитивным. Этот пакет используется в production-системах крупнейших технологических компаний мира.


🎯 Зачем Go разработчику HTTP?

В современной экосистеме Go HTTP используется повсеместно:

Backend разработка: REST API, GraphQL серверы, микросервисы
Cloud-native applications: Kubernetes operators, service mesh
DevOps инструменты: CI/CD системы, мониторинг, метрики
Integration tasks: Webhooks, внешние API, data pipelines
Real-time applications: WebSocket серверы, streaming APIs
Enterprise системы: B2B интеграции, внутренние сервисы

Go стал языком выбора для HTTP сервисов благодаря встроенной поддержке конкурентности, низким накладным расходам и простоте развертывания в виде одного бинарника.


📚 Пакет net/http: полный арсенал

Ключевые возможности пакета

HTTP сервер — встроенный production-ready веб-сервер
HTTP клиент — мощный клиент с поддержкой connection pooling
Middleware support — легко создавать промежуточное ПО
TLS/HTTPS — встроенная поддержка безопасных соединений
HTTP/2 — автоматическая поддержка современного протокола
Context integration — полная поддержка контекстов для cancellation

Философия HTTP в Go

В Go HTTP сервер - это не отдельная сущность, а часть вашего приложения. Нет необходимости в сложных серверах приложений как Apache или Nginx для запуска Go приложений. Скомпилированный бинарник сам является веб-сервером, готовым к production использованию.


🖥️ HTTP Сервер

Простейший HTTP сервер

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Привет, мир! Это мой первый HTTP сервер на Go\n")
    fmt.Fprintf(w, "Метод запроса: %s\n", r.Method)
    fmt.Fprintf(w, "URL: %s\n", r.URL.Path)
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprintf(w, "<h1>О проекте</h1>")
    fmt.Fprintf(w, "<p>Это учебный HTTP сервер на Go</p>")
}

func main() {
    // Регистрируем обработчики маршрутов
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/about", aboutHandler)
    
    fmt.Println("Сервер запущен на http://localhost:8080")
    
    // Запускаем сервер
    log.Fatal(http.ListenAndServe(":8080", nil))
}

JSON API сервер

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "time"
)

// Структуры данных
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

type APIResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
}

// Простое "хранилище" пользователей в памяти
var users = map[int]*User{
    1: {ID: 1, Name: "Иван Иванов", Email: "ivan@example.com", CreatedAt: time.Now()},
    2: {ID: 2, Name: "Петр Петров", Email: "petr@example.com", CreatedAt: time.Now()},
}
var nextUserID = 3

// Вспомогательные функции
func sendJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func sendError(w http.ResponseWriter, status int, message string) {
    sendJSON(w, status, APIResponse{
        Success: false,
        Error:   message,
    })
}

func sendSuccess(w http.ResponseWriter, data interface{}) {
    sendJSON(w, http.StatusOK, APIResponse{
        Success: true,
        Data:    data,
    })
}

// Обработчики API

// GET /api/users - получить всех пользователей
func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        sendError(w, http.StatusMethodNotAllowed, "Only GET allowed")
        return
    }
    
    userList := make([]*User, 0, len(users))
    for _, user := range users {
        userList = append(userList, user)
    }
    
    sendSuccess(w, userList)
}

// GET /api/users/{id} - получить пользователя по ID
func getUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        sendError(w, http.StatusMethodNotAllowed, "Only GET allowed")
        return
    }
    
    // Извлекаем ID из URL
    idStr := strings.TrimPrefix(r.URL.Path, "/api/users/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        sendError(w, http.StatusBadRequest, "Invalid user ID")
        return
    }
    
    user, exists := users[id]
    if !exists {
        sendError(w, http.StatusNotFound, "User not found")
        return
    }
    
    sendSuccess(w, user)
}

// POST /api/users - создать нового пользователя
func createUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        sendError(w, http.StatusMethodNotAllowed, "Only POST allowed")
        return
    }
    
    var user User
    
    // Парсим JSON из тела запроса
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        sendError(w, http.StatusBadRequest, "Invalid JSON")
        return
    }
    
    // Валидация
    if user.Name == "" || user.Email == "" {
        sendError(w, http.StatusBadRequest, "Name and Email are required")
        return
    }
    
    // Создаем пользователя
    user.ID = nextUserID
    user.CreatedAt = time.Now()
    users[nextUserID] = &user
    nextUserID++
    
    sendSuccess(w, user)
}

// DELETE /api/users/{id} - удалить пользователя
func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "DELETE" {
        sendError(w, http.StatusMethodNotAllowed, "Only DELETE allowed")
        return
    }
    
    idStr := strings.TrimPrefix(r.URL.Path, "/api/users/")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        sendError(w, http.StatusBadRequest, "Invalid user ID")
        return
    }
    
    if _, exists := users[id]; !exists {
        sendError(w, http.StatusNotFound, "User not found")
        return
    }
    
    delete(users, id)
    sendSuccess(w, map[string]string{"message": "User deleted"})
}

// Маршрутизатор API
func apiRouter(w http.ResponseWriter, r *http.Request) {
    switch {
    case r.URL.Path == "/api/users":
        if r.Method == "GET" {
            getUsersHandler(w, r)
        } else if r.Method == "POST" {
            createUserHandler(w, r)
        } else {
            sendError(w, http.StatusMethodNotAllowed, "Method not allowed")
        }
    case strings.HasPrefix(r.URL.Path, "/api/users/"):
        if r.Method == "GET" {
            getUserHandler(w, r)
        } else if r.Method == "DELETE" {
            deleteUserHandler(w, r)
        } else {
            sendError(w, http.StatusMethodNotAllowed, "Method not allowed")
        }
    default:
        sendError(w, http.StatusNotFound, "Endpoint not found")
    }
}

func main() {
    http.HandleFunc("/api/", apiRouter)
    
    fmt.Println("API сервер запущен на http://localhost:8080")
    fmt.Println("Доступные эндпоинты:")
    fmt.Println("  GET    /api/users      - список пользователей")
    fmt.Println("  POST   /api/users      - создать пользователя")
    fmt.Println("  GET    /api/users/{id} - получить пользователя")
    fmt.Println("  DELETE /api/users/{id} - удалить пользователя")
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

📡 HTTP Клиент

Простые HTTP запросы

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "time"
)

// Структура для создания пользователя
type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

type APIResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
}

// HTTP клиент с кастомными настройками
func createHTTPClient() *http.Client {
    return &http.Client{
        Timeout: 30 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns:        10,
            IdleConnTimeout:     60 * time.Second,
            DisableCompression:  false,
        },
    }
}

// GET запрос
func getUsers(client *http.Client, baseURL string) error {
    resp, err := client.Get(baseURL + "/api/users")
    if err != nil {
        return fmt.Errorf("ошибка GET запроса: %w", err)
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("неожиданный статус: %d", resp.StatusCode)
    }
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return fmt.Errorf("ошибка чтения ответа: %w", err)
    }
    
    var apiResp APIResponse
    if err := json.Unmarshal(body, &apiResp); err != nil {
        return fmt.Errorf("ошибка парсинга JSON: %w", err)
    }
    
    fmt.Printf("✅ GET /api/users успешно\n")
    fmt.Printf("Response: %+v\n\n", apiResp)
    return nil
}

// POST запрос
func createUser(client *http.Client, baseURL string, name, email string) (*User, error) {
    requestData := CreateUserRequest{
        Name:  name,
        Email: email,
    }
    
    jsonData, err := json.Marshal(requestData)
    if err != nil {
        return nil, fmt.Errorf("ошибка сериализации: %w", err)
    }
    
    resp, err := client.Post(
        baseURL+"/api/users",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        return nil, fmt.Errorf("ошибка POST запроса: %w", err)
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("ошибка чтения ответа: %w", err)
    }
    
    var apiResp APIResponse
    if err := json.Unmarshal(body, &apiResp); err != nil {
        return nil, fmt.Errorf("ошибка парсинга JSON: %w", err)
    }
    
    if !apiResp.Success {
        return nil, fmt.Errorf("API ошибка: %s", apiResp.Error)
    }
    
    // Преобразуем interface{} в User
    userData, err := json.Marshal(apiResp.Data)
    if err != nil {
        return nil, fmt.Errorf("ошибка преобразования данных: %w", err)
    }
    
    var user User
    if err := json.Unmarshal(userData, &user); err != nil {
        return nil, fmt.Errorf("ошибка парсинга пользователя: %w", err)
    }
    
    fmt.Printf("✅ Пользователь создан: %+v\n\n", user)
    return &user, nil
}

// DELETE запрос
func deleteUser(client *http.Client, baseURL string, userID int) error {
    url := fmt.Sprintf("%s/api/users/%d", baseURL, userID)
    
    req, err := http.NewRequest("DELETE", url, nil)
    if err != nil {
        return fmt.Errorf("ошибка создания запроса: %w", err)
    }
    
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("ошибка DELETE запроса: %w", err)
    }
    defer resp.Body.Close()
    
    if resp.StatusCode == http.StatusNotFound {
        return fmt.Errorf("пользователь не найден")
    }
    
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("неожиданный статус: %d", resp.StatusCode)
    }
    
    fmt.Printf("✅ Пользователь %d удален\n\n", userID)
    return nil
}

func main() {
    baseURL := "http://localhost:8080"
    client := createHTTPClient()
    
    fmt.Println("🚀 Тестируем HTTP клиент")
    fmt.Println("Убедитесь, что сервер запущен на http://localhost:8080")
    fmt.Println()
    
    // 1. Получаем список пользователей
    fmt.Println("1. Получаем всех пользователей:")
    if err := getUsers(client, baseURL); err != nil {
        log.Printf("Ошибка получения пользователей: %v", err)
    }
    
    // 2. Создаем нового пользователя
    fmt.Println("2. Создаем нового пользователя:")
    user, err := createUser(client, baseURL, "Анна Сидорова", "anna@example.com")
    if err != nil {
        log.Printf("Ошибка создания пользователя: %v", err)
        return
    }
    
    // 3. Снова получаем список пользователей
    fmt.Println("3. Получаем обновленный список пользователей:")
    if err := getUsers(client, baseURL); err != nil {
        log.Printf("Ошибка получения пользователей: %v", err)
    }
    
    // 4. Удаляем созданного пользователя
    fmt.Println("4. Удаляем созданного пользователя:")
    if err := deleteUser(client, baseURL, user.ID); err != nil {
        log.Printf("Ошибка удаления пользователя: %v", err)
    }
    
    // 5. Проверяем, что пользователь удален
    fmt.Println("5. Финальный список пользователей:")
    if err := getUsers(client, baseURL); err != nil {
        log.Printf("Ошибка получения пользователей: %v", err)
    }
    
    fmt.Println("✨ Тестирование завершено!")
}

⚡ Middleware и продвинутые возможности

HTTP сервер с middleware

package main

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

// Middleware для логирования запросов
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Вызываем следующий обработчик
        next(w, r)
        
        // Логируем после выполнения
        duration := time.Since(start)
        log.Printf("%s %s %v", r.Method, r.URL.Path, duration)
    }
}

// Middleware для CORS
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        
        // Обрабатываем preflight запросы
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next(w, r)
    }
}

// Middleware для аутентификации (упрощенный)
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        if token == "" {
            http.Error(w, "Authorization header required", http.StatusUnauthorized)
            return
        }
        
        if token != "Bearer secret123" {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        next(w, r)
    }
}

// Middleware для проверки Content-Type
func jsonMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "POST" || r.Method == "PUT" {
            contentType := r.Header.Get("Content-Type")
            if contentType != "application/json" {
                http.Error(w, "Content-Type must be application/json", http.StatusBadRequest)
                return
            }
        }
        
        next(w, r)
    }
}

// Композитная функция для применения множественных middleware
func applyMiddleware(handler http.HandlerFunc, middlewares ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// Обработчики
func publicHandler(w http.ResponseWriter, r *http.Request) {
    response := map[string]string{
        "message": "Это публичный эндпоинт",
        "time":    time.Now().Format(time.RFC3339),
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    response := map[string]string{
        "message": "Это защищенный эндпоинт",
        "user":    "authenticated_user",
        "time":    time.Now().Format(time.RFC3339),
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func dataHandler(w http.ResponseWriter, r *http.Request) {
    var data map[string]interface{}
    
    if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    response := map[string]interface{}{
        "message":      "Данные получены",
        "received_data": data,
        "processed_at": time.Now().Format(time.RFC3339),
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func main() {
    // Публичный эндпоинт с базовыми middleware
    http.HandleFunc("/api/public", 
        applyMiddleware(publicHandler, loggingMiddleware, corsMiddleware))
    
    // Защищенный эндпоинт
    http.HandleFunc("/api/protected", 
        applyMiddleware(protectedHandler, loggingMiddleware, corsMiddleware, authMiddleware))
    
    // Эндпоинт для работы с JSON данными
    http.HandleFunc("/api/data", 
        applyMiddleware(dataHandler, loggingMiddleware, corsMiddleware, jsonMiddleware, authMiddleware))
    
    fmt.Println("🚀 Сервер с middleware запущен на http://localhost:8080")
    fmt.Println()
    fmt.Println("Доступные эндпоинты:")
    fmt.Println("  GET  /api/public    - публичный эндпоинт")
    fmt.Println("  GET  /api/protected - защищенный эндпоинт (требует Authorization: Bearer secret123)")
    fmt.Println("  POST /api/data      - обработка JSON данных (требует аутентификацию)")
    fmt.Println()
    fmt.Println("Примеры запросов:")
    fmt.Println("  curl http://localhost:8080/api/public")
    fmt.Println("  curl -H \"Authorization: Bearer secret123\" http://localhost:8080/api/protected")
    fmt.Println("  curl -X POST -H \"Authorization: Bearer secret123\" -H \"Content-Type: application/json\" -d '{\"name\":\"test\"}' http://localhost:8080/api/data")
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

🔧 Практический пример: Веб-сервис для заметок

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "time"
)

// Модели данных
type Note struct {
    ID          int       `json:"id"`
    Title       string    `json:"title"`
    Content     string    `json:"content"`
    Tags        []string  `json:"tags"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
}

type CreateNoteRequest struct {
    Title   string   `json:"title"`
    Content string   `json:"content"`
    Tags    []string `json:"tags"`
}

type UpdateNoteRequest struct {
    Title   *string  `json:"title,omitempty"`
    Content *string  `json:"content,omitempty"`
    Tags    []string `json:"tags,omitempty"`
}

// Хранилище заметок
type NotesStorage struct {
    notes  map[int]*Note
    nextID int
}

func NewNotesStorage() *NotesStorage {
    return &NotesStorage{
        notes:  make(map[int]*Note),
        nextID: 1,
    }
}

func (ns *NotesStorage) Create(req CreateNoteRequest) *Note {
    note := &Note{
        ID:        ns.nextID,
        Title:     req.Title,
        Content:   req.Content,
        Tags:      req.Tags,
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }
    
    ns.notes[ns.nextID] = note
    ns.nextID++
    
    return note
}

func (ns *NotesStorage) GetAll() []*Note {
    notes := make([]*Note, 0, len(ns.notes))
    for _, note := range ns.notes {
        notes = append(notes, note)
    }
    return notes
}

func (ns *NotesStorage) GetByID(id int) (*Note, bool) {
    note, exists := ns.notes[id]
    return note, exists
}

func (ns *NotesStorage) Update(id int, req UpdateNoteRequest) (*Note, bool) {
    note, exists := ns.notes[id]
    if !exists {
        return nil, false
    }
    
    if req.Title != nil {
        note.Title = *req.Title
    }
    if req.Content != nil {
        note.Content = *req.Content
    }
    if req.Tags != nil {
        note.Tags = req.Tags
    }
    
    note.UpdatedAt = time.Now()
    return note, true
}

func (ns *NotesStorage) Delete(id int) bool {
    if _, exists := ns.notes[id]; !exists {
        return false
    }
    delete(ns.notes, id)
    return true
}

func (ns *NotesStorage) Search(query string) []*Note {
    var results []*Note
    query = strings.ToLower(query)
    
    for _, note := range ns.notes {
        if strings.Contains(strings.ToLower(note.Title), query) ||
           strings.Contains(strings.ToLower(note.Content), query) {
            results = append(results, note)
        }
        
        // Поиск по тегам
        for _, tag := range note.Tags {
            if strings.Contains(strings.ToLower(tag), query) {
                results = append(results, note)
                break
            }
        }
    }
    
    return results
}

// HTTP обработчики
type NotesHandler struct {
    storage *NotesStorage
}

func NewNotesHandler() *NotesHandler {
    return &NotesHandler{
        storage: NewNotesStorage(),
    }
}

func (nh *NotesHandler) sendJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func (nh *NotesHandler) sendError(w http.ResponseWriter, status int, message string) {
    nh.sendJSON(w, status, map[string]string{"error": message})
}

// GET /notes - получить все заметки или поиск
func (nh *NotesHandler) getNotes(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query().Get("search")
    
    var notes []*Note
    if query != "" {
        notes = nh.storage.Search(query)
    } else {
        notes = nh.storage.GetAll()
    }
    
    nh.sendJSON(w, http.StatusOK, map[string]interface{}{
        "notes": notes,
        "count": len(notes),
    })
}

// POST /notes - создать заметку
func (nh *NotesHandler) createNote(w http.ResponseWriter, r *http.Request) {
    var req CreateNoteRequest
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        nh.sendError(w, http.StatusBadRequest, "Invalid JSON")
        return
    }
    
    if req.Title == "" {
        nh.sendError(w, http.StatusBadRequest, "Title is required")
        return
    }
    
    note := nh.storage.Create(req)
    nh.sendJSON(w, http.StatusCreated, note)
}

// GET /notes/{id} - получить заметку по ID
func (nh *NotesHandler) getNote(w http.ResponseWriter, r *http.Request, id int) {
    note, exists := nh.storage.GetByID(id)
    if !exists {
        nh.sendError(w, http.StatusNotFound, "Note not found")
        return
    }
    
    nh.sendJSON(w, http.StatusOK, note)
}

// PUT /notes/{id} - обновить заметку
func (nh *NotesHandler) updateNote(w http.ResponseWriter, r *http.Request, id int) {
    var req UpdateNoteRequest
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        nh.sendError(w, http.StatusBadRequest, "Invalid JSON")
        return
    }
    
    note, exists := nh.storage.Update(id, req)
    if !exists {
        nh.sendError(w, http.StatusNotFound, "Note not found")
        return
    }
    
    nh.sendJSON(w, http.StatusOK, note)
}

// DELETE /notes/{id} - удалить заметку
func (nh *NotesHandler) deleteNote(w http.ResponseWriter, r *http.Request, id int) {
    if !nh.storage.Delete(id) {
        nh.sendError(w, http.StatusNotFound, "Note not found")
        return
    }
    
    nh.sendJSON(w, http.StatusOK, map[string]string{
        "message": "Note deleted successfully",
    })
}

// Главный роутер
func (nh *NotesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // CORS
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    
    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }
    
    // Логирование
    log.Printf("%s %s", r.Method, r.URL.Path)
    
    path := strings.TrimPrefix(r.URL.Path, "/notes")
    
    if path == "" || path == "/" {
        // /notes
        switch r.Method {
        case "GET":
            nh.getNotes(w, r)
        case "POST":
            nh.createNote(w, r)
        default:
            nh.sendError(w, http.StatusMethodNotAllowed, "Method not allowed")
        }
        return
    }
    
    // /notes/{id}
    if strings.HasPrefix(path, "/") {
        idStr := strings.TrimPrefix(path, "/")
        id, err := strconv.Atoi(idStr)
        if err != nil {
            nh.sendError(w, http.StatusBadRequest, "Invalid note ID")
            return
        }
        
        switch r.Method {
        case "GET":
            nh.getNote(w, r, id)
        case "PUT":
            nh.updateNote(w, r, id)
        case "DELETE":
            nh.deleteNote(w, r, id)
        default:
            nh.sendError(w, http.StatusMethodNotAllowed, "Method not allowed")
        }
        return
    }
    
    nh.sendError(w, http.StatusNotFound, "Endpoint not found")
}

func main() {
    handler := NewNotesHandler()
    
    // Создаем несколько тестовых заметок
    handler.storage.Create(CreateNoteRequest{
        Title:   "Изучить Go",
        Content: "Пройти учебник по Go и сделать несколько проектов",
        Tags:    []string{"programming", "golang", "learning"},
    })
    
    handler.storage.Create(CreateNoteRequest{
        Title:   "Купить продукты",
        Content: "Молоко, хлеб, яйца, сыр",
        Tags:    []string{"shopping", "food"},
    })
    
    http.Handle("/notes", handler)
    http.Handle("/notes/", handler)
    
    fmt.Println("🚀 Notes API запущен на http://localhost:8080")
    fmt.Println()
    fmt.Println("Доступные эндпоинты:")
    fmt.Println("  GET    /notes           - получить все заметки")
    fmt.Println("  GET    /notes?search=query - поиск заметок")
    fmt.Println("  POST   /notes           - создать заметку")
    fmt.Println("  GET    /notes/{id}      - получить заметку")
    fmt.Println("  PUT    /notes/{id}      - обновить заметку")
    fmt.Println("  DELETE /notes/{id}      - удалить заметку")
    fmt.Println()
    fmt.Println("Примеры запросов:")
    fmt.Println("  curl http://localhost:8080/notes")
    fmt.Println("  curl http://localhost:8080/notes/1")
    fmt.Println("  curl http://localhost:8080/notes?search=go")
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

🧠 Лучшие практики

1. Используй контексты для отмены запросов

func makeRequestWithTimeout(url string) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    return nil
}

2. Настраивай HTTP клиент

func createOptimizedClient() *http.Client {
    return &http.Client{
        Timeout: 30 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns:        100,
            MaxIdleConnsPerHost: 10,
            IdleConnTimeout:     90 * time.Second,
        },
    }
}

3. Валидируй входные данные

func validateCreateUserRequest(req CreateUserRequest) error {
    if req.Name == "" {
        return errors.New("name is required")
    }
    if !strings.Contains(req.Email, "@") {
        return errors.New("invalid email format")
    }
    return nil
}

🧠 Проверь себя

  • Как создать простейший HTTP сервер?
  • Как отправить POST запрос с JSON данными?
  • Что такое middleware и зачем оно нужно?
  • Как правильно обрабатывать ошибки HTTP запросов?
  • Как настроить таймауты для HTTP клиента?

📌 Главное из главы

  • http.ListenAndServe() запускает HTTP сервер
  • http.HandleFunc() регистрирует обработчики маршрутов
  • http.Get(), http.Post() для простых HTTP запросов
  • http.Client для настройки клиента (таймауты, пулы соединений)
  • Middleware позволяет добавлять сквозную функциональность
  • JSON кодирование/декодирование для API
  • Контексты для отмены длительных операций