19 вопросов
HTTP/1.1 - по умолчанию в net/http (одно соединение - один запрос за раз без pipelining). HTTP/2 включен автоматически при TLS (h2) или через x/net/http2; мультиплексирование, один connection на много запросов. HTTP/3 (QUIC) - экспериментально в Go (quic-go). В Go сервер и клиент поддерживают HTTP/2 при наличии TLS или при явной регистрации.
import _ "golang.org/x/net/http2"
srv := &http.Server{...}
http2.ConfigureServer(srv, nil)Сервер: передать TLSConfig и сертификаты в http.Server, слушать через ListenAndServeTLS(":443", "cert.pem", "key.pem", handler). Клиент: tls.Config с RootCAs или InsecureSkipVerify (только для тестов), http.Transport с TLSClientConfig. Для production загрузка сертификатов из файлов или системы; минимум TLS 1.2.
srv := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12},
}
srv.ListenAndServeTLS("cert.pem", "key.pem")TCP: net.Listen("tcp", ":8080"), Accept(), Read/Write или bufio. UDP: net.ListenPacket("udp", ":8080"), ReadFrom/WriteTo. Для HTTP используется TCP под капотом. TCP надежный, с установкой соединения; UDP без установки, без гарантий доставки. В Go типично net.Conn для TCP, net.PacketConn для UDP (DNS, игровые протоколы).
ln, _ := net.Listen("tcp", ":8080")
conn, _ := ln.Accept()
defer conn.Close()
conn.Write([]byte("data"))Резолвинг через net.LookupHost, net.ResolveTCPAddr и при установке соединения (по умолчанию используется системный резолвер). Подмена для тестов: кастомный Resolver с полем Dial или подмена net.DefaultResolver. Для контроля над DNS (например, fallback) используют пакет net с Resolver.PreferGo и кастомным Dial.
addrs, err := net.LookupHost("example.com")
r := &net.Resolver{PreferGo: true}
ctx := context.Background()
addrs, err := r.LookupHost(ctx, "example.com")GET - получение, идемпотентный. POST - создание/действие, не идемпотентный. PUT - замена ресурса, идемпотентный. PATCH - частичное обновление. DELETE - удаление, идемпотентный. В Go в handler проверяют r.Method: switch r.Method { case http.MethodGet: ... case http.MethodPost: ... }. Для идемпотентности повторный PUT/DELETE с теми же данными дают тот же результат; при ретраях безопасны.
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", 405)
return
}2xx: 200 OK, 201 Created (и Location), 204 No Content. 3xx: 301/302 редирект. 4xx: 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity. 5xx: 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable. В Go: w.WriteHeader(code), затем w.Write(body). Для редиректа http.Redirect(w, r, url, code).
w.WriteHeader(http.StatusCreated)
w.Header().Set("Location", "/users/"+id)
json.NewEncoder(w).Encode(user)Ресурсы по URI, методы HTTP для действий, stateless, представления (JSON). В Go: маршрутизатор (mux) по пути и методу, handler возвращает JSON (Content-Type, сериализация). Вложенные ресурсы: /users/1/orders. Версионирование: префикс /v1/ или заголовок. Коды ошибок и тело ошибки в едином формате.
mux.HandleFunc("/users", handleUsers)
mux.HandleFunc("/users/", handleUserByID)GET, PUT, DELETE идемпотентны по спецификации. POST не идемпотентен; для повторных отправок используют идемпотентные ключи: клиент шлет Idempotency-Key, сервер кеширует результат по ключу и при повторном запросе возвращает тот же ответ. В Go: middleware извлекает ключ, проверяет кеш (Redis/БД), при совпадении возвращает сохраненный ответ, иначе выполняет handler и сохраняет результат.
key := r.Header.Get("Idempotency-Key")
if key != "" {
if cached, ok := cache.Get(key); ok { writeResponse(w, cached); return }
}
resp := doWork()
cache.Set(key, resp)
writeResponse(w, resp)CORS - разрешение cross-origin запросов. Браузер шлет preflight (OPTIONS); сервер отвечает Access-Control-Allow-Origin, Allow-Methods, Allow-Headers. Для простого случая: разрешить один origin или * (для публичного API). В Go middleware проверяет Origin, выставляет заголовки, для OPTIONS возвращает 204. Credentials - Allow-Credentials: true и конкретный Origin (не *).
w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
if r.Method == http.MethodOptions { w.WriteHeader(204); return }JWT: подпись и проверка claims (например, golang-jwt/jwt). После логина выдают токен, клиент шлет в Authorization: Bearer
token, err := jwt.ParseWithClaims(bearer, &claims, keyFunc)
if err != nil || !token.Valid { http.Error(w, "Unauthorized", 401); return }
r = r.WithContext(context.WithValue(r.Context(), "user", claims.Subject))Протокол по HTTP Upgrade. В стандартной библиотеке нет готового WebSocket; используют gorilla/websocket или nhooyr.io/websocket. После Upgrade получают Conn; чтение и запись в цикле (ReadMessage, WriteMessage). Важно: обрабатывать закрытие и таймауты, ограничивать размер сообщения. Для масштабирования - sticky session на балансировщике или pub/sub между инстансами.
upgrader := websocket.Upgrader{}
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
for { _, msg, err := conn.ReadMessage(); ... }Односторонний поток с сервера: соединение держится открытым, данные в формате text/event-stream (data:, id:, event:). В Go: w.Header().Set("Content-Type", "text/event-stream"), w.Flush() после каждой отправки, писать события в цикле. Клиент - EventSource. Удобно для уведомлений и лент. Таймауты и keep-alive (комментарии) для сохранения соединения.
w.Header().Set("Content-Type", "text/event-stream")
flusher := w.(http.Flusher)
fmt.Fprintf(w, "data: %s\n\n", data)
flusher.Flush()Cache-Control (max-age, no-cache, no-store, private, public), ETag, Last-Modified. При повторном запросе клиент шлет If-None-Match (ETag) или If-Modified-Since; сервер отвечает 304 Not Modified при неизменности. В Go выставляют заголовки в ответе; для 304 сравнивают If-None-Match с текущим ETag и при совпадении пишут 304 без тела.
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Header().Set("ETag", "\""+etag+"\"")
if r.Header.Get("If-None-Match") == "\""+etag+"\"" {
w.WriteHeader(304)
return
}HTTP/1.1 keep-alive - одно TCP-соединение для нескольких запросов. В Go http.Client по умолчанию переиспользует соединения (Transport с пулом). Настройка: MaxIdleConns, MaxIdleConnsPerHost, IdleConnTimeout. Не закрывать тело ответа до полного прочтения - иначе соединение не вернется в пул. Для большого числа запросов увеличить MaxIdleConnsPerHost.
client := &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 30 * time.Second,
}Через CDN отдают статику (изображения, CSS, JS) и при необходимости кешируемые API-ответы. Go-приложение - origin; отдает заголовки Cache-Control и ETag. CDN кеширует по этим заголовкам; инвалидация по URL или ключу. Для статики в Go часто используют embed или отдельный сервис; для API - короткий max-age или no-cache с ETag.
Ограничение частоты запросов по ключу (IP, user id, API key). Алгоритмы: фиксированное окно, скользящее окно, token bucket. В Go: middleware с хранилищем (map + mutex или Redis для распределенного). При превышении возвращают 429 Too Many Requests и Retry-After. Библиотеки: golang.org/x/time/rate (token bucket), ulule/limiter.
limiter := rate.NewLimiter(10, 20) // 10/sec, burst 20
if !limiter.Allow() {
http.Error(w, "Too Many Requests", 429)
return
}http.Server - сервер (Addr, Handler, ReadTimeout, WriteTimeout). http.Handler - интерфейс ServeHTTP(w, r). http.HandlerFunc - адаптер func(w, r). http.Request - метод, URL, заголовки, тело. http.ResponseWriter - WriteHeader, Write, Header(). ListenAndServe запускает сервер. Клиент: http.Get/Post или http.Client с кастомным Transport и Timeout.
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "OK")
}
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)Handler обрабатывает запрос: ServeHTTP(w, r). Middleware - функция func(http.Handler) http.Handler: оборачивает следующий handler (логирование, auth, recovery). Цепочка: h = middleware1(middleware2(finalHandler)). В Go типично передавать next в замыкание и вызывать next.ServeHTTP(w, r) после проверок. Альтернатива - chi, echo с встроенным middleware.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.Path)
next.ServeHTTP(w, r)
})
}
mux := logging(http.DefaultServeMux)httputil.ReverseProxy перенаправляет запросы на другой сервер. NewSingleHostReverseProxy(url) создает прокси к одному бэкенду. В Director меняют Host, Scheme, путь. Используют для балансировки, скрытия бэкендов, добавления заголовков. Для нескольких бэкендов свой Director с выбором по правилу (round-robin, sticky).
target, _ := url.Parse("http://backend:8080")
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Director = func(r *http.Request) {
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.Host = target.Host
}
http.ListenAndServe(":80", proxy)