HTTP 되짚기
우리는 어떻게 서버와 통신하고 있을까?

우리가 사이트에서 회원 가입 버튼을 누르면 브라우저는 서버에게 보내기 위한 회원 정보를 포장합니다.
"데이터 포장은 왜 하지?"
친구에게 택배 보내는 상황을 떠올려보면 쉽습니다. 책을 힘껏 던져서 친구 집 창문 틈새로 절묘하게 떨어지면 좋겠지만, 누구도 그렇게 할 초능력은 없습니다. 그래서 우리는 택배를 이용합니다.

데이터도 마찬가지입니다. 여러 포장을 씌워야 정상적으로 보낼 수 있습니다. 이런 포장을 패킷이라 부르고, 패킷은 여러 가지가 있습니다.
옛날엔 업체들이 자기들만의 통신법을 써서 타사, 타기종 간 통신 호환이 엉망이었습니다. 그래서 국제 표준 협회에서 표준 통신 규약 (OSI 참조 모델 7계층)을 만들었고, 요즘은 간소화한 TCP / IP 4계층 표준을 많이 씁니다.
이름은 TCP/IP 4계층 모델이지만 FTP, SMTP 등 여러 프로토콜도 포함됩니다. (TCP, IP는 데이터를 처리 절차지만 패킷을 가리킬 수도 있습니다.)
TCP
TCP에선 데이터 흐름(순서)를 제어하며 정확성을 검사합니다.

- 접속되는지 확인(SYN)
- 접속 가능하면 서버는 응답 & 정보 보내라고 요청한다.(ACK, SYN)
- 클라이언트는 요청을 수락하고, 정보를 보낸다.
만약 데이터에 오류가 있으면 상호 소통해서 데이터를 재전송, 재결합한다.
TCP는 데이터 신뢰를 보장하는 절차다. 단점은 확인 절차 때문에 시간이 조금 걸린다.
3-2. TCP/IP 4계층

- 데이터 생성(어플리케이션 계층)
예: 쇼핑몰(어플리케이션)에서 회원 정보(데이터)를 입력 - TCP, 혹은 UDP로 포장(전송 계층)
TCP: 정확한 전송과 피드백. 데이터 신뢰 부여.
UDP: 빠른 전송. 미디어 통신 등 빠르게 전송하기만 하면 되는 곳에 사용. - IP 헤더 추가. IP 주소가 있고 데이터 보내는데 중점. (인터넷 계층)
- 네트워크 접속 계층에선 물리적 주소(Media Access Control 주소)를 담는다. MAC 주소는 회사에서 호스트를 구별하려고 개별적으로 할당하는 고유한 식별 번호다. 이더넷처럼 물리적인 주소로 통신할 때 쓰기 때문에 일반적으로 MAC 주소를 쓰는 경우는 많지 않아서 생략되는 단계이기도 하다.
서버의 인터넷 계층은 IP 패킷을 검사하고 전송 계층에 보낸다.
전송 계층은 데이터 정확성 등을 체크(TCP)
3-3. IP와 PORT
IP는 한 개 뿐이라서 여러 서비스를 이용하기 힘들다. 그래서 port를 추가
유명한 포트 번호는 443(https, 보안)
IP가 도시라면 port는 항구다. 도시에서 교역을 하려면 1번 항구로 가고, 배를 수리하려면 2번 항구로 가는 식이다.
3-4. 도메인
IP는 123.100.001.211 이런 식으로 구성되어있는데 저렇게 주소를 입력해서 접속하려면 일일이 기억하기가 힘들다.
DNS(Domain Name System)를 이용하면 도메인이 IP 주소로 변환되어서 접속한다.
1. 클라이언트가 도메인을 입력하면 로컬 DNS에 캐시를 확인
2. 없으면 Root 서버에 확인
3. TLD 서버 등 여러 서버를 거쳐서 도메인에 매핑되는 IP 수령
4. 해당 IP에 접속
3-5. URI
Uniform: 구분한다.
Resource: 자원을,
Identifier: 식별자로.
URI는 보통 아래와 같은 형식을 가진다.(port라던지, 생략되는 부분도 있다.)
schema://host/path?query=...
coffee png라고 검색하면 구글은 아래와 같은 URI로 검색한다.
만약 개발하다가 URI를 작명한다고 하면 리소스를 기준으로 하면 좋다.
- /create/user... [X]
- /user [O]
모든 URI를 리소스 기준으로만 만들기란 생각보다 쉽진 않다. 위의 구글 예시도 그렇다. /search같은 건 컨트롤 URI다. 동사로 만드는 편.
똑같은 URI에서 회원 가입도 하고, 회원 조회도 하길 원하면 HTTP 메소드(GET,POST 등)를 사용한다.
4. HTTP 메소드

안전성(Safe) : 해당 메소드를 호출해도 리소스는 변하지 않는다.
멱등성(Idempotent) : 해당 메소드를 몇 번 호출해도 결과는 동일하다.
캐시 가능(Cacheable) : 해당 메소드는 호출했을 때 캐싱 가능하다.
- GET : 데이터 조회할 때 사용.
- POST : 리소스 신규 등록(URI 몰라도 됨. 서버가 알아서 URI 등록), 정보를 가지고 작업하거나 처리, 조회, 수정할 때 등 다용도
- PUT : 리소스 신규 등록(클라이언트가 URI를 알고 입력해야 됨), 정보 수정(일부 내용 변경할 때엔 PATCH가 더 취지에 맞음. PUT은 기존의 데이터를 다 날려먹기 때문.)
POST처럼 서버가 URI를 만들고 관리하는 디렉토리는 Collection,
PUT처럼 클라이언트가 URI를 알고 관리하는 디렉토리는 Store라 부른다.
4-1. REST API, RESTful API
REST는 REpresentational State Transfer의 줄임말로 개발자가 HTTP 통신 기반으로 API를 만들 때 염두하길 권장하는 규칙이다. IBM에선 아래와 같이 알려준다.
- 균일한 인터페이스. 요청이 어디에서 오는지와 무관하게, 동일한 리소스에 대한 모든 API 요청은 동일하게 보여야 합니다. REST API는 사용자의 이름이나 이메일 주소 등의 동일한 데이터 조각이 오직 하나의 URI(Uniform Resource Identifier)에 속함을 보장해야 합니다. 리소스가 너무 클 필요는 없지만, 이는 클라이언트가 필요로 하는 모든 정보를 포함해야 합니다.
- 클라이언트-서버 디커플링. REST API 디자인에서, 클라이언트와 서버 애플리케이션은 서로 간에 완전히 독립적이어야 합니다. 클라이언트 애플리케이션이 알아야 하는 유일한 정보는 요청된 리소스의 URI이며, 이는 다른 방법으로 서버 애플리케이션과 상호작용할 수 없습니다. 이와 유사하게, 서버 애플리케이션은 HTTP를 통해 요청된 데이터에 전달하는 것 말고는 클라이언트 애플리케이션을 수정하지 않아야 합니다.
- Stateless. REST API는 stateless입니다. 이는 각 요청에서 이의 처리에 필요한 모든 정보를 포함해야 함을 의미합니다. 즉, REST API는 서버측 세션을 필요로 하지 않습니다. 서버 애플리케이션은 클라이언트 요청과 관련된 데이터를 저장할 수 없습니다.
- 캐싱 가능성. 가급적이면, 리소스를 클라이언트 또는 서버측에서 캐싱할 수 있어야 합니다. 또한 서버 응답에는 전달된 리소스에 대해 캐싱이 허용되는지 여부에 대한 정보도 포함되어야 합니다. 이의 목적은 서버측의 확장성 증가와 함께 클라이언트측의 성능 향상을 동시에 얻는 것입니다.
- 계층 구조 아키텍처. REST API에서는 호출과 응답이 서로 다른 계층을 통과합니다. 일반적으로는, 클라이언트와 서버 애플리케이션이 서로 간에 직접 연결된다고 가정하지 마세요. 통신 루프에는 다수의 서로 다른 중개자가 있을 수 있습니다. REST API는 엔드 애플리케이션 또는 중개자와 통신하는지 여부를 클라이언트나 서버가 알 수 없도록 설계되어야 합니다.
- 코드 온디맨드(옵션). REST API는 일반적으로 정적 리소스를 전송하지만, 특정한 경우에는 응답에 실행 코드(예: Java 애플릿)를 포함할 수도 있습니다. 이러한 경우에, 코드는 요청 시에만 실행되어야 합니다.
REST 원칙을 잘 지키지 않더라도 HTTP 통신 기반으로 작성한 API는 REST API라고 부르곤 한다. 한 편으로, REST 원칙을 준수해서 작성한 API는 RESTful API라고 구분해서 부른다.
RESTful API는 쉽고 프론트엔드와 백엔드를 분리하기 때문에 편리하고 새 기능을 추가할 때에도 확장하기 쉽다. 그리고 URI 즉, 리소스에 초점을 맞춰서 처리하기 때문에 특정 언어나 기기 등의 환경 변화에 결과가 영향을 받지 않는 등 장점이 많다.
그러나 CRUD에만 집중하는 점, 보안은 고려 않는 점, 자유도가 너무 높고, 불편한 문서 관리로 인한 문서 신뢰 하락 등 한계점도 여럿 있다. 그렇기에 GraphQL 같은 IDL 즉, 인터페이스 정의 언어들도 관심을 많이 받고 있다.
어떤 개발을 하든 절대적 왕도는 없다. 상황마다 적용할 기술, 디자인 패턴, 기술은 제각각이며, REST API는 여전히 중요한 개념 중 하나다.
5. HTTP 상태
HTTP 요청을 보내면 여러 응답이 발생한다. 상태를 표현하는 코드도 많다.
200번대: 정상 처리
- 200 : 정상 처리됨.
- 201 : 정상처리가 됐고, 서버에서 리소스를 만듦.
- 202 : 요청 접수는 됐는데 처리는 안 됨.
- 204 : 요청은 정상처리됐는데 payload가 비어있음.
300번대: 리디렉션 관련
- 300 : 요청을 완료하려면 웹 브라우저에서 추가 조치가 필요.
- 301 : 영구적인 리다이렉트. 서버가 URI 주소를 바꿨을 때 리다이렉트시킴.(HTTP 메소드를 GET으로 바꾸는 편이고, 내가 작성한 것들이 날아감)
- 308 : 301이랑 비슷.
(HTTP 메소드 유지, 내가 작성한 것들 유지. 하지만 쓸 일은 별로 없음) - 302 : 일시적 리다이렉트. HTTP 메소드가 GET으로 변할 수 있음.
- 303 : 일시적 리다이렉트. HTTP 메소드가 GET으로 변함.
- 307 : 일시적 리다이렉트. HTTP 메소드에 변경 없음.
- 304 : 캐시 목적 리다이렉트.
400번대: 클라이언트 잘못
- 400 : 요청 구문, 메시지 등의 오류. (파라미터가 잘못됐거나 API 스펙에 안 맞음. 숫자를 보내야 하는데 문자를 보내는 등)
- 401 : 인증 에러. 응답에 www-Authenticate 헤더와 함께 인증법을 줘야함.
인증: Authentication. 본인 확인.
인가: Authorization. 해당 유저의 권한 확인. - 403 : 요청은 맞는데, 권한이 안 맞음.
예: 일반 사용자가 관리자 권한으로 요청 - 404 : 해당 리소스를 못 찾음.
예: 없는 주소를 입력함. 혹은 해당 리소스가 숨겨진 상태.
500번대: 서버 잘못
서버 잘못이라서 요청을 재시도하면 성공할 가능성이 있다.
- 500 : 서버 내부 문제. 애매할 땐 500
- 503 : 서버의 일시적 과부하, 혹은 점검용 서버 다운 등.
(Retry-After로 언제 복구될 지 작성할 수도 있음.)
6. HTTP 헤더
HTTP 헤더는 요약문이다
- Content-Type : payload의 타입.
text/html
charset=UTF-8
application/json
image/png.... - Content-Language : 표기할 언어.
ko(한국어)
en(영어) - referer : 내가 어떤 URI에서 들어왔는지 표시(뒤로 가기 누르면 보이는 사이트)
- user-agent : 클라이언트의 앱 정보(브라우저 등)
- server : origin 서버의 정보
origin 서버란 최종적으로 응답을 주는 진짜 서버를 말한다.
HTTP로 요청과 응답을 주고받을 때엔 중간에 여러 서버를 경유한다. - location : 리다이렉트 주소
- HTTP 네고: Accept~: ~~ 요청을 보내면 서버는 그 요청에 최대한 응한다.
예: Accept-Language: ko-KR, ko;q=0.9, en-US,en;q=0.7, ja;q=0.6
한국어로 사이트를 표기해달라는 요청. 안 되면 영어, 일본어로 표기해달라는 것. - 쿠키: 따로 서술
- 캐시: 따로 서술
6-1. 쿠키
HTTP는 무상태(Stateless) 프로토콜. 요청, 응답만 하면 됨.
자체적으로 정보 기억하진 못해서 쿠키 활용
Set-Cookie: sessionID=abc123; Expires=Thu, 30 Jun 2023 12:00:00 GMT; Path=/; Domain=.example.com; Secure
Set-Cookie: userID=12345; Expires=Thu, 30 Jun 2023 12:00:00 GMT; Path=/; Domain=.example.com; Secure
Set-Cookie: theme=dark; Expires=Thu, 30 Jun 2023 12:00:00 GMT; Path=/; Domain=.example.com; Secure
Set-Cookie: language=en; Expires=Thu, 30 Jun 2023 12:00:00 GMT; Path=/; Domain=.example.com; Secure
- 쿠키는 서버가 발급.
- 클라이언트는 쿠키 저장하고 요청마다 전송
- 쿠키에 세션ID 등이 적혀있음
- 사용자는 쿠키 주고, 서버는 쿠키로 사용자 식별
대충 쿠키 플로우 요약
1. 아이디, 비번으로 접속
2. 서버가 ID 담은 쿠키 생성, 발급
3. 요청마다 쿠키 전송
4. 서버는 쿠키의 ID 정보로 유저 식별, 로그인 유지, 정보 저장
그 외) 세션ID는 세션 종료하면 소멸, 일반 쿠키는 저장되기에 자동 로그인 가능
- 옛날엔 쿠키에 아이디랑 비번 정보가 있었음
+ 근데 쿠키 훔쳐보는 거 너무 쉬움
-> 해킹 너무 쉬움
-> 쿠키에 아디, 비번 대신 세션 ID만 담기로 변경
-> 세션 하이재킹(로그인된 상태 훔치기) 유행
-> 보안 대응은 필수
쿠키 옵션 요약: 도메인, 경로, 기한, 보안
- Domain : 지정 도메인의 서브 도메인에서도 쿠키 사용 가능
안 하면 오로지 정확한 도메인에서만 쿠키 사용 가능 - Path : 지정 경로의 하위 경로에서도 쿠키 사용 가능
- expires : xxxx년 xx월 xx일 xx시 xx분 xx초까지 유효
- Max-Age : 발급일로부터 xx초 동안 유효
- secure : https만 쿠키 전송
- HttpOnly : XSS 공격 방지. JS로 쿠키 접근 불가능
- SameSite : XSRF 공격 방지. 요청 도메인 = 쿠키 도메인일 때만 쿠키 전송
6-2. 캐시
3mb 이미지를 요청하고, 10초마다 같은 이미지를 재요청하면 어떻게 될까?
원래는 매번 다운로드.
캐시 쓰면? 저장했다가 재사용.
장점
- 성능: 재요청 때 시간, 자원 절약(가장 중요)
- 오프라인 작업 : 인터넷 끊겨도 캐시는 볼 수 있음
- 물리적 효율 : 배터리 절약 등
단점
- 성능: 많이 쌓이면 용량 부담
- 손상된 캐시 : 캐시에 손상 생기면 에러
- 오래된 캐시 : 최신 버전 나와도 캐시 때문에 구버전 볼 수 있음
캐시 기한 끝나면?
- last-modified
최근 업데이트 일자로 판단
업데이트가 없었으면 서버는 캐시 재사용하라고 304 응답(빈 body)
요청하려면 if-modified-since: 를 사용한다
한계: 가볍게 업데이트해도 재다운로드해야 함 - ETag(Entity Tag)
캐시에 붙인 etag만 비교해보고 판단
요청할 땐 if-none-match 사용
캐시 쓰기 싫으면?
안 쓰겠다고 명시. 안 그러면 브라우저가 자동으로 캐시함.
- no-cache : 매 요청마다 캐시 검증받고 갱신 or 일회성 재사용
- no-store : 캐시 자체를 금지
- must-revalidate : 기한 끝났으면 검증
접근 실패 시 반드시 오류 발생시켜야 함 - no-cache & must-revalidate: 프록시에서 검증
- 서버 연결이 고장나면?
no-cache: 옛날 캐시 반환. (2월 25일 요청인데 1월 11일 결과로 응답)
must-revalidate: 504 에러
웹 캐시(프록시 서버)?
외국 서비스 빠르게 이용 가능한 이유:
각지에 프록시가 요청을 캐치하고 사용자들한테 캐시를 줌.
리버스 프록시?
프록시: 클라이언트 대신 요청, 클라이언트를 익명화.
리버스 프록시: 서버 대신 수신, 오리진 서버를 익명화, 로드 밸런싱(트래픽 고루 분산), 실험 기능 테스트 등등
CDN(Content Delivery Network)?
프록시로 사용자들에게 컨텐츠를 빠르게 제공하는 서비스
7. 마무리
HTTP 통신에 대해 가볍게 알아봤다.