HTTP під капотом: як працюють redirect і статус-коди 3xx
В минулій статті про HTTP ми розібралися, як працює HTTP-сервер на базовому рівні, сьогодні ми зосередимось на тому, як працюють статус-коди 3xx Redirection.
3xx Redirection
3xx Redirection це група статус-кодів, за допомогою яких сервер повідомляє, що клієнту потрібно виконати додаткову дію для отримання ресурсу. У більшості випадків - перейти за іншою адресою.
Згідно з RFC 7231 їх всього 9, тому розглянемо кожен.
300 Multiple Choices
Цей статус-код каже клієнту, що сервер має декілька варіантів і клієнт повинен сам обрати, який йому потрібен. Браузер автоматично не обирає шлях перенаправлення, клієнт повинен зробити це сам.
Наприклад, клієнт надсилає запит на /document, а сервер має два формати відображення документа XML та JSON і не знає, який саме потрібен користувачу, тому він може відправити відповідь у форматі HTML:
<ul>
<li><a href="/document.xml">XML</a></li>
<li><a href="/document.json">JSON</a></li>
</ul>
Браузер відмалює цей HTML та запропонує клієнту самому обрати варіант.
Приклад коду, що реазізує статус-код 300:
const HTTPStatusMultipleChoices HTTPStatus = "300 Multiple Choices"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
const body = `<ul>
<li><a href="/document.xml">XML</a></li>
<li><a href="/document.json">JSON</a></li>
</ul>`
return &HTTPResponse{
StatusCode: HTTPStatusMultipleChoices,
Headers: map[string]string{
"Connection": "close",
"Content-Type": "text/html",
},
Body: []byte(body),
}
}
Відповідь з статус-кодом 300 за стандартом кешується і браузер відправить всього один запит на /document, в наступні рази сторінка буде відмальовуватись з кешованих даних.
Навіщо взагалі існує цей статус-код?
Чому замість нього просто не використати 200 OK?
В описі протоколу HTTP розробники вирішили явно виділити ситуацію, коли сервер має декілька відповідей.
На практиці цей статус-код майже не використовується, і в більшості випадків сервери повертають 200 OK.
301 Moved Permanently
Цей статус-код каже клієнту, що ресурс переміщений назавжди на нову адресу.
Гарний приклад його використання - це перенаправлення з HTTP на HTTPS, майже завжди, коли ми намагаємось відкрити сайт за допомогою HTTP (порт 80) нас перенаправляє на HTTPS (порт 443):

Коли браузер отримує статус-код 301 він автоматично надсилає GET-запит за адресою, вказаною в заголовку Location:

Також браузер кешує відповідь сервера і при наступних запитах одразу надсилає запит на закешований Location:

Наш сервер після кешування не отримає запит на /redirect:

Код, що реазізує статус-код 301:
const HTTPStatusMovedPermanently HTTPStatus = "301 Moved Permanently"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
return &HTTPResponse{
StatusCode: HTTPStatusMovedPermanently,
Headers: map[string]string{
"Location": "/",
"Connection": "close",
},
Body: nil,
}
}
302 Found
На відміну від 301 Moved Permanently, 302 Found використовується в тих випадках, коли ресурс тимчасово перемістився за іншою адресою.
Браузер зазвичай не кешує таку відповідь, тому кожен раз надсилає запит і потім робить GET-запит на отриманий Location.
Код, що реазізує статус-код 302:
const HTTPStatusFound HTTPStatus = "302 Found"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
return &HTTPResponse{
StatusCode: HTTPStatusFound,
Headers: map[string]string{
"Location": "/",
"Connection": "close",
},
Body: nil,
}
}
303 See Other
Фактично 303 це той самий 302. Але 303 See Other в RFC був створений для того, щоб повідомити браузеру, що результат виконаної дії потрібно отримувати за іншим шляхом.
Гарним прикладом, коли слід використовувати 303 See Other є створення запису в адмін-панелі. Наприклад в нас є адмін-панель з формою для створення картки товару і після того, як сервер запише дані від користувача в БД він повинен відправити відповідь з статус-кодом 303 та посиланням на картку товару.
POST /create-product -> GET /product/123
Код, що реазізує статус-код 303:
const HTTPStatusSeeOther HTTPStatus = "303 See Other"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
return &HTTPResponse{
StatusCode: HTTPStatusSeeOther,
Headers: map[string]string{
"Location": "/",
"Connection": "close",
},
Body: nil,
}
}
304 Not Modified
Статус-код 304 Not Modified використовується для валідації кешу.
Браузер, маючи вже збережену копію ресурсу, може вирішити перевірити її актуальність і надсилає запит на сервер, вказуючи дані свого кешу. Сервер перевіряє, чи ця копія все ще є актуальною.
Якщо ресурс не змінився, сервер повертає відповідь 304 Not Modified, і браузер використовує дані зі свого кешу. Якщо ж ресурс було змінено, сервер повертає 200 OK разом з новим тілом відповіді.
Цей механізм дозволяє уникнути повторної передачі даних і значно зменшує мережевий трафік. Детальніше ми розглянемо його в окремій статті про кешування.
305 Use Proxy
Статус-код 305 Use Proxy історично використовувався для того, щоб повідомити клієнту про необхідність надсилати запити через вказаний проксі-сервер.
Проте через серйозні проблеми з безпекою цей статус-код був оголошений застарілим, і сучасні браузери повністю його ігнорують.
306 Switch Proxy
Статус-код 306 Switch Proxy був зарезервований у ранніх версіях стандарту, як ідея для майбутнього механізму перемикання проксі-серверів.
Він планувався як доповнення до 305 Use Proxy, проте, так само як і 305, цей статус-код так і не отримав практичного застосування. Сьогодні він офіційно позначений як Unused, і сучасні браузери не використовують і не обробляють його.
307 Temporary Redirect
Статус-код 307 Temporary Redirect схожий на 302 Found, але з важливою відмінністю: під час перенаправлення зберігається оригінальний HTTP-метод і тіло запиту.
Тобто, якщо початковий запит був POST, то запит на адресу з заголовка Location також буде виконаний як POST.

Код, що реазізує статус-код 307:
const HTTPStatusTemporaryRedirect HTTPStatus = "307 Temporary Redirect"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
return &HTTPResponse{
StatusCode: HTTPStatusTemporaryRedirect,
Headers: map[string]string{
"Location": "/",
"Connection": "close",
},
Body: nil,
}
}
308 Permanent Redirect
308 Permanent Redirect є альтернативою 301 Moved Permanently, так само як і 307 Temporary Redirect зберігає оригінальний HTTP-метод і тіло запиту.
Але на відміну від 301 Moved Permanently, браузери зазвичай не кешують цей статус-код автоматично, якщо це не вказано явно.
Код, що реазізує статус-код 308:
const HTTPStatusPermanentRedirect HTTPStatus = "308 Permanent Redirect"
func handleRequest(request *HTTPRequest) *HTTPResponse {
...
return &HTTPResponse{
StatusCode: HTTPStatusPermanentRedirect,
Headers: map[string]string{
"Location": "/",
"Connection": "close",
},
Body: nil,
}
}
Додаткові ресурси
Резюме
Сьогодні ми ознайомилися з групою статус-кодів 3xx Redirection, розібрали їх роль і побачили, як саме браузер використовує їх для виконання перенаправлень.