Какие базовые HTTP методы существуют и их альтернативы
Что проверяют
Понимаете ли вы семантику методов и как она отражается в коде на Go: в роутинге, клиенте и повторах запросов. Следом обычно спрашивают про идемпотентность и про то, как сделать безопасным повтор POST.
Как отвечать
Методы с их свойствами по RFC 9110:
- GET: чтение, безопасный и идемпотентный, кэшируется;
- HEAD: GET без тела ответа, дешёвая альтернатива для проверки существования ресурса или
Content-Length; - POST: создание и действия, не идемпотентный;
- PUT: полная замена ресурса по известному адресу, идемпотентный;
- PATCH: частичное изменение, альтернатива PUT, по стандарту не идемпотентный;
- DELETE: удаление, идемпотентный по состоянию, хотя второй вызов может вернуть 404;
- OPTIONS: список поддерживаемых методов и preflight в CORS.
В Go для них есть константы http.MethodGet, http.MethodPost и остальные, лучше писать их, а не строки. С Go 1.22 стандартный http.ServeMux понимает метод в шаблоне: mux.HandleFunc("GET /users/{id}", h), параметр читается через r.PathValue("id"). Шаблон с GET обслуживает и HEAD, а на неподходящий метод мультиплексор сам вернёт 405 с заголовком Allow. До этого метод проверяли вручную через r.Method или брали chi и gorilla/mux.
На клиенте запрос собирают через http.NewRequestWithContext(ctx, http.MethodPatch, url, body). Стандартный http.Transport при обрыве переиспользованного соединения сам повторяет только запросы, которые считает безопасными для повтора: GET, HEAD, OPTIONS, TRACE или запрос с заголовком Idempotency-Key. Этим же заголовком делают идемпотентным POST на своей стороне: сервер хранит ключ и на дубль отдаёт сохранённый ответ.
Альтернативы шире списка: туннелирование через POST с X-HTTP-Method-Override и RPC-стиль, где gRPC вообще ходит только POST поверх HTTP/2.
Где спотыкаются
Пишут собственные ретраи на все методы подряд. Повтор POST на создание платежа без ключа идемпотентности спишет деньги дважды.
Путают идемпотентность с безопасностью. DELETE идемпотентный, но совсем не безопасный.
Не закрывают resp.Body в клиенте и не дочитывают его. Соединение не возвращается в пул, и под нагрузкой сервис упирается в лимит сокетов.