HTTP
HTTP 요청은 클라이언트가 서버에게 보내는 메세지로
사용자가 HTTP 요청을 하게 되면 헤더와 바디를 주고 받는다.
HTTP 헤더 (Header)

사용자의 요청에 대한 정보를 포함하고 있다.
헤더는 바디를 설명하는 정보를 포함하여 여러가지 정보가 묶인 정보묶음이다.
헤더는 콜론 ':' 으로 구분되는 key - value형태로 설정된다.
1. Host : 요청하는 호스트이 도메인 이름 또는 IP 주소
2. User-Agent: 클라이언트의 소프트웨어 식별자 (브라우저나 앱의 정보
3. Accept : 클라이언트가 받을 수 있는 응답 방식 (미디어 타입)
4. Content-Type: 요청 바디의 데이터 타입 (POST 요청에서 사용된다.)
5. Authorization: 인증 정보 (토큰 등)
6. Cookie : 이전에 서버로부터 받은 쿠키 정보
7. Cache-Control : 캐시 제어 지시자
8. Connection : 클라이언트와 서버 간의 연결 설정
헤더의 종류
HTTP요청을 할 때 3가지의 헤더인 일반헤더, 요청헤더, 응답헤더가 자동으로 생긴다.
응답헤더
서버에서 설정하는 헤더
요청헤더
클라이언트에서 설정한 헤더
일반헤더

요청한 URL, 요청메서드, 자원을 요청할 때 해당자원의 출처를 나타내는 URL을 노출시킬지 말지 정한다.
보안정도가 설정되어있는 Refferrer Policy를 포함한다.
요청헤더

클라이언트가 서버에 요청할 때 클라이언트가 설정하거나 자동으로 설정되는 헤더.
요청 헤더에는 메서드, 클라이언트의 OS, 브라우저 정보 등이 포함된다.
응답헤더

서버가 클라이언트에게 응답을 보낼 때 설정하거나 자동으로 설정되는 헤더.
응답 헤더에는 서버의 소프트웨어 정보 등이 담긴다.
예를 들어 nginx를 프록시 서버로 두었을 때 해당 정보가 표기된다.
하지만 대부분의 서버는 어떤 소프트웨어가 사용되고 있는지 알기 어렵게 하기 위해
서버 정보를 숨긴다.
HTTP 바디
HTTP 요청의 바디는
POST 메서드와 같이 데이터를 서버로 전송할 때 사용된다.
서버에서 보내고자
예를 들어, 웹 폼 데이터나 JSON, html, image 형식의 데이터가 포함된다.
바디의 구조는 헤더의 Content-Type 헤더에 따라 달라진다.
웹 폼 데이터 (application/x-www-form-urlencoded) 형식으로 인코딩된 데이터
key1=value1&key2=value2
JSON 데이터 (application/json) 형식으로 전송되는 데이터
{
"key1": "value1",
"key2": "value2"
}
HTTP POST 요청
POST /api/login HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 43
{
"username": "user",
"password": "password123"
}
헤더: HOST, Content-Type, Content-Length
바디: JSON 형식의 데이터
응답 헤더 만들어보기
public class httpHeader {
public static void main(String[] args) throws IOException {
int port = 3000;
// HTTP 서버 생성
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
// 서버 시작 메시지 출력
System.out.println("Server running at http://localhost:" + port + "/");
// 컨텍스트 경로 설정 및 핸들러 할당
server.createContext("/", new MyHandler());
// 기본 실행자 설정 (null 설정 시 기본 실행자 사용)
server.setExecutor(null);
// 서버 시작
server.start();
}
// 요청 핸들러 클래스 정의
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String response = "큰돌, 그는 신인가?!";
// 응답 헤더 설정
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
exchange.getResponseHeaders().set("Kundol", "i love you, but you don't love me");
// 응답 데이터 전송
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}
public static void main(String[] args) throws IOException {
int port = 3000;
// HTTPServer 객체를 생성하여 웹 서버 생성
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
// 서버 시작 메시지 출력
System.out.println("Server running at http://localhost:" + port + "/");
// 컨텍스트 경로를 설정할 핸들러 등록
server.createContext("/", new MyHandler());
// 요청을 처리할 스레드 생성 null은 기본 실행자
server.setExecutor(null);
// 서버 시작
server.start();
}
// 요청 핸들러 클래스 정의
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
//클라이언트에게 보낼 응답 데이터
String response = "큰돌, 그는 신인가?!";
//content-type을 text/plain; charset=UTF-8" 로 설정
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
//사용자 정의 헤더 추가
exchange.getResponseHeaders().set("Kundol", "i love you, but you don't love me");
//HTTP 상태코드 (200) 과 응답 본문의 길이 설정
exchange.sendResponseHeaders(200, response.getBytes().length);
//HttpExchange에서 outputstream을 사용하여 바디 가져옴
OutputStream os = exchange.getResponseBody();
//출력
os.write(response.getBytes());
os.close();
}
}
}
출력!!!


HTTP/1.0과 HTTP/1.1의 차이
HTTP/1.0

HTTP/1.0은 수명이 짧은 연결이다. HTTP요청은 자체 요청에서 완료가 된다.
각 HTTP 요청당 TCP 핸드셰이크가 발생되며 기본적으로 한 연결당 하나의 요청을 처리한다.
한 번 연결할때마다 TCP 연결을 반복하니 RTT가 늘어나는 문제점이 발생했다.
RTT는?
RTT(Round Trip Time: 왕복 지연시간)은 신호를 전송하고 해당 신호의 수신확인에 걸린 시간을 더한 값이자
어떤 메시지가 두 장치 사이를 왕복하는 데 걸리는 시간이다.
HTTP/1.1
HTTP/1.1은 HTTP/1.0의 단점을 보완한 프로토콜이다.

1. Keep-alive default
데이터를 요청할 때 마다 TCP 연결을 하는게 아닌
한 번 연결하면 계속해서 데이터를 받을 수 있게 만들어졌다.
Keep-alive 옵션을 기본옵션으로 설정하면서 가능해졌다.
Keep alive 실습
public class keepAlive {
public static void main(String[] args) throws IOException {
int port = 12010;
// HTTP 서버 생성
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
// 서버 시작 메시지 출력
System.out.println("Server running at http://localhost:" + port + "/");
// 컨텍스트 경로 설정 및 핸들러 할당
server.createContext("/", new MyHandler());
// 기본 실행자 설정 (null 설정 시 기본 실행자 사용)
server.setExecutor(null);
// 서버 시작
server.start();
}
// 요청 핸들러 클래스 정의
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "application/json");
String responseBody = "{\"a\": 1}";
exchange.sendResponseHeaders(200, responseBody.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(responseBody.getBytes());
os.close();
}
}
}
// 포트번호 12010
int port = 12010;
// HttpServer 객체를 사용한 내장 웹 서버 생성
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
// 서버 시작 메시지 출력
System.out.println("Server running at http://localhost:" + port + "/");
// MyHandler 클래스를 사용한 경로 설정 ("/")
server.createContext("/", new MyHandler());
// 스레드 풀 생성 (null 지정 = 기본 실행자)
server.setExecutor(null);
// 서버 시작
server.start();
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
//JSON 형식의 응답 설정
exchange.getResponseHeaders().set("Content-Type", "application/json");
//응답으로 보낼 JSON 문자열
String responseBody = "{\"a\": 1}";
//HTTP 상태 코드 200과 응답 바디의 길이 설정
exchange.sendResponseHeaders(200, responseBody.getBytes().length);
//HttpExchange에서 응답을 가져온다
OutputStream os = exchange.getResponseBody();
//출력
os.write(responseBody.getBytes());
os.close();
실행



Keep-alive header
헤더로 TCP 연결을 유지하는 것을 알려주고, 연결 유지시간인 timeout과 최대 요청수 max를 정할 수 있다.
2. 호스트 헤더
HTTP/1.0은 하나의 IP에 하나의 호스트만 가질 수 있었다.
서버는 여러개의 호스트를 가질 수 있으며, 유연성을 위해 HTTP/1.1은
헤더에 특정 호스트를 포함할 수 있게 되면서 항상 호스트를 포함하여 요청하도록 변경되었다.
3. 대역폭 최적화
HTTP/1.0의 경우 파일을 다운로드 받다가 연결이 끊기면 다시 다운로드 받는 것은 불가능했다.
HTTP/1.1 에선 다시 다운로드 받을 수 있게 바뀌었다.
HTTP/1.0에서 10KB 파일을 다운로드 받을 때 5KB만 받고 다시 다운로드를 못 받는다면
1.1에서는 Range:bytes=5000- 라는 헤더를 추가하여 다운로드 재개 요청을 할 수 있다.
4. 요청을 줄이기 위한 기술
HTTP/1.1로 발전했음에도 불구하고 서버를 요청할 때 마다 RTT는 계속해서 증가하기 때문에
요청을 줄이기 위한 여러가지 기술들이 있었다.
대표적으로 이미지 스프라이트, 코드압축, Base64 인코딩 기술이 있다.
이미지 스프라이트

수많은 이미지를 하나의 이미지로 만들어 하나의 이미지를 다운받은 것을
여러 이미지를 다운받은 듯한 효과를 낸다.
코드 압축
https://www.toptal.com/developers/javascript-minifier
코드를 압축해서 서빙한다.
이미지 Base64 인코딩
https://www.base64-image.de/
이미지 파일을 64진법으로 이루어진 문자열로 인코딩하여
이미지 서버에 대한 HTTP요청할 필요 없이 만든다.
하지만 Base64 인코딩을 할 경우 파일크기가 더 커지는 단점이 있다.
| 기능/특성 |
HTTP 1.0 |
HTTP 1.1 |
| 지속 연결 |
기본적으로 지원하지 않음 |
지속 연결을 지원함 |
| 호스트 헤더 |
지원하지 않음 |
필수 (하나의 IP 주소에서 여러 도메인 호스팅 가능) |
| 요청 메서드 |
GET, POST, HEAD |
GET, POST, HEAD, PUT, DELETE, OPTIONS, TRACE, CONNECT |
| 캐시 제어 |
제한적이거나 캐시 제어 헤더가 없음 |
다양한 캐시 제어 헤더 지원 (예: Cache-Control) |
| 파이프라인 지원 |
지원하지 않음 |
하나의 연결로 여러 요청을 파이프라인으로 처리 가능 |
| 에러 처리 |
간단하고 사용자 친화적이지 않은 에러 메시지 |
명확하고 사용자 친화적인 에러 메시지 |
| 길이 필드 |
Content-Length 헤더 필수 |
Transfer-Encoding 또는 Content-Length 사용 가능 |
| 쿠키 |
지원하지 않음 |
표준화된 쿠키 사용과 관리 |
| 가상 호스팅 |
지원하지 않음 |
Host 헤더를 사용하여 가상 호스팅 지원 |
| 압축 |
지원하지 않음 |
Content-Encoding을 통한 데이터 압축 지원 |
HTTP/1.1 의 문제점

HOL과 무더운 헤더를 가진다.
HOL(Head of Line Blocking)
네트워크에서 같은 큐에 있는 패킷 중 첫번째 패킷이 지연되면
전부 지연되는 성능저하현상이다.
HTTP/2
구글에선 HTTP/1.1 의 HOL 문제를 해결하기 위해 SPDY 프로토콜을 개발하고
이후 SPDY를 기반으로 하는 HTTP/2 프로토콜을 만들었다.
바이너리 포맷 계층

애플리케이션과 전송 계층 사이에 바이너리 포맷 계층을 추가한다.
HTTP 1.0은 일반 텍스트 메시지를 전송하고 줄바꿈으로 나눴다면
HTTP 2.0은 0과 1로 이루어진 바이너리 데이터로 변경하여 캡슐화하여 전송된다.
h2와 h2c
HTTP/2 는 TLS를 선택적으로 쓸 수 있다.
* TLS는 브라우저와 서버간의 데이터 통신을 암호화하는 프로토콜이다.
h2c는 TLS를 사용하지 않고 TCP 연결에서 직접 HTTP/2를 사용하는 것을 의미하며
개발자가 편리하게 테스트를 할 수 있다는 장점, 암호화와 관련된 오버헤드가 없다는 장점이 있다.
h2는 tls가 장착된 http2를 부르며 브라우저에서는 HTTPS가 없는 HTTP2는 지원하지 않기 때문에
브라우저에는 h2c가 아니라 h2만 허용된다.
멀티플렉싱
단일 TCP 연결의 여러 스트림에서 여러 HTTP 요청과 응답을 비동기적으로 보낼 수 있다.
이를 통해 HOL을 해결한다.
HTTP 1.1에서는 병렬요청을 하려면 다중 TCP 연결을 통해서 하고
일반적으로는 TCP 연결 하나당 병렬 요청은 불가능하다.
HTTP/2.0에서는 리소스를 작은 프레임으로 나누고 전달한다.
각각의 프레임은 스트림 ID, 해당 청크의 크기를 나타내는 프레임이 추가되었기 때문에
작게 나눠서 다운로드가 되더라도 결과적으로 응답데이터에서는 올바른 순서로 재조립된다.
청크

대량의 데이터를 조각으로 나누어 전송하는 방법.
데이터 조각화: 전송하려는 데이터를 일정한 크기의 청크로 나눈다.
각 청크는 크기와 데이터로 구성된다.
청크 크기: 각 청크의 시작 부분에는 해당 청크의 크기 (16진수로 표시된 바이트 수) 가 포함된다.
종료 신호: 마지막 청크는 크기가 0으로 표시되어 전송이 완료되었음을 나타낸다.
7\r\n
Hello, \r\n
6\r\n
world!\r\n
0\r\n
\r\n
Hello, World!
\r\n 은 청크의 크기, 데이터 사이, 청크들 간의 구분, 마지막 청크와 헤더 사이에서 사용된다.
마지막 청크의 크기는 0이다.
청크의 장점
높은 신뢰성
청크 전송은 큰 데이터를 여러 조각으로 나누기 때문에
네트워크 연결이 불안정한 경우 전체 데이터를 재전송하지 않고 일부 청크만 재전송한다.
서버 자원 활용
클라이언트가 데이터를 받는 속도에 따라 서버는 여러 클라이언트에게 데이터 동시 전송을 할 수 있다.
서버 푸시

서버가 리소스를 클라이언트에 푸시 할 수 있다.
요청된 html 파일과 함께 다른 개체를 별도로 보낼 수 있다.
만약 요청한 html에 css가 포함되어있다면 별도 요청없이 css를 같이 보낼 수 있다.
헤더 압축
HTTP/1.1 에도 헤더가 무거웠다면
이를 허프만 인코딩 압축 방법 등으로 압축시켰다.
똑같은 서버에서 2개의 이미지를 준다고 할 때, 중복되는 헤더는 제외한 채 보내고
해당 공통 필드로 헤더를 재구성하며 중복되지 않은 헤더값은 허프만 인코딩 압축 방법으로
압축해 전송한다.
허프만 인코딩 압축
데이터 빈도에 따라 가변 길이 코드를 부여하여 데이터를 압축한다.
1. 빈도 계산
입력 데이터에서 각 문자 또는 기호의 빈도를 계산한다.
2. 허프만 트리 구축
빈도가 가장 높은 문자에는 짧은 코드, 낮은 문자에는 긴 코드를 부여하는 이진 트리를 생성한다.
3. 코드 할당
허프만 트리를 통해 각 문자에 압축된 코드(비트 패턴)을 할당한다.
빈도가 높은 문자에는 짧은 코드를 할당하고 낮은 빈도의 문자에는 긴 코드를 할당한다.
4. 데이터 인코딩
할당된 코드를 사용하여 입력 데이터를 압축한 형식으로 인코딩한다.
5. 데이터 디코딩
허프만 트리로 인코딩된 데이터를 디코딩하여 원래 데이터로 복원한다.
예시
입력 데이터가 ABBCCCDDDDEEEE일 때
각 문자의 빈도를 계산한다.
A: 1회
B: 2회
C: 3회
D: 4회
E: 5회
허프만 트리를 구성한다.
가장 빈도가 낮은 문자부터 시작하여 트리를 구축한다.
코드를 할당한다.
A: 1111
B: 111
C: 11
D: 0
E: 10
인코딩
"ABBCCCDDDDEEEEE"는
"111111111111111111111111111111110000111111111111111011111111111111100000"으로 압축된다.
우선순위
서버에서 원하는 순서대로 우선순위를 정해 리소스를 전달한다.
HTTP/3

HTTP/2는 TCP를 사용하기 때문에 초기 연결에 대한 RTT로 인한
지연시간이라는 문제점이 있었다.
QUIC이라는 계층 위에서 돌아가며 TCP 기반이 아닌 UDP 기반으로 돌아간다.
HTTP/2의 장점이었던 멀티플렉싱을 가지고 가며
초기 연결 설정시 지연시간 감소라는 특성을 가지고 있다.
HTTP/3의 장점
HTTP/2의 경우 3 - RTT 가 필요하다면 QUIC는 1 - RTT만 필요하다.
HTTP/2의 경우 클라이언트와 서버간의 연결을 맺어 세션에 만드는데 필요한
핸드셰이크, 암호화통신을 구축하기 위한 TLS 핸드셰이크가 각각 필요했다.
HTTP /3은 TLS로 암호화통신을 구축할 때 단 한번의 핸드셰이크를 활용해
클라이언트와 서버간의 연결, 암호화 통신 모두 구축을 한다.
따라서 1 - RTT만에 모든 연결을 성립할 수 있다.

HTTP/2 : 각각 핸드셰이크 필요
HTTP/3: 단 한번의 핸드셰이크로 가능
순방향 오류 수정 메커니즘 (FEC, Forward Error Correction)
전송된 패킷이 손실 되었다면 수신 측에서 에러를 검출하고 수정하는 방식이며
열약한 네트워크 환경에서도 낮은 패킷 손실률을 자랑한다.