🌍 네트워크

HTTP/2

HTTP/2

다중화, 헤더 압축, 서버 푸시 지원. HTTP/1.1 개선.

📖 상세 설명

HTTP/2는 2015년 IETF에서 표준화된 차세대 웹 프로토콜로, HTTP/1.1의 성능 한계를 극복하기 위해 설계되었습니다. Google의 SPDY 프로토콜을 기반으로 개발되었으며, 바이너리 프레이밍(Binary Framing)을 도입하여 텍스트 기반이던 HTTP/1.1보다 훨씬 효율적인 데이터 전송이 가능합니다. 현재 전 세계 웹사이트의 50% 이상이 HTTP/2를 사용하고 있습니다.

HTTP/2의 핵심 기능은 멀티플렉싱(Multiplexing)입니다. HTTP/1.1에서는 하나의 TCP 연결당 하나의 요청만 처리할 수 있어, 브라우저가 6개의 병렬 연결을 열어야 했습니다. 반면 HTTP/2는 단일 연결에서 수백 개의 요청을 동시에 처리할 수 있어, Head-of-Line Blocking 문제를 해결하고 페이지 로딩 속도를 30-50% 향상시킵니다.

HPACK 헤더 압축은 HTTP/2의 또 다른 중요한 특징입니다. HTTP/1.1에서는 매 요청마다 동일한 헤더(Cookie, User-Agent 등)를 반복 전송했지만, HTTP/2는 헤더 테이블을 유지하고 변경된 부분만 전송합니다. 이를 통해 헤더 크기를 85-90%까지 줄일 수 있으며, 특히 모바일 환경에서 큰 성능 향상을 보입니다.

서버 푸시(Server Push)는 클라이언트가 요청하기 전에 서버가 필요한 리소스를 미리 전송하는 기능입니다. HTML 파일을 요청하면 서버가 연관된 CSS, JavaScript 파일을 함께 푸시할 수 있습니다. 다만 실제 사용에서는 캐시 무효화 문제 등으로 Chrome 106부터 서버 푸시 지원이 제거되어, 현재는 103 Early Hints나 preload 태그를 권장합니다.

💻 코드 예제

# Nginx HTTP/2 설정
# /etc/nginx/nginx.conf 또는 /etc/nginx/sites-available/default

server {
    listen 443 ssl http2;  # HTTP/2 활성화 (TLS 필수)
    listen [::]:443 ssl http2;
    server_name example.com;

    # SSL 인증서 설정
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # HTTP/2 최적화 설정
    http2_max_concurrent_streams 128;  # 동시 스트림 수
    http2_push_preload on;  # preload 링크 자동 푸시

    # HTTP/2 서버 푸시 예시 (Chrome 106 이후 미지원)
    location / {
        root /var/www/html;
        http2_push /css/style.css;
        http2_push /js/app.js;
    }

    # 103 Early Hints 설정 (서버 푸시 대안)
    location / {
        add_header Link "; rel=preload; as=style";
        add_header Link "; rel=preload; as=script";
    }
}

# HTTP → HTTPS 리다이렉트
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

# HTTP/2 지원 확인: curl -I --http2 https://example.com
# Apache HTTP/2 설정
# /etc/apache2/apache2.conf 또는 httpd.conf

# HTTP/2 모듈 활성화
# sudo a2enmod http2
LoadModule http2_module modules/mod_http2.so

# 전역 HTTP/2 설정
Protocols h2 h2c http/1.1
H2Direct on
H2MaxSessionStreams 100
H2WindowSize 65535

# 가상 호스트 설정
<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/html

    # SSL 설정
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    # HTTP/2 활성화
    Protocols h2 http/1.1

    # HTTP/2 서버 푸시
    <Location />
        Header add Link "</css/style.css>; rel=preload; as=style"
        Header add Link "</js/app.js>; rel=preload; as=script"
    </Location>

    # HTTP/2 우선순위 설정
    H2Push on
    H2PushPriority * after
    H2PushPriority text/css before
    H2PushPriority application/javascript interleaved

    # 로그 설정
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{SSL_PROTOCOL}x %{HTTP2}e" http2_log
</VirtualHost>

# 확인: apachectl -M | grep http2
const http2 = require('http2');
const fs = require('fs');
const path = require('path');

// HTTP/2 서버 생성 (TLS 필수)
const server = http2.createSecureServer({
    key: fs.readFileSync('server.key'),
    cert: fs.readFileSync('server.crt'),
    allowHTTP1: true  // HTTP/1.1 폴백 허용
});

// 스트림 핸들러
server.on('stream', (stream, headers) => {
    const reqPath = headers[':path'];
    const method = headers[':method'];

    console.log(`${method} ${reqPath}`);

    if (reqPath === '/') {
        // 메인 HTML 응답
        stream.respond({
            'content-type': 'text/html; charset=utf-8',
            ':status': 200
        });

        // 서버 푸시 (권장하지 않음 - 대안 사용)
        // 103 Early Hints 방식으로 대체 권장
        stream.pushStream({ ':path': '/style.css' }, (err, pushStream) => {
            if (!err) {
                pushStream.respond({ 'content-type': 'text/css', ':status': 200 });
                pushStream.end('body { font-family: sans-serif; }');
            }
        });

        stream.end(`
            <!DOCTYPE html>
            <html>
            <head>
                <link rel="stylesheet" href="/style.css">
            </head>
            <body>
                <h1>HTTP/2 Server with Node.js</h1>
                <p>멀티플렉싱, 헤더 압축, 서버 푸시 지원</p>
            </body>
            </html>
        `);
    }
});

// HTTP/2 성능 비교 테스트
const http2Client = http2.connect('https://example.com');

// 멀티플렉싱: 여러 요청 동시 전송
const requests = ['/api/users', '/api/posts', '/api/comments'];
requests.forEach(path => {
    const req = http2Client.request({ ':path': path });
    req.on('response', headers => console.log(headers[':status']));
    req.end();
});

server.listen(443, () => console.log('HTTP/2 server running on port 443'));

🗣️ 실무에서 이렇게 말하세요

💬 성능 최적화 회의에서
"HTTP/2로 전환하면 멀티플렉싱 덕분에 이미지 스프라이트나 CSS/JS 번들링을 덜 해도 됩니다. 오히려 파일을 분리하는 게 캐싱에 유리해요. 다만 서버 푸시는 Chrome에서 제거됐으니 103 Early Hints나 preload 태그를 사용하세요."
💬 기술 면접에서
"HTTP/2의 핵심은 바이너리 프레이밍과 멀티플렉싱입니다. HTTP/1.1은 텍스트 기반이고 연결당 하나의 요청만 처리해서 HOL Blocking 문제가 있었는데, HTTP/2는 단일 연결에서 여러 스트림을 동시에 처리합니다. HPACK으로 헤더도 85% 이상 압축되고요. 실무에서 Nginx 설정 시 http2_max_concurrent_streams를 128로 설정해서 운영했습니다."
💬 인프라 설계 논의에서
"HTTP/2는 TLS가 필수는 아니지만, 브라우저들이 h2c(cleartext)를 지원하지 않아서 사실상 HTTPS가 필요합니다. Let's Encrypt로 인증서 발급받고, Nginx에서 'listen 443 ssl http2'만 추가하면 되는데, 기존 HTTP/1.1 클라이언트와도 호환됩니다. ALPN으로 프로토콜 협상하거든요."

⚠️ 흔한 실수 & 주의사항

HTTP/1.1 최적화 기법을 그대로 적용

HTTP/2에서는 도메인 샤딩, 이미지 스프라이트, CSS/JS 번들링이 오히려 성능을 저하시킬 수 있습니다. 멀티플렉싱이 있으므로 파일을 분리하고 개별 캐싱이 가능하도록 하세요.

서버 푸시에 의존

Chrome 106(2022년)부터 서버 푸시 지원이 제거됐습니다. 대신 103 Early Hints 응답이나 HTML의 <link rel="preload"> 태그를 사용하세요. 서버 푸시는 캐시된 리소스도 다시 보내는 문제가 있었습니다.

TCP HOL Blocking 무시

HTTP/2는 HTTP 레벨 HOL Blocking은 해결했지만, TCP 레벨에서는 여전히 발생합니다. 패킷 손실 시 모든 스트림이 영향받습니다. 이 문제는 QUIC 기반의 HTTP/3에서 해결됩니다.

올바른 HTTP/2 적용 방법

TLS 1.2 이상 + HTTP/2 활성화, 파일 분리로 개별 캐싱 활용, preload 힌트로 중요 리소스 우선 로딩, CDN 사용 시 HTTP/2 지원 확인. curl -I --http2 명령으로 적용 여부를 확인하세요.

🔗 관련 용어

📚 더 배우기