Как установить HTTP-заголовки в Go (CORS, Content-Type и другие)
Проблема: CORS и порядок установки заголовков
Ваш код не работает по двум основным причинам:
- Preflight-запросы (OPTIONS) — браузер перед реальным POST-запросом отправляет OPTIONS-запрос, чтобы проверить, разрешен ли CORS. Ваш сервер не обрабатывает этот метод.
- Порядок заголовков — в Go заголовки нужно устанавливать до вызова
Write()илиWriteHeader(). После записи тела ответа заголовки игнорируются.
Правильное решение: middleware для CORS
Лучший способ — создать middleware, который перехватывает все запросы и добавляет нужные заголовки. Вот рабочий пример:
package main
import (
"encoding/json"
"log"
"net/http"
)
// corsMiddleware добавляет CORS-заголовки ко всем ответам
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(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")
w.Header().Set("Access-Control-Max-Age", "86400") // кэширование preflight на 24 часа
// Обработка preflight-запроса (OPTIONS)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// Передаем управление следующему обработчику
next.ServeHTTP(w, r)
})
}
// apiHandler — ваш основной обработчик
func apiHandler(w http.ResponseWriter, r *http.Request) {
// Важно: заголовки уже установлены в middleware!
w.Header().Set("Content-Type", "application/json")
response := map[string]string{"message": "Hello"}
json.NewEncoder(w).Encode(response)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", apiHandler)
// Оборачиваем весь mux в middleware
handler := corsMiddleware(mux)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Ключевые моменты
- Middleware — функция, которая оборачивает ваш обработчик и выполняется для каждого запроса.
- Preflight (OPTIONS) — обязательно обрабатывайте этот метод и возвращайте 200 OK без тела.
- Порядок заголовков: все
Header().Set()должны быть вызваны доWriteHeader()илиWrite(). В middleware это происходит до вызоваnext.ServeHTTP(). - Content-Type — если вы используете
json.NewEncoder, он сам установит правильный Content-Type. Если пишете вручную черезfmt.Fprintf, ставьте заголовок явно.
Более гибкий вариант: обработка конкретных origin
Если вам нужно разрешить только определенные домены, а не *:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
allowedOrigins := map[string]bool{
"http://localhost:3000": true,
"https://mysite.com": true,
}
if allowedOrigins[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
// остальная логика...
})
}
Также рекомендую использовать готовые библиотеки, например rs/cors, если проект станет больше — они уже учитывают все edge cases.
Альтернатива: использование библиотеки rs/cors
Если вы не хотите писать middleware вручную, есть отличная библиотека github.com/rs/cors. Она уже решает все проблемы с CORS, включая preflight, заголовки и безопасность.
Установка:
go get github.com/rs/cors
Пример использования:
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/rs/cors"
)
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "Hello"})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", apiHandler)
// Настройка CORS через библиотеку
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:3000", "https://mysite.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 86400,
})
handler := c.Handler(mux)
log.Fatal(http.ListenAndServe(":8080", handler))
}
Библиотека автоматически:
- Обрабатывает OPTIONS-запросы
- Устанавливает правильные заголовки
- Поддерживает сложные сценарии (credentials, Vary header)
Это упрощает код и снижает риск ошибок. Для большинства проектов рекомендую именно этот подход.