#
LD31D
HTTP під капотом: як працюють redirect і статус-коди 3xx

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, розібрали їх роль і побачили, як саме браузер використовує їх для виконання перенаправлень.