[HTTP] 특수한 기본 응답 헤더

HTTP 요청과 응답들에는 수많은 기본 헤더들과 사용자 정의 헤더들이 존재한다.

이런 헤더들 중에는 그냥 관례적으로 사용되는 헤더도 있지만, 미리 약속되어서 브라우저에게 행동을 지시하거나 명령할 수 있는 특수한 녀석들도 존재한다.

여기서는 그런 특수 응답 헤더들에 대해서만 정리한다.

예시 코드는 Rust hyper다.

별 이유는 없다.




Location 헤더

이건 흔히 말하는 "리다이렉트"의 행동을 브라우저에게 지시하는 역할을 한다.

Location 헤더에 목적지 URL을 넣고, 300대 응답 코드를 넣으면, 브라우저가 그것을 인식해서 넘겨준다.
보통 301나 302를 많이 쓴다. 301는 영구적으로 고정된 리디렉션 경로를 정의할때 쓴다.


이렇게 말이다.
이 경우에는 naver.com가 또 www.naver.com로 2중 리다이렉트가 되긴 했다.

자세한 것은 문서에 잘 정리되어있다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location




Content-Type 헤더

이건 반환되는 데이터가 어떤 포맷을 가지고 있는지를 브라우저에게 알려준다.
Content-Type이 없어도 없는대로 브라우저가 어련히 처리하긴 하는데, 지정해주면 특수한 액션들을 강요할 수 있다.

일반 그냥 텍스트 계열의 MIME 타입을 지정해주면

그대로 텍스트 렌더링을 한다. 이건 특별할게 없다.

하지만 jpg 이미지 같은 미디어 타입을 Content-Type으로 주면

실제 포맷에 맞는 데이터가 아니라도 그 미디어 타입으로 렌더링하려고 시도를 한다.

그리고 다운로드를 하도록 강요할 수도 있다.
octet-stream은 알 수 없는 바이너리 타입을 뜻하는 MIME 타입인데, 이걸 보내면 대부분의 브라우저는 미리보기를 보여주려고 하지 않고 그냥 다운로드를 해버린다.

이렇게 말이다.

또는, 인코딩에 사용하기도 한다.
별다른 처리 없이 한글 같은 유니코드를 응답으로 전송하면

높은 확률로 텍스트가 깨진다.

이 인코딩 문제를 해결하는데는 2-3가지 정도의 해결책이 있는데, 그 중 하나가 Content-type에 인코딩 정보를 끼워넣는 것이다.

그럼 잘 읽어준다.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type




Content-Disposition

이건 컨텐츠를 어떻게 표시할지를 지시하는 특수 헤더다.

attachment로 값을 지정하면 브라우저는 데이터를 미리보기로 보여주지 않고 바로 다운로드를 한다.

파일네임까지 구겨넣으면 다운받을때의 파일명도 조정이 가능하다.

다운로드 없이 바로 보여주게 하고 싶다면 아예 안주거나 Content-Disposition을 inline으로 넣어주면 된다.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition




이건 브라우저에게 쿠키를 저장하도록 지시하는 특수한 응답 헤더다.

이런식으로 키=값 쌍을 모아서 헤더에 박아주면


브라우저가 그걸 받아서 쿠키 저장소에 저장한다.

쿠키에 대해서 너무 상세히 다루려면 지면이 너무 길어지므로, 이 정도로만 하고 넘어가겠다.
자세한 것은 문서를 참조한다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie




Content-Encoding 헤더

이건 서버에서 response body를 압축해서 보낼 경우, 그 압축 알고리즘이 무엇인지 알려주는 역할을 한다.
이걸 받으면 브라우저는 그 압축 알고리즘으로 바이트열을 해석해서 렌더링한다.

대부분의 서버 환경에서 적극적으로 지원하고, 사용된다.

별도 포스트를 참고한다.
https://blog.naver.com/sssang97/223404949782




테스트에 사용한 Rust 코드 샘플

use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;

async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    let response = Response::builder()
        .body(Full::new(Bytes::from("Hello, World!")))
        .unwrap();

    Ok(response)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // We create a TcpListener and bind it to 127.0.0.1:3000
    let listener = TcpListener::bind(addr).await?;

    // We start a loop to continuously accept incoming connections
    loop {
        let (stream, _) = listener.accept().await?;

        // Use an adapter to access something implementing `tokio::io` traits as if they implement
        // `hyper::rt` IO traits.
        let io = TokioIo::new(stream);

        // Spawn a tokio task to serve multiple connections concurrently
        tokio::task::spawn(async move {
            // Finally, we bind the incoming connection to our `hello` service
            if let Err(err) = http1::Builder::new()
                // `service_fn` converts our function in a `Service`
                .serve_connection(io, service_fn(hello))
                .await
            {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}
[package]
name = "just_test"
version = "0.1.0"
edition = "2021"

[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }