브라우저에서 웹페이지 하나를 열면 수십 개의 리소스(HTML, CSS, JS, 이미지)를 동시에 받아와야 한다. HTTP/1.1에서는 이걸 어떻게 처리했고, 왜 HTTP/2가 필요해졌을까?

HTTP/1.1의 한계 — 왜 HTTP/2가 필요한가

HTTP/1.1에서 하나의 TCP 연결은 한 번에 하나의 요청-응답만 처리할 수 있습니다. 응답이 올 때까지 다음 요청을 보내지 못하는 이 문제를 Head-of-Line(HOL) Blocking 이라고 합니다.

브라우저들은 이 문제를 우회하기 위해 도메인당 6~8개의 TCP 연결 을 동시에 맺습니다. 하지만 이 방식에는 근본적인 한계가 있습니다.

  • **TCP 핸드셰이크 오버헤드 **: 연결마다 3-way 핸드셰이크 + TLS 핸드셰이크가 필요합니다
  • ** 커넥션 수 제한 **: 브라우저가 열 수 있는 연결 수에 한계가 있습니다
  • ** 텍스트 기반 프로토콜 **: 헤더가 텍스트이므로 파싱 비용이 크고, 매 요청마다 중복된 헤더(Cookie, User-Agent 등)를 반복해서 보냅니다
  • ** 서버에서 클라이언트로 먼저 보낼 수 없음 **: 클라이언트가 요청해야만 응답할 수 있습니다

HTTP/2는 이 모든 문제를 ** 하나의 TCP 연결** 위에서 해결합니다.


HTTP/2의 핵심 개념 네 가지

1. 바이너리 프레이밍 (Binary Framing)

HTTP/1.1은 텍스트 기반입니다. GET /index.html HTTP/1.1\r\n처럼 사람이 읽을 수 있는 형태죠. HTTP/2는 이를 ** 바이너리 프레임 **으로 바꿨습니다.

PLAINTEXT
HTTP/1.1 (텍스트)                    HTTP/2 (바이너리)
+-----------------------+            +--------+--------+
| GET /index.html       |            | Length | Type   |
| Host: example.com     |   →       | Flags  | Stream |
| Accept: text/html     |            | ID     | Payload|
+-----------------------+            +--------+--------+

프레임의 구조는 고정된 9바이트 헤더로 시작합니다.

필드크기설명
Length3바이트페이로드 길이 (최대 16,384바이트)
Type1바이트프레임 타입 (HEADERS, DATA, SETTINGS 등)
Flags1바이트프레임별 플래그 (END_STREAM, END_HEADERS 등)
Reserved1비트예약 비트
Stream ID31비트이 프레임이 속한 스트림 식별자

바이너리라서 파싱이 훨씬 빠르고, 필드 경계가 명확해서 오류가 줄어듭니다.

2. 멀티플렉싱 (Multiplexing)

HTTP/2의 가장 핵심적인 변화입니다. ** 하나의 TCP 연결에서 여러 요청/응답을 동시에** 주고받을 수 있습니다.

PLAINTEXT
HTTP/1.1 — 요청이 순서대로 기다림 (HOL Blocking)

연결 1: [요청A] ────→ [응답A] ────→ [요청B] ────→ [응답B]
연결 2: [요청C] ────→ [응답C] ────→ [요청D] ────→ [응답D]

HTTP/2 — 하나의 연결에서 동시에 처리 (멀티플렉싱)

연결 1: [A 헤더][C 데이터][B 헤더][A 데이터][B 데이터][C 헤더]
         ← 프레임 단위로 섞여서 전송 →

이게 가능한 이유는 ** 스트림(Stream)** 개념 덕분입니다. 각 요청-응답 쌍이 고유한 Stream ID를 가지고, 프레임에 Stream ID가 포함되어 있어서 수신 측에서 프레임을 올바른 스트림에 재조립할 수 있습니다.

  • ** 스트림 **: 하나의 요청-응답을 나타내는 양방향 프레임 흐름
  • ** 홀수 Stream ID**: 클라이언트가 시작한 스트림
  • ** 짝수 Stream ID**: 서버가 시작한 스트림 (서버 푸시)
  • Stream 0: 연결 전체에 대한 제어 프레임 (SETTINGS, PING 등)

3. 헤더 압축 — HPACK

HTTP/1.1에서는 매 요청마다 Cookie, User-Agent 같은 동일한 헤더를 반복해서 보냅니다. 쿠키가 크면 헤더만 수 KB가 됩니다.

HPACK은 두 가지 기법으로 헤더를 압축합니다.

  • ** 정적 테이블 **: 자주 쓰이는 헤더 61개를 인덱스로 매핑 (:method: GET → 인덱스 2)
  • ** 동적 테이블 **: 연결 동안 주고받은 헤더를 기억하여 이후 요청에서 인덱스로 참조
PLAINTEXT
첫 번째 요청: content-type: application/json → 전체 전송 + 동적 테이블에 저장
두 번째 요청: content-type: application/json → 인덱스 번호 하나만 전송

같은 연결에서 요청을 반복할수록 헤더 크기가 줄어드는 구조입니다.

4. 서버 푸시 (Server Push)

서버가 클라이언트의 요청 없이도 리소스를 미리 보낼 수 있는 기능입니다. HTML을 요청하면 서버가 CSS와 JS를 함께 밀어 넣는 식이죠.

PLAINTEXT
클라이언트: GET /index.html
서버:       PUSH_PROMISE (style.css를 보내겠다)
            HEADERS + DATA (index.html 응답)
            HEADERS + DATA (style.css 응답)  ← 클라이언트가 요청하기 전에 도착

다만 실무에서는 ** 서버 푸시를 잘 쓰지 않는 추세 **입니다. 클라이언트 캐시에 이미 있는 리소스를 중복 전송하는 문제가 있고, 브라우저 호환성도 일관적이지 않아서 Chrome은 서버 푸시 지원을 제거했습니다.


스트림의 생명주기

스트림은 생성부터 종료까지 상태를 가집니다. 이 상태 흐름을 알아야 HTTP/2 에러를 디버깅할 수 있습니다.

PLAINTEXT
                        +--------+
                 send   |        | recv
                HEADERS |  idle  | HEADERS
                        |        |
                        +--------+
                           / \
                          /   \
                         v     v
                  +--------+ +--------+
           send   |  open  | |  open  |   recv
           DATA   | (half  | | (half  |   DATA
                  | closed)| | closed)|
                  +--------+ +--------+
                         \     /
                          \   /
                           v v
                        +--------+
                        | closed |
                        +--------+

주요 상태를 정리하면 다음과 같습니다.

상태설명
idle스트림이 아직 사용되지 않은 초기 상태
open양쪽 모두 프레임을 보내고 받을 수 있는 상태
half-closed (local)로컬에서 END_STREAM을 보냄. 받기만 가능
half-closed (remote)원격에서 END_STREAM을 보냄. 보내기만 가능
closed양쪽 모두 END_STREAM을 보냈거나 RST_STREAM으로 즉시 종료

Netty의 HTTP/2 지원 구조

네티는 HTTP/2를 두 가지 핵심 핸들러로 지원합니다.

PLAINTEXT
[SslHandler]              ← TLS + ALPN 프로토콜 협상

[Http2FrameCodec]         ← HTTP/2 프레임 인코딩/디코딩

[Http2MultiplexHandler]   ← 스트림별 자식 Channel 생성

[Http2StreamChannel]      ← 스트림 #1, #3, #5, ...
     └── 각각 독립된 ChannelPipeline

Http2FrameCodec — 프레임 처리의 핵심

Http2FrameCodec은 HTTP/2 바이너리 프레임을 인코딩/디코딩하는 코덱입니다. TCP 바이트 스트림을 받아서 네티의 Http2Frame 객체로 변환하고, 반대 방향으로는 Http2Frame을 바이트로 직렬화합니다.

처리하는 주요 프레임 타입은 다음과 같습니다.

프레임 타입네티 클래스역할
SETTINGSHttp2SettingsFrame연결 설정 (최대 스트림 수, 윈도우 크기 등)
HEADERSHttp2HeadersFrame요청/응답 헤더 전송
DATAHttp2DataFrame바디 데이터 전송
RST_STREAMHttp2ResetFrame스트림 강제 종료
GOAWAYHttp2GoAwayFrame연결 종료 통보
WINDOW_UPDATEHttp2WindowUpdateFrame흐름 제어 윈도우 갱신
JAVA
// Http2FrameCodec 빌더로 생성 — 서버용
Http2FrameCodec frameCodec = Http2FrameCodecBuilder.forServer()
    .initialSettings(Http2Settings.defaultSettings()
        .maxConcurrentStreams(100)      // 동시 스트림 최대 100개
        .initialWindowSize(65535))     // 초기 흐름 제어 윈도우 크기
    .build();

빌더 패턴으로 SETTINGS 프레임의 초기값을 설정할 수 있습니다. maxConcurrentStreams는 한 연결에서 동시에 열 수 있는 스트림 수를 제한합니다.

Http2MultiplexHandler — 스트림을 Channel로 분리

Http2MultiplexHandler는 HTTP/2의 각 스트림을 ** 독립된 자식 Channel(Http2StreamChannel)로** 생성합니다. 이게 네티 HTTP/2 프로그래밍의 핵심 아이디어입니다.

JAVA
// 각 스트림이 생성될 때 실행되는 초기화 핸들러
Http2MultiplexHandler multiplexHandler = new Http2MultiplexHandler(
    new ChannelInitializer<Channel>() {
        @Override
        protected void initChannel(Channel ch) {
            ChannelPipeline pipeline = ch.pipeline();
            // 각 스트림에 독립된 핸들러를 구성할 수 있다
            pipeline.addLast(new Http2StreamHandler());
        }
    }
);

이렇게 하면 각 스트림이 별도의 ChannelPipeline을 가지게 됩니다.

  • ** 스트림 독립성 **: 스트림 #1의 처리가 느려도 스트림 #3에 영향을 주지 않습니다
  • ** 기존 모델 재사용 **: SimpleChannelInboundHandler 같은 기존 핸들러를 스트림 단위로 그대로 쓸 수 있습니다
  • ** 자원 격리 **: 스트림별로 메모리 할당과 해제가 독립적입니다

HTTP/2 서버 구현

전체적인 서버 구현 코드를 살펴봅니다.

스트림 핸들러

JAVA
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http2.*;
import io.netty.util.CharsetUtil;

// 각 HTTP/2 스트림에서 요청을 처리하는 핸들러
public class Http2StreamHandler extends SimpleChannelInboundHandler<Http2Frame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) {
        if (frame instanceof Http2HeadersFrame) {
            Http2HeadersFrame headersFrame = (Http2HeadersFrame) frame;
            handleHeaders(ctx, headersFrame);
        } else if (frame instanceof Http2DataFrame) {
            Http2DataFrame dataFrame = (Http2DataFrame) frame;
            handleData(ctx, dataFrame);
        }
    }

    private void handleHeaders(ChannelHandlerContext ctx,
                               Http2HeadersFrame headersFrame) {
        Http2Headers headers = headersFrame.headers();
        String method = headers.method() != null ? headers.method().toString() : "";
        String path = headers.path() != null ? headers.path().toString() : "";

        System.out.println("요청 수신: " + method + " " + path);

        // GET 요청이면서 END_STREAM이면 바로 응답
        if (headersFrame.isEndStream()) {
            sendResponse(ctx, "Hello, HTTP/2!");
        }
    }

    private void handleData(ChannelHandlerContext ctx,
                            Http2DataFrame dataFrame) {
        // POST 바디 등 데이터 프레임 처리
        ByteBuf content = dataFrame.content();
        System.out.println("데이터 수신: " + content.toString(CharsetUtil.UTF_8));

        if (dataFrame.isEndStream()) {
            sendResponse(ctx, "데이터를 받았습니다");
        }
    }

    private void sendResponse(ChannelHandlerContext ctx, String body) {
        // 1. 응답 헤더 전송
        Http2Headers responseHeaders = new DefaultHttp2Headers()
            .status("200")
            .add("content-type", "text/plain; charset=utf-8");

        ctx.write(new DefaultHttp2HeadersFrame(responseHeaders));

        // 2. 응답 바디 전송 (END_STREAM 플래그로 스트림 종료)
        ByteBuf responseBody = Unpooled.copiedBuffer(body, CharsetUtil.UTF_8);
        ctx.writeAndFlush(new DefaultHttp2DataFrame(responseBody, true));
    }
}

요청/응답 모두 프레임 단위로 처리한다는 점이 HTTP/1.1과 다릅니다. HEADERS 프레임에서 메서드와 경로를 읽고, DATA 프레임에서 바디를 읽습니다. 응답도 HEADERS 프레임과 DATA 프레임을 나눠서 보냅니다.

isEndStream() 체크가 중요합니다. 이 플래그가 true여야 요청이 완전히 도착한 것입니다. POST 요청처럼 바디가 있으면 HEADERS 프레임의 isEndStream()false이고, 마지막 DATA 프레임에서 true가 됩니다.

서버 부트스트랩

JAVA
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.*;
import io.netty.handler.ssl.*;
import io.netty.handler.ssl.util.SelfSignedCertificate;

public class Http2Server {

    private static final int PORT = 8443;

    public static void main(String[] args) throws Exception {
        // 자체 서명 인증서 생성 (개발용)
        SelfSignedCertificate ssc = new SelfSignedCertificate();

        // HTTP/2는 TLS + ALPN이 필요하다
        SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
            .applicationProtocolConfig(new ApplicationProtocolConfig(
                ApplicationProtocolConfig.Protocol.ALPN,
                ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
                ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
                ApplicationProtocolNames.HTTP_2   // "h2"를 ALPN에 등록
            ))
            .build();

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();

                        // 1. TLS 핸들러 — ALPN 프로토콜 협상 포함
                        pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));

                        // 2. HTTP/2 프레임 코덱
                        pipeline.addLast("frameCodec",
                            Http2FrameCodecBuilder.forServer().build());

                        // 3. 멀티플렉스 핸들러 — 스트림별 자식 Channel 생성
                        pipeline.addLast("multiplexHandler",
                            new Http2MultiplexHandler(
                                new ChannelInitializer<Channel>() {
                                    @Override
                                    protected void initChannel(Channel ch) {
                                        ch.pipeline().addLast(
                                            new Http2StreamHandler());
                                    }
                                }
                            ));
                    }
                });

            ChannelFuture future = bootstrap.bind(PORT).sync();
            System.out.println("HTTP/2 서버 시작 — 포트: " + PORT);

            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

파이프라인 순서가 중요합니다. SslHandler → Http2FrameCodec → Http2MultiplexHandler 순서로 배치해야 합니다. SslHandler가 TLS 복호화를 먼저 하고, Http2FrameCodec이 바이너리 프레임을 파싱하고, Http2MultiplexHandler가 스트림을 분리합니다.


ALPN — 프로토콜 협상

ALPN이 필요한 이유

클라이언트가 서버에 연결할 때, ** 이 서버가 HTTP/2를 지원하는지 어떻게 알 수 있을까요?** 먼저 HTTP/1.1로 연결하고 Upgrade 헤더로 HTTP/2를 시도하는 방법도 있지만, 이러면 왕복이 한 번 더 필요합니다.

ALPN(Application-Layer Protocol Negotiation)은 TLS 핸드셰이크 안에서 프로토콜을 협상합니다. 추가 왕복 없이 TLS 연결이 맺어지는 동시에 프로토콜이 결정됩니다.

PLAINTEXT
클라이언트 → 서버: ClientHello + ALPN 확장 ["h2", "http/1.1"]
서버 → 클라이언트: ServerHello + ALPN 확장 ["h2"]
                  ↑ 서버가 h2를 선택

Netty에서의 ALPN 설정

JAVA
// ALPN을 포함한 SSL 컨텍스트 구성
SslContext sslCtx = SslContextBuilder.forServer(certFile, keyFile)
    .applicationProtocolConfig(new ApplicationProtocolConfig(
        // ALPN 프로토콜 사용
        ApplicationProtocolConfig.Protocol.ALPN,
        // 클라이언트가 지원하지 않으면 광고하지 않음
        ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
        // 선택된 프로토콜이 없어도 연결 수락
        ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
        // 지원하는 프로토콜 목록 (우선순위 순서)
        ApplicationProtocolNames.HTTP_2,    // "h2"
        ApplicationProtocolNames.HTTP_1_1   // "http/1.1"
    ))
    .build();

ApplicationProtocolNames.HTTP_2는 문자열 "h2"에 해당합니다. ALPN에서 사용하는 프로토콜 식별자입니다.


HTTP/1.1과 HTTP/2 동시 지원

실제 서비스에서는 HTTP/1.1만 지원하는 클라이언트도 있으므로, 양쪽을 동시에 지원해야 합니다. ApplicationProtocolNegotiationHandler를 사용하면 ALPN 결과에 따라 파이프라인을 분기할 수 있습니다.

JAVA
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http2.*;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;

// ALPN 협상 결과에 따라 파이프라인을 동적으로 구성하는 핸들러
public class Http2OrHttp1Handler extends ApplicationProtocolNegotiationHandler {

    // ALPN이 지원되지 않을 때 기본 프로토콜
    public Http2OrHttp1Handler() {
        super(ApplicationProtocolNames.HTTP_1_1);
    }

    @Override
    protected void configurePipeline(ChannelHandlerContext ctx,
                                     String protocol) {
        ChannelPipeline pipeline = ctx.pipeline();

        if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
            // HTTP/2로 협상됨
            configureHttp2(pipeline);
        } else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
            // HTTP/1.1로 협상됨
            configureHttp1(pipeline);
        } else {
            throw new IllegalStateException("알 수 없는 프로토콜: " + protocol);
        }
    }

    private void configureHttp2(ChannelPipeline pipeline) {
        pipeline.addLast("frameCodec",
            Http2FrameCodecBuilder.forServer().build());
        pipeline.addLast("multiplexHandler",
            new Http2MultiplexHandler(
                new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline().addLast(new Http2StreamHandler());
                    }
                }
            ));
    }

    private void configureHttp1(ChannelPipeline pipeline) {
        pipeline.addLast("httpCodec", new HttpServerCodec());
        pipeline.addLast("aggregator",
            new HttpObjectAggregator(1024 * 1024));
        pipeline.addLast("handler", new Http1RequestHandler());
    }
}

서버 부트스트랩에서의 사용

JAVA
// SSL + 프로토콜 분기를 적용한 파이프라인
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();

        // 1. TLS 핸들러
        pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));

        // 2. ALPN 결과에 따라 HTTP/2 또는 HTTP/1.1 파이프라인 구성
        pipeline.addLast("protocolNegotiator", new Http2OrHttp1Handler());
    }
});

이 구조의 핵심은 SslHandler 다음에 ApplicationProtocolNegotiationHandler를 배치 하는 것입니다. TLS 핸드셰이크가 완료되면 configurePipeline()이 호출되고, 이 메서드 안에서 프로토콜에 맞는 핸들러를 동적으로 추가합니다. 핸들러 자기 자신은 파이프라인에서 자동으로 제거됩니다.


HTTP/2 프레임 타입별 처리

실무에서 자주 다루는 프레임 타입을 좀 더 자세히 살펴봅니다.

SETTINGS 프레임 — 연결 초기 설정

HTTP/2 연결이 맺어지면 양쪽이 SETTINGS 프레임을 교환합니다.

JAVA
// 서버가 보내는 초기 설정
Http2Settings settings = new Http2Settings()
    .maxConcurrentStreams(100)     // 동시 스트림 최대 100개
    .initialWindowSize(1048576)   // 흐름 제어 윈도우 1MB
    .maxHeaderListSize(8192);     // 헤더 최대 크기 8KB

Http2FrameCodec frameCodec = Http2FrameCodecBuilder.forServer()
    .initialSettings(settings)
    .build();
설정기본값설명
HEADER_TABLE_SIZE4096HPACK 동적 테이블 크기
MAX_CONCURRENT_STREAMS무제한동시 열린 스트림 수 제한
INITIAL_WINDOW_SIZE65535스트림 수준 흐름 제어 윈도우
MAX_FRAME_SIZE16384단일 프레임의 최대 페이로드 크기
MAX_HEADER_LIST_SIZE무제한헤더 블록의 최대 크기

흐름 제어 — WINDOW_UPDATE

HTTP/2는 스트림 레벨과 연결 레벨 두 단계의 흐름 제어를 합니다. 수신 측이 처리할 수 있는 만큼만 데이터를 보내도록 WINDOW_UPDATE 프레임으로 윈도우를 갱신합니다.

네티의 Http2FrameCodec은 기본적으로 흐름 제어를 자동으로 처리합니다. DATA 프레임을 수신하면 윈도우를 소비하고, 처리 후 자동으로 WINDOW_UPDATE를 보냅니다.


HTTP/2의 한계 — TCP HOL Blocking

HTTP/2는 HTTP 레벨의 HOL Blocking을 해결했지만, TCP 레벨의 HOL Blocking 은 여전히 존재합니다.

PLAINTEXT
TCP 패킷 흐름:

[패킷1: 스트림A] [패킷2: 스트림B] [패킷3: 스트림C] [패킷4: 스트림A]

                  이 패킷이 유실되면?
                  → 패킷3, 패킷4도 기다려야 함
                  → 스트림 B, C, A 모두 영향

TCP는 순서 보장 프로토콜입니다. 패킷 하나가 유실되면 그 뒤의 모든 패킷이 재전송을 기다려야 합니다. HTTP/2에서는 서로 다른 스트림의 패킷이라도 같은 TCP 연결을 공유하므로, 한 패킷의 유실이 모든 스트림에 영향을 미칩니다.

이 문제를 근본적으로 해결하기 위해 등장한 것이 HTTP/3 (QUIC) 입니다.

HTTP/1.1HTTP/2HTTP/3
전송 프로토콜TCPTCPQUIC (UDP)
멀티플렉싱불가가능가능
HOL BlockingHTTP + TCPTCP만없음
헤더 압축없음HPACKQPACK
연결 설정TCP + TLS (2~3 RTT)TCP + TLS (2~3 RTT)0~1 RTT

QUIC은 UDP 위에 신뢰성 있는 전송 을 구현하면서, 스트림별로 독립적인 패킷 순서를 보장합니다. 스트림 A의 패킷이 유실되어도 스트림 B, C는 영향을 받지 않습니다.

HTTP/2의 멀티플렉싱이 "한 연결에서 여러 스트림을 동시에 처리"하는 것이라면, QUIC은 "각 스트림이 전송 계층에서도 진짜 독립적"인 것입니다. HTTP/2의 멀티플렉싱은 애플리케이션 계층에서만 독립적이었던 셈이죠.


정리

HTTP/2의 핵심 내용을 요약합니다.

  • **HTTP/1.1의 한계 **: 요청당 연결 또는 HOL Blocking. 텍스트 헤더의 중복 전송
  • **HTTP/2의 해법 **: 바이너리 프레이밍, 멀티플렉싱, HPACK 헤더 압축, 서버 푸시
  • ** 스트림 **: 하나의 TCP 연결 안에서 각 요청-응답에 고유 Stream ID를 부여하여 동시 처리
  • Http2FrameCodec: HTTP/2 바이너리 프레임을 네티 객체로 인코딩/디코딩
  • Http2MultiplexHandler: 스트림을 자식 Channel로 분리하여 각 스트림에 독립된 파이프라인 제공
  • ALPN: TLS 핸드셰이크에서 프로토콜을 협상하여 추가 왕복 없이 HTTP/2 사용 결정
  • ** 프로토콜 동시 지원 **: ApplicationProtocolNegotiationHandler로 HTTP/1.1과 HTTP/2를 ALPN 결과에 따라 분기
  • TCP HOL Blocking: HTTP/2에서도 TCP 패킷 유실 시 모든 스트림이 영향받는 한계가 있으며, HTTP/3(QUIC)이 이를 해결
댓글 로딩 중...