HTTP에 대해 최대한 조사해보자 - 1

지난번에 TCP와 UDP를 학습하였습니다. 이제 이것을 발전시킨 기술 하나를 살펴볼 건데요. 그것은 바로 HTTP입니다.
HTTP가 웹을 위한 프로토콜로 잘 알려져 있긴 하지만 HTTP에 대해 잘 알지 못하고 있습니다. 인터넷에 HTTP를 검색하면 많은 정보들이 나오긴 하지만 생각만큼 잘 와닿지 않았습니다. 1960년대에 HTTP라는 기술에 대한 설계가 시작이 되었고, 팀 버너스 리를 필투로 1989년에 HTTP가 개발이 되었다고 합니다. 어떻게 보면 중요한 내용일 수도 있겠지만, 생각해 보면 지금 말하고 있는 사실과 HTTP의 연관성을 짓지 못하는 경우가 많은 거 같습니다. 이러한 이유가 발생한 이유가 뭘까요? 단순히 암기를 하려고 했던 게 아닐까요? 이번에 제대로 HTTP를 빠개봅시다.

HTTP는 어디에서 동작을 하는가?

HTTP는 TCP위에서 동작을 하는 프로토콜입니다. 그러면 왜 UDP가 아닌 TCP에서 동작을 하는 걸까요? 초창기 HTTP는 웹 문서를 전달하는 목적으로 만들어졌습니다. 그러다 보니 안정적인 프로토콜이 필요했습니다. 지금에야 많은 기술들이 있기 때문에 UDP로도 충분히 HTTP의 구현이 가능했지만 그 당시에는 불가능했습니다.(참고로 HTTP3.0은 TCP가 아닌 UDP로 구성되어 있습니다.)
그래서 HTTP2.0까지는 TCP를 통해 HTTP를 구성했습니다. 이쯤 되니 HTTP를 직접 구현할 수 있지 않을까 생각했습니다.
왜냐하면, TCP를 통해 구현하면 된다고 생각했기 때문입니다. 공식 HTTP까지는 힘들어도 어느 정도는 가능하지 않을까요? HTTP는 웹서버라는 구현체를 이용하여 동작을 한다고 합니다. 그렇다면, HTTP의 구현은 무엇을 뜻하는 걸까요? 사실 HTTP는 TCP의 규칙을 재 구성해서 만든 프로토콜입니다. HTTP에 대한 내용은 RFC에 정의가 되어있습니다. 우리가 공식적인 HTTP를 구현해서 사용한다는 뜻은 RFC에 맞게 개발이 되어야 한다는 뜻입니다. 이제 본격적으로 HTTP를 구현해 봅시다.

이제 본격적으로 HTTP를 구현해봅시다.

import java.io.*;
import java.net.*;

public class SimpleHttpServer {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("HTTP Server started on port " + port);

        while (true) {
            Socket clientSocket = serverSocket.accept(); // TCP 연결 수립
            handleClient(clientSocket); // HTTP 요청 처리
        }
    }

    private static void handleClient(Socket socket) {
        try (
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        ) {
            // HTTP 요청 읽기 (첫 줄만 확인)
            String requestLine = in.readLine();
            System.out.println("Request: " + requestLine);

            // 간단한 HTTP 응답 작성
            String responseBody = "<html><body><h1>Hello, HTTP!</h1></body></html>";
            String response =
                    "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html\r\n" +
                    "Content-Length: " + responseBody.getBytes().length + "\r\n" +
                    "\r\n" +
                    responseBody;

            // 응답 전송
            out.write(response);
            out.flush();

            // 연결 종료 (HTTP/1.0 느낌)
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

요렇게 만들면 간단하게 HTTP를 구현할 수 있다고 합니다. (코드는 GPT를 통해 작성하였습니다. )이 코드는 TCP 위에서 동작하기 때문에, Wireshark나 tcpdump 같은 네트워크 분석 도구를 통해 실제 패킷을 확인할 수도 있습니다. 하지만 그 단순함 속에서, 현대의 복잡한 웹 통신 프로토콜이 발전해 왔다는 점이 흥미롭습니다.
이제 본론으로 돌아와서 HTTP는 어떻게 동작을 할까요?

HTTP 동작 흐름

HTTP는 알고 있다시피 TCP를 통해 동작합니다. 일단 클라이언트가 요청을 보냅니다.
생각을 해야 될 문제는 클라이언트가 무엇을 보내는지 생각을 해봐야 합니다. 가장 쉽게 생각할 수 있는 건 url일 겁니다. 이건 뒤에서 조금 더 자세하게 설명할 예정이라 여기서는 간단히 말하면, 주소값을 입력해줘야 합니다.

요렇게 입력하게 되면 요것을 받아서 서버 쪽으로 전달하게 됩니다. 사실 HTTP는 주소값만 보내는 것은 아닙니다. 무엇을 보내는지 어떻게 보내는지 어떤 것을 보내는지 지정을 해야 합니다. 그것들은 요청 라인, 헤더, 바디가 그 주인공들입니다.
이것들을 잘 정의를 해줘야 비로소 HTTP로써의 가치가 되는 거죠.

요청 라인

요청 라인은 왜 필요할까요? 이것을 작성하지 않으면 안 되는 걸까요? HTTP에서 이거를 정의했기 때문에 따라야 하는 걸까요? 왜 필요한지 알고 써야 사용하는 의미가 있다고 생각합니다. 요청 라인은 '편지' 같다는 생각이 듭니다. 어디에서 발송하는지 어떤 내용이 담겨있는지 작성되어 있죠. 일단 요청 라인에는 다음과 같은 정보들이 들어가 있다고 합니다. 어떤 리소스를 요청하는지(URL), 어떤 방식으로 요청하는지(method), 어떤 HTTP 버전을 사용하는지(version) 만약에 엉뚱한 정보를 넣는다면 어떻게 될까요? 동작할까요? 아니면 동작하지 않을까요? 사실 HTTP입장에서는 이것을 어떻게 작성되는지는 상관없습니다. 우리가 간과하고 있다는 사실이 있습니다. 바로 클라이언트는 서버로 요청을 한다는 점입니다. 이 사실은 굉장히 중요합니다. 이걸 확인하는 주체는 HTTP가 아니라 서버입니다.
이걸 우리는 웹 서버라고 부르는데, 대표적으로 tomcat, nginx, netty 등이 있습니다. 여기에 HTTP 규격에 맞게 작성이 되어 있을 겁니다.

이게 대략적인 요청라인에 대한 내용이다. 이제 상세하게 이해해 봅시다.

URL

URL 같은 경우는 문자열로 작성된 결과이기 때문에 따로 언급하지는 않을 예정입니다. 만약 클라이언트에 전송된 URL이 잘못되거나, 존재하지 않는 경우라면 4XX Exception을 던지게 됩니다. 다만 의문인 것은 메서드라던지 버전 같은 경우는 웹 서버에 작성이 되겠지만, URL을 웹서버에 작성하기는 쉽지 않다고 생각합니다. 왜냐하면 웹서버가 어찌 알고 URL을 전달하지 못한다고 생각했기 때문입니다. 확인해 보니 "작성"이 아닌 "매핑 규칙"에 의해 작성이 된다고 합니다. 그것을 이용해서 스프링 같은 웹 개발 프레임워크로 URL을 작성하는 거일 거 같습니다. 아마 요 부분은 추후에 웹 프레임워크를 학습하게 되면 더 자세하게 이해할 수 있을 거 같습니다. 매핑 규칙 같은 경우는 웹 서버마다 각기 다른 특징을 가지고 있다고 합니다. 대략적으로 말씀드린다면, 아파치 또는 Nginx는 파일 경로 또는 프락시 경로, Tomacat이라면 서블릿 이름, Spring이라면 Controller 메서드라고 합니다.

Method

메서드는 수단이라는 뜻을 가지고 있습니다. 그렇다면 HTTP에는 무수히 많은 수단이 존재한다는 사실을 알 수 있습니다. 같은 URL일지라도 Mehtod가 다르면 다른 결과가 도출됩니다.

뭐라도 있어야 할거 같아서 그림

초창기 HTTP는 단순히 문서를 요청하기 위한 용도였기 때문에 사실상 GET만 존재했습니다.
이후 웹이 발전하면서 POST, PUT, DELETE, OPTIONS 등이 추가되어, 단순 조회를 넘어 데이터의 생성·수정·삭제까지 다룰 수 있게 되었습니다. 그럼 각각 어떤 특징이 있는지 살펴보겠습니다.

GET은 서버에서 지정된 리소스를 조회(Retrieve)하는 요청이며 Body를 포함하지 않습니다.
특징으로는 멱등성과 안전성을 가집니다. 여기서 멱등성이란 여러 번 호출해도 결과는 동일하다는 뜻입니다. 또, 캐싱이 가능하다고 합니다.

  • 조회가 GET이나 READ가 아니라 Retrieve입니다. Retrieve인 이유는 서버를 통해 전송이 된다는 의미입니다.

POST는 서버에 데이터를 전송하여 새로운 리소스를 생성하거나, 특정 리소스에 행위를 위임하는 요청입니다. 요청은 Body에 데이터가 포함이 되며 비멱등성이라고 합니다. 주의할 점은 반드시 리소스 생성에만 쓰이는 것은 아니라고 합니다.

PUT은 지정된 리소스를 완전히 교체하는 요청으로 Body에 새 리소스의 전체 표현이 표현이 됩니다. 멱등성을 가지기 때문에 동일한 요청을 여러 번 보내도 결과는 같습니다. 존재하는 리소스에 대해 호출 시, 일부 서버는 생성으로 간주할 수 있다고 합니다.

DELETE는 지정된 리소스를 제거하는 요청으로, 멱등성을 가지고 있다고 합니다. 또한 리소스가 실제로 "즉시 삭제" 되리라는 보장은 없다고 합니다. 비동기 삭제도 가능하다고 합니다.

여기서 멱등성이라고 나온 부분들이 굉장히 많습니다. 잠깐 언급하긴 했지만 멱등성이라는 것은 무엇일까요?
같은 요청이 계속 들어와도 항상 서버 상태는 같다는 뜻입니다. POST가 위에서 말한 유일한 비 멱등성이기 때문에 요거로 설명하자면, 계정을 생성을 한다고 가정해 봅시다. 그러면 생성이 성공이 됩니다. 대충 200 또는 201이 발생하겠죠. 그러고 나서 또 생성 요청을 해봅시다. 이미 데이터가 존재하기 때문에 서버 상태는 달라질 수밖에 없습니다. 반면 GET, PUT, DELETE 같은 경우는 서버 상태는 변경이 되지 않는다는 것을 알 수 있습니다.

여기까지가 일반적으로 사용이 되는 메소드들입니다. 지금부터 말씀드릴 Meothod들은 특수한 상황에서 사용이 되어지는 Method들입니다.

PATCH 같은 경우는 리소스의 부분만 수정한다고 합니다. RFC 5789를 보시면 자세히 확인할 수 있으며 전체 리소스를 바꾸는 PUT과 달리 일부만 변경이 가능하다고 합니다. 멱등성 같은 경우는 기본적으로는 보장하지는 않고 구현에 따라 달라질 수 있다고 합니다.

HEAD는 GET과 동일하지만 응답 본문은 반환되지 않고 헤더만 확인한다는군요. 주로 리소스 존재 여부, 크기, 수정 날짜 등을 확인할 때 사용한다고 합니다.

TRACE는 클라이언트에서 서버로 요청 전송 후에 서버가 받은 그대로 되돌려 준다고 합니다. 주로 진단용으로 사용이 되며 디버깅, 경로 확인할 때 사용한다고 합니다. 다만 XSS 공격에 악용될 수 있어서 대부분 서버에서는 비활성화를 한다고 합니다.

마지막 OPTIONS는 특정 리소스 또는 서버가 지원하는 HTTP 메서드와 기능을 확인하는 목적으로 사용되며, 주로 CORS에서 사용한다고 합니다.

생각해 보니 PATCH는 가끔 쓰는 거 같은데 HEAD, TRACE, OPTIONS는 사용한 적이 따로 없는듯합니다. 그래서 간단하게 알아봤습니다.

이제 요청 라인의 마지막 버전에 대해 알아봅시다.!!!!

Version

HTTP에는 많은 버전이 존재합니다. 가장 먼저 실용화된 0.9 버전부터 많은 사람들이 많이 사용되는 1.0,1.1 그리고 2.0 최근에 생성된 3.0까지 있습니다. 클라이언트에서 버전을 지정하고 웹 서버 쪽으로 작성된 버전을 전송한다고 가정해 봅시다. 그럼 웹 서버에서는 그에 해당하는 결과를 리턴하게 됩니다. 위에서 말했듯이 HTTP는 규격화된 약속이지 TCP나 UDP처럼 패킷 레벨에서 동작하는 프로토콜은 아닙니다. 이점을 유의해서 학습하시면 좋습니다.

간단하게 살펴봅시다. (추후 자세히 살펴볼 예정입니다.)

HTTP/0.9는 1991년도에 만들어졌습니다. 다시 한 번 더 말씀드리자면, 이 당시에는 웹 문서 전달하는 목적이었습니다. 그래서 메서드도 GET만 존재합니다. 그러다 5년 뒤 (1996년) HTTP/1.0이 개발되었습니다. 이때 POST, HEAD메서드가 추가되었다고 합니다. 또한 헤더가 도입이 되었으며, 연결 방식도 요청마다 연결하는 방식에서 처리 후 종료하는 방식으로 변경되었다고 합니다. 이걸 보면 알 수 있다시피 0.9 버전은 요청마다 연결하는 방식인 점을 유추할 수 있죠. 다만 지속 연결이 없어서 효율이 굉장히 좋지 않았다고 합니다. RFC 1945 규격화되어있다고 합니다. 하지만 이럼에도 불구하고 단일 요청으로 데이터를 처리가 되었다고 합니다. 그래서 1년 뒤에 1997년에 HTTP/1.1가 개발이 되었다고 합니다. 이때 지속 연결과 파이프라이닝 기능이 지원되었으며, 캐싱, 압축, 인코딩 기능이 추가되었다고 합니다. 비로소 다수 요청을 하나의 TCP 연결에서 처리가 가능해졌습니다. 일반적으로 많이 사용되는 HTTP 버전이라 추후에 다시 살펴보도록 하죠. 여기서 어떻게 설명을 해야 할지 잘 모르겠지만, 아무튼 속도를 개선을 시키고 싶었나 봅니다. 그래서 2015년 HTTP/2.0에 텍스트 기반으로 전송하는 것이 아닌 바이너리 프레이밍 방식으로 기술이 변화가 되었으며 지연 감소가 되었고 성능이 향상되었다고 합니다.
마지막으로 HTTP/3.0을 알아보죠. 이전 장에서 알아봤듯이 TCP는 속도를 포기하고 신뢰성을 올린 프로토콜입니다. 이 당시 구글에서 UDP기반으로 QUIC 프로토콜을 만들게 됩니다. 이에 대한 얘기는 잠시 미뤄두고 암튼 이 친구 때문에 성능이 더욱더 향상되었다고 합니다.

간략하게 HTTP 버전에 대해 학습해 봤습니다. 기술적인 내용은 설명하지 않았습니다. 대략적으로 어떤 흐름인지만 살펴봤습니다. 추후에 기회가 된다면 얘기를 해보면 좋을 거 같습니다. 대략적으로 요청 라인에 대해 알아봤는데요. 어떻게 작성할까요?

<메서드> <URL> <HTTP 버전>

헤더

요청라인에서 버전 쪽을 학습했을 때 헤더에 대한 내용이 잠깐 나왔었습니다. 이때 이러한 헤더가 추가되었고 저런 헤더가 추가되었고..
뭐 이런 내용들을 본 적이 있었던 거 같습니다. 다시 한 번 더 말하겠지만 이 내용들도 웹 서버에 구현된 내용들입니다.
본격적으로 헤더가 무엇을 뜻하는지 어떤 것을 할 수 있는지 살펴보고 종류에 대해 알아봅시다.

다시 한 번 더 복습해 봅시다. 위에서 HTTP/0.9에서는 웹 문서를 조회하는 용도로 사용이 되었다고 하였습니다. 하지만 시간이 지날수록 단순 조회만 필요하지 않았습니다. 서버는 점점 더 복잡한 데이터를 다뤄야 하고, 클라이언트는 그 데이터를 “어떤 형식으로 전송할지, 어떤 언어로 인코딩할지, 어떤 인증 정보를 포함할지” 전달할 방법이 필요해졌습니다.


이때 우리가 흔히 떠올리는 방법이 하나 있죠.

“그럼 Body에다가 다 써버리면 되지 않나?”

맞습니다. Body는 본문이기 때문에 문자열이든 JSON이든 다 때려 넣을 수 있습니다.
하지만 문제는 서버 입장에서 Body를 해석할 단서가 없다는 점입니다.
Body에 JSON이 들어올 수도 있고, 이미지가 들어올 수도 있으며, 심지어 바이너리 데이터일 수도 있습니다.
즉, 데이터 형식, 길이, 인코딩 방식, 언어, 인증 여부 등 메타 정보가 없으면 파싱 불가능합니다.

그래서 등장한 게 바로 HTTP Header입니다.
헤더는 본문(Body) 자체가 아니라, 그 본문을 해석하기 위한 메타데이터(Metadata)를 명시합니다.
대표적으로 다음과 같은 필드가 존재하죠:

  • Content-Type: Body에 어떤 형식의 데이터가 들어있는가
  • Content-Length: Body의 길이는 얼마인가
  • Authorization: 어떤 인증 정보를 사용했는가
  • Accept: 클라이언트가 어떤 형식의 응답을 받고 싶은가

즉, 헤더는 요청/응답의 “문맥(Context)”을 명확히 정의하는 영역입니다.
이 정보가 없으면 서버는 Body를 어떻게 처리해야 할지 판단할 수 없습니다.

헤더에는 커스텀 헤더라는 것이 존재합니다. 그렇다면 헤더로 작성을 해야 할지 바디로 작성을 해야 할지 선택을 잘해야 할거 같습니다.
HTTP 헤더는 메타데이터(metadata)를 담는 용도라고 합니다.

HTTP는 양방향 프로토콜이기 때문에, 응답(Response Header) 또한 매우 중요합니다.
즉, 요청 헤더는 클라이언트의 의도를 전달하고,
응답 헤더는 서버가 반환하는 데이터의 성격·정책·상태를 설명합니다.

바디

요청 라인, 헤더까지 전부 설정을 하면 바디만 입력하면 됩니다. 사실 요거를 작성을 해야 할지 고민했습니다. 왜냐하면 바디자체는 작성하기 어렵지 않기 때문입니다. 일반적으로 바디에는 JSON을 입력합니다. 그렇다면 JSON만 작성이 될까요? 이렇게 작성이 되는 이유는 헤더에 어떤 바디로 작성할지 작성하기 때문입니다.

REST API 요청 application/json { "title": "Post", "content": "..." }
XML 기반 통신 application/xml John
파일 업로드 multipart/form-data 이미지, 문서, 동영상
IoT/바이너리 전송 application/octet-stream 센서 데이터, 이미지 RAW
단순 텍스트 text/plain "hello"
HTML Form 전송 application/x-www-form-urlencoded name=John&age=20

즉, JSON은 가장 흔한 케이스일 뿐, 바디는 전송 가능한 모든 데이터의 컨테이너입니다.

지금까지 헤더 라인, 헤더, 바디에 대해 알아봤습니다. 헤더 라인을 통해 어떤 곳으로 데이터를 보낼지 결정하고 헤더를 통해 이 데이터는 어떤 데이터인지 알려주고 바디를 통해 이 데이터의 값의 실체?를 알려준다는 사실을 알게 되었습니다. 그러면 서버는 얘네를 잘 분석한 뒤 응답을 전달을 하게 될 것입니다.

ACK

HTTP는 TCP를 기반으로 동작하는 응용 계층 프로토콜입니다. 클라이언트가 서버로 연결 요청을 보내면, 먼저 TCP 수준에서 3-way-handshake를 통해 연결이 수립됩니다. 그 후, 이 TCP 연결 위에서 HTTP 요청 메시지가 전송되고, 서버는 그에 대한 HTTP 응답을 반환하게 됩니다. 요청과 마찬가지로 응답에 필요한 헤더, 바디 정보를 웹서버에서 받아서 전달해 줍니다. 이 과정을 통해 우리는 “HTTP 요청-응답 모델(Request-Response Model)”을 완성하게 됩니다.

구조적으로 보면 단순한 요청과 응답의 교환처럼 보이지만, 이 위에 연결 유지, 캐싱, 쿠키, 세션 관리 등 수많은 확장 기능이 동작합니다.
캐싱, 쿠키, 세션 관리는 서버 내부적으로 처리가 됩니다. 이렇게 메시지를 전달하면 되면 다시 실행을 시키지 않는 이상 HTTP는 종료합니다. 여기서 이상함을 느낄 겁니다. 현재 웹 서버는 요청이 여러 번 들어가도 종료되지 않기 때문이죠. 이제부터 그 비밀을 알아봅시다.

TCP에서는 연결 해제를 하는데 HTTP에서는 어떻게 하지?

생각을 해보면 이미 요청이 들어왔고 응답을 받았는데 그러면 연결을 해제해야 하는 건가?라고 생각할 수 있습니다. 실제로 HTTP/0.9에서는 단일 요청이었다고 합니다. 그러다 보니 요청마다 비용이 발생하였습니다. 이 문제를 해결하기 위해 HTTP/1.0부터는 지속 연결(Persistent Connection) 개념이 도입되었습니다. 그렇다면 HTTP는 어떻게 지속 연결 개념을 도입할 수 있는 걸까요?

결론부터 말씀드리자면 계층이 다르기 때문입니다. TCP의 연결 지향과 HTTP의 비연결성은 다른 얘기입니다.
또한 웹 서버 on/off와 TCP, HTTP의 통신은 다른 과정임을 기억해야 합니다.
다시 한 번 더 복습해 봅시다. HTTP/0.9 버전일 때는 웹 문서를 가져오는 정도만 필요했습니다. 
그렇기 때문에 통신할 때마다 커넥션을 연결하고 끊고를 반복해도 크게 무리가 가지 않았습니다.
하지만 웹이 발전하면서 하나의 페이지가 JS, CSS, 이미지 등 수십 개의 리소스를 요청해야 하는 상황이 생겼고,
요청마다 TCP 3-way-handshake를 반복하게 되면 지연(latency)과서버 부하가 커졌습니다.
이 문제를 해결하기 위해 HTTP/1.0 후반부부터 “Connection: keep-alive” 헤더가 도입되었고,
HTTP/1.1에서는 이를 기본 동작으로 변경했습니다.
즉, HTTP가 TCP의 지속 연결 기능을 응용 계층에서 활용하기 시작한 것이죠.

이걸 다시 말씀드리면, TCP와 HTTP의 연결성은 상관이 없다고 할 수 있습니다.
이제 버전별로 어떤 식으로 발전을 했는지 이야기를 해봅시다.
제가 HTTP/1.0 버전에서 도입이 되었다고 했죠. 하지만 이때는 선택이었습니다. 그러니까 default값이 지속 연결이 아니었습니다.
직접 선택을 해야 "지속 연결"을 사용할 수 있었습니다. 그러다 HTTP/1.1이 돼서야 지속 연결 헤더가 필수 값이 돼버렸습니다. 그렇다면 헤더에 Connection:Keep-alive를 입력하지 않아도 HTTP가 알아서 지속 연결이 된다고 할 수 있습니다. 완벽하게 HTTP/1.0과 반대로 해더에 Connection:close를 하게 되면 더 이상 지속 연결을 사용하지 않게 되었습니다.

생각해 보면 이제 더 이상 단순 조회뿐만 아니라 많은 리소스를 한 번에 보내야 하는 경우가 많아졌습니다. 그렇기 때문에 헤더에 on/off를 시킬 이유는 없어졌다고 생각합니다. 아무튼 그래서 HTTP/2.0부터는 해더에 Connection이 붙은 것은 금지가 되었다고 합니다. 여기서 의문이 생겼습니다. 왜냐하면 "지속 연결"이라는 개념을 더 이상 헤더로 사용할 필요가 없어졌기 때문입니다. 

HTTP/2.0

HTTP/2.0부터는 하나의 TCP연결에서 다중요청(MultiPlexing)이 가능해졌습니다. 이걸 프레임 단위로 쪼개서 처리가 됩니다.

  • 각 요청은 고유한 “스트림(stream ID)”을 가진다.
  • 하나의 TCP 연결 위에서 여러 스트림이 병렬로 흘러간다.
    ->  Connection: keep-alive나 close 같은 개념이 필요 없어진다.

여기서 말하는 프레임은 네트워크의 프레임 단위가 아닙니다. HTTP/2.0은 HTTP/1.1과 달리 텍스트 기반에서 바이너리 기반으로 변경이 되었습니다. 바이너리 셋을 묶어놓은 논리적인 단위가 "프레임"입니다. 헷갈리시면 안 됩니다. (프레임은 조각 모음이라는 뜻)
이렇게 되면 지속 연결을 해더로써 제어를 해야 할지 아니면 프로토콜이 기본적으로 지원하는가를 두고 혼동이 올 수 있다 판단하여 결국 헤더에서의 "지속 연결"은 금지를 시키게 됩니다.

주의해야 할 점은, 금지되었다고 해서 개념이 완전히 사라진 것은 아닙니다.
단지, 이러한 헤더를 사용해도 HTTP/2 구현체가 자동으로 무시하기 때문에
더 이상 사용할 이유가 없어졌습니다. 따라서 “금지”라는 표현을 사용하는 것입니다.

HTTP/3.0

이제부터는 알다시피 UDP 기반으로 프로토콜이 재구성되었습니다.
다만 단순히 전송 계층만 UDP로 바꾼 것이 아니라, 그 위에 QUIC(Quick UDP Internet Connections)이라는 완전히 새로운 전송 계층 프로토콜을 사용합니다.

QUIC은 TCP의 신뢰성(reliability)과 TLS의 보안(security)을 결합한 구조로, (여기에서는 설명하지는 않겠습니다. 왠지 설명하면 길어질 거 같아..)
패킷 손실 복구, 연결 유지, 재연결(resumption) 등을 모두 애플리케이션 계층에서 자체적으로 처리할 수 있습니다.

이로 인해 TCP 기반의 “연결” 개념, 특히 HTTP/1.x나 2.0에서 사용되던 지속 연결(Persistent Connection) 개념은 사실상 불필요해졌습니다.
QUIC 자체가 연결을 유지하고 복구하는 기능을 내장하고 있기 때문에,
더 이상 “Connection: keep-alive” 같은 헤더로 제어하거나,
프로토콜 차원에서 지속 연결을 별도로 정의할 필요가 없게 된 것입니다.

결론적으로, HTTP/3에서는 “지속 연결”이라는 개념이 프로토콜 내부로 완전히 흡수되어 사라졌다고 볼 수 있습니다.
(즉, 이제는 연결을 “유지”한다는 행위를 클라이언트가 명시적으로 제어할 필요가 없습니다.)


대략적으로 HTTP가 어떤 식으로 연결을 지속하는지에 대해 알아봤습니다. 
이렇게 HTTP동작 원리까지 살펴봤습니다. 사실 빼먹은 부분이 하나 존재합니다. 그건 바로 HTTPS에 관한 이야기입니다. HTTPS 같은 경우는 보안성이 추가된 프로토콜로 HTTP의 버전과는 무관했습니다. 그래서 요거를 추가하고 글을 마치겠습니다.

+) HTTPS

하지만 HTTP자체에는 보안기능이 존재하지 않았습니다. 그러면 보안기능을 추가를 해야 했습니다. 그렇다면 여기서 말하는 보안이란 무엇일까요? 흔히 보안이 좋다?라고 말하는 건 어떠한 정보를 지키고 싶다는 걸 유추할 수 있습니다. 감히 유추해 보자면, 웹 데이터를 말한다고 생각합니다. 하지만 이게 접속이 불가하다는 걸까요? 뭘까요?

일단 HTTP자체는 평문입니다. 그렇기 때문에 브라우저와 서버가 주고받는 모든 요청 응답이 암호화되지 않은 문자열 그대로 네트워크를 타고 흘러가게 됩니다. 예를 들어, GET //login? user=yonghun&pass=1234 HTTP/1.1HTTP/1.1 Host: example.com이라는 요청이 있다고 해봅시다. 이렇게 되면 누군가 사용자의 비밀번호를 볼 수도 있습니다. 이것을 스니핑(sniffing)이라고 부른다고 합니다.

 스니핑: 네트워크 주변을 지나다니는 패킷을 엿보는 행위이다. 출처: 나무위키

이렇게 되면 300바이트 정도만 가로챌 수 있어도 특정 사용자의 계정의 아이디나, 비밀번호를 훔칠 수 있기에 보안과 개인정보에 큰 타격을 줄 수 있다고 합니다. 그렇다고 합니다. 여기는 HTTP파트이기 때문에 간략하게 생각해 보면 HTTP는 TCP위에서 동작하는 프로토콜입니다. 즉 패킷을 통해 데이터가 전송이 된다는 것을 유추할 수 있습니다. (여기에서는 스니핑에 대한 내용은 작성되지 않았습니다.) 

아무튼 HTTP는 전달의 목적이 있지 어떤 것을 지킨다는 목적자체가 없습니다. 그래서 생겨난 것이 HTTPS입니다. 

그러면 HTTPS는 어떻게 정보를 지킬까요? 단순히 생각했을 때 패킷정보를 암호화를 시킬까요? HTTP/2.0은 바이너리로 동작하는데 그것을 암호화를 시킬까요? 
HTTPS는 HTTP는 그대로 두고, 그 아래 계층에 TLS(Transport Layer Security)를 추가한 것을 말합니다. 근데 이상합니다. HTTP는 7 계층.. 그 아래는 6 계층인데 얘이름은 전송 계층입니다. 뭐가 문제일까요? 그 이유는 애초부터 OSI기준으로 만들어진 게 아니라 TCPI/IP기준이라고 합니다.

7 응용 계층 Application HTTP, FTP, DNS
6 표현 계층 (통합됨) 데이터 인코딩, 암호화
5 세션 계층 (통합됨) 연결 관리
4 전송 계층 Transport TCP, UDP
3 네트워크 계층 Internet IP
2 데이터 링크 계층 Network Access Ethernet, Wi-Fi
1 물리 계층 Physical 케이블, 신호선

여기서 TLS는 5~6 계층의 기능(세션 관리 + 암호화)을 수행하지만, TCP 위에서 동작하기 때문에 현실적으로는 전송 계층과 응용 계층 사이의 보안 계층”으로 취급됩니다.. 

네 좋습니다. TCP/IP 기준으로 만들어졌다는 건 알겠는데 그래도 이름은 전송인데요?
이름이 Transport Layer Security인 이유는 역사적인 이유에서 비롯되었습니다.
과거 SSL 시절에는 TCP 소켓을 감싸는 보안 계층으로 설계되었기 때문에,
“전송 계층 위에서 동작하는 보안”이라는 의미로 Transport Layer Security라고 불리게 되었습니다.
즉, TCP가 전송 계층이기 때문에, TCP의 데이터를 암호화로 감싸는 구조라서 이런 이름이 붙게 된 것입니다.

이제 TLS에 대해 알아봤습니다.
HTTP는 여전히 웹 데이터를 주고받는 역할을 하며, TLS는 이 데이터를 암호화하여 TCP로 전송되기 전에 감싸는 보안 계층입니다.
이렇게 함으로써 요청과 응답은 평문이 아닌 암호화된 형태로 전달되어, 도청이나 변조로부터 보호받게 됩니다.

그렇다면 단순히 HTTP + TLS 이렇게만 추가하면 되는 걸까요? 아쉽게도 아닙니다.
HTTP 요청/응답을 안전하게 주고받기 위해 사전에 신뢰를 수립하는 과정이 포함된 구조입니다.
즉, TLS는 서버 인증서를 통해 신뢰를 수립하고, 세션키를 협상하여 이후 모든 HTTP 데이터를 암호화된 채로 송수신한다는 것을 알 수 있습니다. 


마무리

이제 지금까지 학습한 것을 gpt로 돌려서 요약을 해보자. 일단 HTTP에 전반적인 내용은 요 정도면 충분하다고 생각한다. HTTP책도 1000페이지가 넘는데 말이다.

HTTP는 TCP 위에서 동작하는 응용 계층 프로토콜로, 요청(Request)과 응답(Response)을 통해 데이터를 주고받는다.
 초기 버전은 단순 문서 전송이었지만, 버전이 올라가면서 지속 연결(keep-alive), 캐싱, 멀티플렉싱(HTTP/2),
그리고  UDP 기반 QUIC(HTTP/3)으로 발전했다.
 하지만 HTTP 자체는 평문 통신이어서 스니핑(도청)에 취약했다. 이를 해결하기 위해 TLS(Transport Layer Security)가 추가된 HTTPS가 등장했다. TLS는 TCP 위에서 동작하며 데이터를 암호화·인증·무결성 보장한다.
 HTTP/2부터는 성능과 보안을 동시에 확보하기 위해 HTTPS가 사실상 필수가 되었고,
HTTP/3에서는 QUIC 프로토콜에 TLS가 내장되어 “보안 없는 HTTP”는 더 이상 존재하지 않게 되었다.


👉 한 줄 정리:

HTTP는 TCP 위에서 동작하는 비보안 프로토콜이고,
HTTPS는 여기에 TLS를 결합해 암호화·인증을 제공한다.
HTTP/2부터는 보안과 성능을 위해 HTTPS가 기본 전제가 되었다.

다음 장에서 HTTP의 인증 쿠키, 세션, 토큰에 대해 학습해 봅시다.
추가고 CORS.. REST와 RESTful의 개념 요렇게 적어보겠습니다.

댓글

Designed by JB FACTORY