HTTP 프로토콜의 진화 - HTTP/1.0에서 HTTP/3까지

keemsebeen·4일 전
post-thumbnail

해당 글은 HTTP 프로토콜의 기본 개념과 특징, 브라우저에서 HTTP 요청을 보내는 주요 방법인 XMLHttpRequest, Fetch API, axios를 비교하고, HTTP 1.0 / 1.1 / 2.0의 차이를 정리한 문서입니다.

HTTP란?

HyperText Transfer Protocol의 약자로 웹에서 클라이언트(브라우저 등)와 서버가 요청과 응답을 주고받기 위한 애플리케이션 레벨 프로토콜을 의미합니다.

왜 HyperText라고 지었을까?

텍스트 안에 링크(참조)가 있어서, 사용자가 원하는 대로 점프하면서(non-linear) 읽을 수 있는 문서를 뜻합니다. HTML의 <a href="...">링크</a> 가 대표적인 HyperText 요소인데요.

처음 설계될 때의 목표가 하이퍼텍스트 문서를 전송하는 프로토콜이었기 때문에 단순히 텍스트만 전송하는게 아니라 다른 문서로 링크 정보를 포함한 텍스트(hypertext)를 전송해 링크들을 통해 전 세계 문서들이 서로 연결되는 것을 목표로 했다고 합니다.

예를 들어, 브라우저가 HTTP를 사용해, 링크들이 존재하는 HTML 문서를 서버에서 받아오고, 사용자가 링크를 클릭할 때마다 다시 HTTP로 다음 문서를 받아오는 과정 전체를 말합니다. 조금 더 단계별로 보자면 다음과 같습니다.

  1. 브라우저가 html을 반환하라는 GET HTTP 요청을 보냄
  2. 서버가 HTTP 응답으로 HTML(하이퍼텍스트 문서)을 돌려줌
  3. 그 HTML 안에는 또 다른 문서/이미지/비디오로 가는 링크들이 들어 있음
  4. 사용자가 그 링크를 클릭하면
  5. 다시 그 URL로 새 HTTP 요청
  6. 또 다른 하이퍼텍스트 문서(또는 리소스)를 응답으로 받음

특징

  1. 요청–응답(Request–Response)을 주고 받습니다.
    HTTP는 클라이언트가 먼저 요청을 보내야만 서버가 응답할 수 있는 단방향 통신입니다. 서버가 먼저 클라이언트에게 데이터를 보낼 수 없습니다.
  2. 무상태(Stateless)이다.각 요청은 이전 요청과 논리적으로 분리되어 있어서, 서버는 이전 요청을 자동으로 기억하지 않습니다.
  3. URI와 메서드로 자원을 조작합니다.
  4. HTTP 메시지는 헤더(메타데이터)와 바디(실제 데이터)로 구성됩니다.
  5. 상태코드가 존재한다.

XMLHttpRequest(XHR) / fetch / axios

XMLHttpRequest(XHR)

초기 JavaScript에서 HTTP 요청을 보내는 유일한 방법이었습니다.

특징

  1. 브라우저 내장 API, 오래된 Ajax 중심으로 설계되었습니다.
  2. 이벤트 핸들러(onload, onerror, onreadystatechange) 기반입니다.
  3. 응답을 문자열로 받고 JSON 등은 직접 파싱해야 합니다.
  4. 콜백 지옥, 장황한 코드 구조로 인해 지금은 보통 레거시 코드 유지보수용으로만 사용하는 경우가 많습니다.

예시코드

const xhr = new XMLHttpRequest();

xhr.open('GET', '/api/users');

xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  }
};

xhr.onerror = function() {
  console.error('요청 실패');
};

xhr.send();

문제점

  1. 문법이 장황하고 보일러플레이트가 많습니다다.
    1. open, setRequestHeader, onload, onerror 등을 일일이 설정해야 하며 코드가 길고 읽기 어렵습니다.
  2. 콜백 기반이라 콜백 지옥이 쉽게 발생합니다.
    1. 여러 비동기 요청을 순차/의존적으로 처리할 때 콜백 중첩이 심해집니다.
xhr1.onload = function() {
  xhr2.onload = function() {
    xhr3.onload = function() {
      // 깊어지는 중첩...
    };
  };
};
  1. Promise/async-await과 호환되지 않습니다.
    a. XHR은 이벤트 기반 API이기 때문에, Promise/async-await을 쓰려면 직접 래핑해야 합니다.
  2. 에러/상태 처리 코드를 매번 직접 작성해야 한다.
    a. 응답 본문 responseText를 직접 파싱(JSON 등)해야 하고, 상태 코드별 처리도 수동으로 나눠야 합니다.

Fetch API

브라우저 표준 HTTP 클라이언트 API이며, Promise 기반으로 설계되어 있습니다. 최신 브라우저와 Node.js 환경에서도 널리 사용됩니다. 

과거에는 Node.js에 fetch가 없어서 node-fetch 같은 패키지를 사용해야 했지만, Node 18부터는 전역 fetch가 기본 제공되며 Node 21에서 안정화되었습니다. 다만 구 버전 Node를 쓰는 환경에서는 여전히 polyfill이나 axios 등의 라이브러리가 필요합니다.

fetch()의 두 번째 인자로 전달하는 RequestInit 객체에는 다양한 옵션이 있습니다. 궁금해서 찾아봤는데요. 설명만 간단히 적고 넘어가도록 하겠습니다.

interface RequestInit {
  /** 요청의 body를 설정하기 위한 BodyInit 객체 또는 null입니다. */
  body?: BodyInit | null; // ReadableStream | XMLHttpRequestBodyInit;

  /** 요청이 브라우저의 캐시와 어떻게 상호작용할지 나타내는 문자열로, request의 cache 값을 설정합니다. */
  cache?: RequestCache; // "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload";

  /** 자격 증명(쿠키, 인증 정보 등)을 항상 보낼지, 아예 보내지 않을지, 같은 출처에만 보낼지를 나타내는 문자열로, request의 credentials를 설정합니다. */
  credentials?: RequestCredentials; // "include" | "omit" | "same-origin";

  /** request의 headers를 설정하기 위한 Headers 객체, 일반 객체 리터럴, 또는 [키, 값] 튜플 배열입니다. */
  headers?: HeadersInit;

  /** 요청으로 가져올 리소스의 암호학적 해시값으로, request의 integrity를 설정합니다. */
  integrity?: string;

  /** request의 keepalive를 설정하기 위한 boolean 값입니다. */
  keepalive?: boolean;

  /** 요청의 HTTP 메서드를 설정하는 문자열입니다. */
  method?: string;

  /** 요청이 CORS를 사용할지, 동일 출처 URL로만 제한될지를 나타내는 문자열로, request의 mode를 설정합니다. */
  mode?: RequestMode; // RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";

  /** 요청의 우선순위를 나타냅니다. */
  priority?: RequestPriority;

  /** 리다이렉트를 따를지, 리다이렉트를 만나면 에러로 처리할지, 혹은 리다이렉트 응답 자체를(불투명하게) 반환할지를 나타내는 문자열로, request의 redirect를 설정합니다. */
  redirect?: RequestRedirect; // "error" | "follow" | "manual";

  /** 동일 출처 URL, "about:client", 혹은 빈 문자열 중 하나의 값을 가지는 문자열로, request의 referrer를 설정합니다. */
  referrer?: string;

  /** request의 referrerPolicy를 설정하기 위한 리퍼러 정책입니다. */
  referrerPolicy?: ReferrerPolicy; // "" | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url";

  /** request의 signal을 설정하기 위한 AbortSignal입니다. */
  signal?: AbortSignal | null;

  /** 항상 null만 허용됩니다. request를 어떤 Window와도 연결하지 않기 위해 사용됩니다. */
  window?: null;
}

특징

  1. Promise 기반의 현대적인 비동기 API입니다.
    fetch()는 Promise를 반환하므로, then/catch 또는 async/await 패턴과 자연스럽게 어울린다.

  2. Request / Response 객체 중심으로 설계되었습니다.

    Request에는 method, headers, body, mode, credentials 등 많은 프로퍼티가 있고, RequestInit 옵션이 이 값들을 설정하는 역할을 합니다다. Response에는 status, ok, headers, body 와 함께 json(), text() 등 바디 파서 메서드가 존재합니다.

  3. CORS, Origin, 캐시 등 설정이 가능합니다.

  4. 가볍고 의존성이 없습니다.

    브라우저가 제공하는 기능이기 때문에, 별도 번들 크기 증가 없이 사용할 수 있습니다.

🧐 왜 네트워크 요청을 Promise로 구현했을까?
fetch는 HTTP 요청을 보내고 응답을 받아오는 함수입니다. 브라우저 → 서버까지 패킷이 오고 가야하고 이후 서버에서 처리도 필요한 뿐더러 중간에 네트워크 지연, 손실, 재전송 등도 일어날 수 있습니다.
즉, fetch를 호출하는 그 순간에는 결과를 바로 알 수 없는데요. 이를 동기 방식으로 만들면 서버 응답이 올 때 까지 사용자는 아무 것도 할 수 없습니다.
이에 비동기 처리를 위해 Promise로 구현하지 않았을까 생각합니다!

예시코드

// GET 요청
fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

// async/await
async function getUsers() {
  try {
    const response = await fetch('/api/users');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

문제점

  1. HTTP 에러(4xx/5xx)도 reject가 아닙니다.

    fetch()는 네트워크 레벨 에러(DNS 실패, 연결 끊김 등)에서만 Promise를 reject 합니다. 따라서 404, 500 응답도 정상응답으로 취급되기 때문에 개발자가 response.ok 나 response.status를 직접 체크해야 합니다. (response.ok가 false여도 Promise는 fulfilled 상태이므로, HTTP 에러를 한 군데서 잡지 못하고 분리된 상태로 관리해야 한다는 점이 직관적이지는 않다고 생각합니다)

    try {
      const res = await fetch('/api/users');
    
      if (!res.ok) {
        // 4xx, 5xx는 여기서 직접 처리
        throw new Error(`HTTP error: ${res.status}`);
      }
    
      const data = await res.json();
    } catch (e) {
      // 네트워크 에러, 위에서 throw한 에러 둘 다 여기로
    }
  2. 응답을 받으면 항상 res.json(), res.text(), res.blob() 등으로 직접 파싱해야 합니다.

  3. 진행률, 업로드/다운로드 이벤트는 직접 스트림을 다뤄야 합니다.
    a. XHR은 onprogress 같은 이벤트로 진행률을 쉽게 받을 수 있지만, fetch는 ReadableStream 기반으로 직접 스트림을 읽으면서 구현해야 해서 코드가 복잡해집니다.

axios

axios는 브라우저와 Node.js 모두에서 사용할 수 있는 Promise 기반 HTTP 클라이언트 라이브러리입니다. 브라우저에서는 내부적으로 XMLHttpRequest, Node.js에서는 http 모듈을 사용해 동작합니다.

특징

  1. 브라우저 + Node.js 양쪽에서 동일한 API를 전송합니다.
    axios는 “isomorphic”이라고 표현합니다. HTTP 클라이언트라, 브라우저에선 XHR 위에서, Node에선 http 모듈 위에서 돌아가면서도 개발자는 똑같은 API (axios.get, axios.post 등)를 사용합니다.

  2. Promise 기반 + 자동 JSON 변환을 진행합니다.

    fetch처럼 Promise 기반이라 async/await과 자연스럽게 어울립니다. 응답이 JSON이면 response.data에 자동으로 파싱된 객체가 들어가고, 요청 시 JS 객체를 보내면 자동으로 JSON 문자열로 변환해 줍니다.

  3. 4xx/5xx 상태 코드를 자동으로 reject합니다.

    axios는 HTTP 에러 상태(예: 404, 500)를 받으면 Promise를 reject합니다. 그래서 try/catch 안에서 네트워크 에러 + HTTP 에러를 한 번에 처리하기 쉽습니다.

  4. 인터셉터(Interceptors) 제공합니다.
    요청/응답을 가로채는 함수를 등록할 수 있습니다.

예시 코드

export const externalInstance = axios.create({
  baseURL: ENV.FAST_API_BASE_URL,
  timeout: 10000,
});

export const get = async <T>(...args: Parameters<typeof externalInstance.get>) => {
  const response = await externalInstance.get<T>(...args);
  return response.data;
};

// .. 

문제점

  1. 외부 의존성 + 번들 크기 증가합니다.

    axios는 서드파티 라이브러리라서 프로젝트에 직접 설치해야 합니다. fetch는 브라우저 내장이라 0KB지만, axios는 약 수십 KB 수준의 번들 크기를 추가로 가져옵니다.

HTTP 1.0

특징

  1. 커넥션당 하나의 요청/응답을 갖습니다.
    a. 기본 동작: 요청 1개 → 응답 1개 → TCP 연결 끊기
    b. 같은 서버에 이미지를 10개 요청하면, TCP 연결도 10번 맺었다 끊었다 해야 합니다.
  2. 기본적인 헤더/캐싱을 도입했습니다.
    a. 0.9에는 없던 상태 코드, 헤더, 간단한 캐시 관련 헤더(If-Modified-Since 등)가 추가됐습니다.
  3. 문자 기반 프로토콜입니다.
    a. 요청/응답이 전부 사람이 읽을 수 있는 텍스트(ASCII) 형식입니다.
    b. GET /index.html HTTP/1.0\r\nHeader: value\r\n\r\n
  4. Host 헤더가 필수는 아닙니다.
    a. HTTP/1.0 스펙에서는 Host 헤더가 필수가 아니라서, 하나의 IP에 하나 도메인만 매핑하는 방식이 일반적입니다.

동작 방식

[요청 1: index.html]
1. TCP 연결 열기 (3-way handshake)
2. GET /index.html 전송
3. 응답(HTML) 받기
4. TCP 연결 닫기

[요청 2: style.css]
1. TCP 연결 다시 열기
2. GET /style.css 전송
3. 응답(CSS) 받기
4. TCP 연결 닫기

[요청 3: app.js]
... (계속 반복)

문제점

  1. TCP 3-way handshake 비용이 매번 발생합니다.
    a. 리소스가 많을 수록 TCP 연결 생성/해제 오버헤드가 증가하고 왕복 지연이 누적되면서 페이지 로딩이 늦어지는 문제가 발생합니다.
  2. 한 번에 한 개 리소스만 전송 가능합니다.
    a. 같은 서버에 요청이 몰려도, 리소스 하나 받고 연결을 종료합니다. 따라서 이미지/JS/CSS가 많은 페이지에는 매우 비효율적입니다.
  3. 가상 호스팅(여러 도메인을 하나의 IP에 올리는 것)에 제약
    Host 헤더가 필수가 아니라서, “같은 IP인데 어떤 도메인 요청인지” 서버 입장에서 구분이 애매합니다.

HTTP 1.1

특징

  1. Persistent Connection(커넥션 유지, Keep-Alive 기본)
    a. 1.1에서는 기본이 연결을 재사용하는 것이기 때문에 하나의 TCP 연결 위에서 여러 요청/응답을 순차 처리가 가능해졌습니다.

  2. Host 헤더 필수
    a. www.example.com 헤더가 반드시 포함되어야 합니다. 하나의 IP에서 여러 도메인을 동시에 서비스하는 가상 호스팅이 가능해져서,서버/호스팅 비용을 크게 줄일 수 있었습니다.
    b. http 1.1이면 Host: 도메인 네임, http 2/3이면 authority를 보면됩니다.

  3. 파이프라이닝(Pipelining) 도입
    a. 하나의 연결에서 요청1, 요청2, 요청3을 응답 기다리지 않고 연속해서 보내고, 서버가 순서대로 응답 내려주는 방식입니다. 지연시간을 줄이려는 시도였지만, 프록시/중간 장비들이 제대로 지원을 안해서 실무에서는 거의 안쓰이게 됐다고 합니다.

  4. Chunked Transfer Encoding
    a. 응답의 전체 길이를 미리 모를 때, 조각(chunk) 으로 나눠서 스트리밍 전송하는 방식을 의미합니다. 서버가 데이터를 생성되는 대로 조금씩 보내줄 수 있어서, 긴 응답도 조금 더 빠르게 사용자에게 보여주기 좋아졌다고 합니다.

동작방식

[처음 HTML 요청]
1. TCP 연결 1개 열기
2. GET /index.html 전송
3. 응답(HTML) 받음
   (연결 유지)

[같은 연결에서 추가 요청]
4. GET /style.css 전송
5. 응답(CSS) 받음

6. GET /app.js 전송
7. 응답(JS) 받음

... 필요할 때까지 같은 연결을 재사용

재연결 덕분에 TCP handshake 비용이 감소하고, TCP가 time이 지날수록 속도가 올라가는 slow start 효과도 살릴 수 있었습니다.

문제점

  1. HTTP 레벨 Head-of-Line Blocking (응답 순서 막힘 문제)
    a. 1개의 커넥션에서 요청을 순서대로 보내고, 응답도 반드시 같은 순서로 와야 합니다. 만약 앞선 요청 하나가 처리 느려지면 → 그 뒤 요청들 응답도 다 같이 대기하게 됩니다. 이것이 HTTP/1.1의 HOL Blocking 문제입니다.
  2. 파이프라이닝이 거의 안 쓰임
    a. 이론상으론 좋은데, 일부 서버/프록시가 파이프라이닝 요청을 제대로 처리 못 해서 실무에서는 대부분 비활성화하거나 브라우저가 사용 안 하도록 하게됐다고 합니다.
  3. 그래서 브라우저가 꼼수를 씀: 커넥션 여러 개 열기
    a. HOL 문제를 피하려고, 브라우저는 같은 도메인에 TCP를 여러 개(보통 6개 정도) 열어서 병렬 요청을 보내기 시작했습니다. 이것은 또 서버/네트워크 입장에서 커넥션 수 증가하는 문제가 있고, 헤더가 길어질수록(쿠키, User-Agent 등) 오버헤드가 커지는 문제가 있습니다.
    b. 이런 성능 병목을 근본적으로 해결하려고 만든 게 HTTP/2입니다.

HTTP 2.0

특징

  1. 텍스트가 아닌 이진(Binary) 프레이밍 레이어
    a. HTTP/2는 HTTP/1.1의 메서드/URI/상태코드/헤더 등의 의미는 그대로 두고, “전송 방식”만 이진 프레임 기반으로 바꾼 버전이라고 보면 됩니다.
  2. Multiplexing(하나의 TCP 연결에서 여러 요청/응답 동시 처리)
    a. 요청/응답을 스트림(stream) 이라는 단위로 나누고, 각 스트림을 다시 작은 프레임(frame) 으로 쪼개서 한 TCP 연결 위에서 프레임들을 섞어서(interleave) 보냅니다. 덕분에 HTML, CSS, JS, 이미지 요청을 완전 동시에 보내고 받을 수 있고 특정 리소스 응답이 느려져도, 다른 스트림 응답은 먼저 도착 가능합니다. (HTTP/1.1의 HOL(HTTP 레벨) 문제를 크게 줄임.)
  3. Header Compression (HPACK)
    a. User-Agent, Cookie, Accept-* 헤더처럼 매 요청마다 거의 똑같은 헤더를 쏟아내는 문제를 해결하기 위해 헤더를 압축해서 전송합니다 (HPACK).
  4. Stream Prioritization & Flow Control
    a. 어떤 리소스를 더 중요하게 받을지 우선순위 지정이 가능해졌습니다. 스트림마다 흐름 제어(flow control) 를 해서, 중요한 리소스를 먼저/더 빠르게 가져올 수 있게 최적화가 가능합니다.

동작방식

TCP 연결 1- 스트림 #1: index.html
- 스트림 #3: app.js
- 스트림 #5: style.css
- 스트림 #7: image.png

각 스트림이 프레임 단위로 쪼개져서:
[1번 프레임][3번 프레임][5번 프레임][1번 프레임][7번 프레임]
이런 식으로 섞여서 전송 → 수신 측에서 스트림 ID 보고 재조립

문제점

  1. TCP 레벨 HOL Blocking은 여전히 존재
    a. HTTP/2는 HTTP 레벨 HOL은 많이 줄였지만, 결국 모든 스트림이 하나의 TCP 연결을 공유하기 때문에 그 TCP에서 패킷이 유실되면 → 재전송이 끝날 때까지 모든 스트림이 영향을 받습니다.

    → 이 한계를 더 줄이려고, 아예 TCP 대신 QUIC(UDP 기반) 를 사용하는 HTTP/3가 등장했습니다.

  2. 구현 복잡도와 디버깅 난이도 증가
    a. 이진 프레이밍, 스트림 ID, 플로우 제어, HPACK 등 내부 구조가 복잡해서, 구현/디버깅 난도가 올라감.

  3. Server Push 오남용 시 오히려 비효율
    a. 필요 없는 리소스를 푸시하면 네트워크 낭비가 발생합니다. 브라우저 캐시와의 상호작용도 복잡해서, 실제로는 많은 팀들이 Server Push를 쓰지 않거나 꺼두는 경우가 많다고 합니다.

HTTP 3.0

HTTP/3는 HTTP/2가 여전히 해결하지 못했던 TCP 레벨 Head-of-Line Blocking 문제를 해결하기 위해 등장했습니다. HTTP/2는 하나의 TCP 연결 위에서 멀티플렉싱을 통해 HTTP 레벨의 HOL 문제는 줄였지만, TCP 자체의 특성 때문에 패킷 손실이 발생하면 모든 스트림이 함께 지연되는 한계가 있었습니다. HTTP/3는 이 문제를 해결하기 위해 TCP 대신 UDP 기반의 QUIC 프로토콜을 사용합니다.

특징

  1. UDP 기반의 QUIC(Quick UDP Internet Connections) 프로토콜 사용합니다.
    a. UDP 자체는 신뢰성을 보장하지 않지만, QUIC이 전송 계층에서 필요로 하는 기능을 애플리케이션 레벨에서 직접 구현함으로써 TCP의 역할을 대체합니다. QUIC은 패킷 번호 기반의 손실 감지와 재전송, 흐름 제어, 혼잡 제어를 제공해 신뢰성을 유지합니다.
  2. 독립적인 스트림 (패킷 손실이 다른 스트림에 영향 없음)
    a. 각 요청/응답은 QUIC 스트림 단위로 처리되며, 특정 스트림에서 패킷 손실이 발생하더라도 해당 스트림만 영향을 받고 다른 스트림은 정상적으로 처리됩니다. 이로 인해 HTTP/2에서 문제가 되었던 TCP 레벨 HOL Blocking이 제거됩니다.
  3. 0-RTT 연결 (재연결 시 핸드셰이크 생략 가능)
    a. QUIC은 TLS 1.3을 프로토콜 내부에 통합하고 이전 연결 정보를 재사용함으로써, 재연결 시 추가적인 핸드셰이크 없이 곧바로 데이터를 전송할 수 있습니다. 이를 통해 네트워크 지연이 큰 환경에서도 초기 요청 지연을 크게 줄일 수 있습니다.
  4. 연결 마이그레이션 (WiFi ↔ 모바일 전환 시에도 연결 유지)
    a. QUIC은 IP 주소와 포트가 아닌 Connection ID를 기반으로 연결을 식별하기 때문에, 사용자가 WiFi에서 모바일 네트워크로 전환하더라도 기존 연결을 유지할 수 있습니다. 이는 모바일 환경에서 특히 큰 장점이라고 합니다.

동작 방식

HTTP/3의 통신은 UDP 기반의 QUIC 연결을 수립하는 것에서 시작됩니다. 초기 연결 과정에서 QUIC 핸드셰이크가 수행되며, 이 과정에서 TLS 1.3 암호화 설정이 함께 이루어집니다. 연결이 수립되면 하나의 QUIC 연결 위에 여러 개의 스트림이 생성됩니다.

각 HTTP 요청과 응답은 서로 다른 QUIC 스트림으로 처리되며, 스트림 데이터는 다시 작은 패킷 단위로 분할되어 전송됩니다. 이 패킷들은 하나의 UDP 연결 위에서 섞여(interleave) 전송되지만, 수신 측에서는 스트림 ID를 기준으로 각 스트림의 데이터를 독립적으로 재조립합니다.

이 과정에서 특정 스트림의 패킷이 손실되더라도 해당 스트림만 재전송이 발생하며, 다른 스트림의 데이터 전송은 영향을 받지 않습니다. 또한 네트워크 환경이 변경되더라도 Connection ID를 통해 동일한 연결을 유지할 수 있으며, 이전 연결 정보가 있다면 0-RTT 방식으로 즉시 데이터 전송이 가능합니다.

문제점

  1. UDP 차단 환경 문제
    a. 일부 방화벽, 프록시, 기업 네트워크 환경에서는 UDP 트래픽이 제한되거나 차단되는 경우가 있습니다. 이 경우 HTTP/3 연결이 실패하며, 브라우저는 HTTP/2 또는 HTTP/1.1로 폴백해야 합니다.
  2. 구현 복잡도 증가
    a. QUIC은 전송 계층의 기능을 애플리케이션 레벨에서 직접 구현하기 때문에, TCP 기반 HTTP/1.1이나 HTTP/2에 비해 구현과 디버깅 난이도가 높습니다. 서버와 클라이언트 모두에서 복잡한 상태 관리가 필요합니다
  3. 0-RTT 보안 제약
    a. 0-RTT 연결은 초기 지연을 줄여주지만, 재전송 공격(Replay Attack)의 가능성이 있어 멱등성이 보장되는 요청(GET 등)에만 제한적으로 사용됩니다. 모든 요청에서 0-RTT의 이점을 활용할 수는 없습니다.

실제 3.0을 사용 중인 곳은 어딜까?

구글

네이버

유튜브


참고자료

https://axios-http.com/docs/intro?utm_source=chatgpt.com

https://medium.com/rate-labs/quic-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EA%B5%AC%EA%B8%80-%EB%98%90-%EB%84%88%EC%95%BC-932befde91a1

다음 글은 궁금했던 UDT, TCP의 차이점으로 돌아오겠습니닷

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글