본문 바로가기

문돌이 존버/프로그래밍 스터디

HTTP 기본 개념 특성 다지기

반응형
본 글은 인프런의 "모든 개발자를 위한 HTTP 웹 기본 지식" 강의를 듣고 정리한 일지입니다.

HTTP(Hypertext Transfer Protocol)

브라우저와 웹서버 사이에서 하이퍼텍스트 문서를 전송할 때 사용되는 프로토콜을 HTTP라고 부른다. 브라우저는 사용자 컴퓨터에 설치되어 있고, 사용자가 요청하는 자료를 얻어 와서 사용자에게 체계화된 방식으로 보여주는 일을 담당한다.

현재는 텍스트뿐만 아니라 이미지, 영상, 음성, 파일 등 모든 것을 HTTP 메시지에 전송하고 있다. 서버간에 데이터를 주고 받을 때 역시 대부분 HTTP를 사용한다. 데이터를 텍스트 파일로 표현하기 위한 표기체계는 HTML, XML, JSON 등이 있다.

HTTP는 버전이 여러 가지인데, TCP 위에서 동작하는 것은 HTTP/1.1, HTTP/2이고, UDP 위에서 동작하는 것은 HTTP/3이다. 현재는 HTTP/1.1을 주로 사용하고 있지만 HTTP/2, HTTP/3도 점점 증가하는 상황이다.

(추가) 
HTTP/1.1과 HTTP/2 간의 차이

HTTP/1.1이 느린 이유
1) HTTP Pipelining
- 연결당 하나의 요청과 응답을 처리하기 때문에 동시 전송 문제와 다수의 리소스를 처리하기에 속도와 성능 이슈 발생(Network Latency)
2) HOL(Head Of Line) Blocking(특정 응답 지연)
- HTTP/1.1 사양상의 제한으로 클라이언트의 리퀘스트 순서와 서버의 응답순서는 동기화 필요, 즉 서버는 요청을 받은 순서대로 응답을 하기 때문에 뒤에 오는 요청은 앞산 요청에 의해 지연됨
3) 헤더 크기 증가
- 헤더에 많은 메타 정보들이 저장
- 클라이언트의 매 요청마다 중복된 헤더 값을 전송하고, 이때 각 도메인에 설정된 쿠키 정보도 헤더에 포함되어 전송

HTTP/2.0이 빠른 이유
1) Multiplexed Streams
- 한 커넥션에 여러 개의 메시지를 동시에 주고받을 수 있음
<출처: 구글 디벨로퍼스>

2) Stream Prioritization
- 응답에 대한 우선순위를 정해 우선순위가 높을수록 응답을 빨리 함
3) Header Compression
- 헤더 정보를 HPACK(Huffman Coding 사용) 압축 방식을 이용하여 압축 전송, 이로써 이전 요청과 중복되는 헤더 필드는 전송하지 않아 네트워크 자원 낭비 감소
- Huffman Coding: 데이터 문자의 빈도에 따라 다른 길이의 부호를 사용하는 알고리즘
<출처: 구글 디벨로퍼스>

4) Server Push
- HTML 문서 상에 필요한 리소스를 클라이언트 요청 없이 보내줄 수 있음

참고
https://goldfishhead.tistory.com/26
https://ssungkang.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-11-VS-HTTP-20
https://velog.io/@taesunny/HTTP2HTTP-2.0-%EC%A0%95%EB%A6%AC

클라이언트 서버 구조

다시 말해 Request <-> Response 구조이다. 클라이언트는 서버에 요청을 보내고, 서버로부터 응답을 대기하며 서버는 요청에 대한 결과를 만들어 응답하게 된다.

이런 분리된 구조를 통해 각각 독립적으로 진화를 할 수 있다는 것이 중요하다. 클라이언트는 UI, 사용자 경험에 집중하면 되고, 서버는 트래픽 대응에 맞춰 아키텍쳐 및 백엔드 서버만 고민하면 된다.

Stateful, Stateless 차이

Stateful - 상태 유지 / Stateless - 무상태

상태 유지
- 클라이언트의 요청이 들어오는 중간에 서버가 바뀌면 안된다(바뀐다 하더라도 상태 정보를 다른 서버에 알려줘야 한다).
- 중간에 서버가 장애나면 클라이언트는 처음부터 다시 요청해야 하는 문제가 있다.
무상태 - 스케일 아웃(수평 확장에 유리)
1. 중간에 다른 서버로 바뀌어도 되며, 갑자기 클라이언트 요청이 증가해도 서버를 대거 투입할 수 있다.
2. 또한, 응답 서버를 쉽게 바꿀 수 있어 무한한 서버 증설이 가능하다.

실무의 한계
1. 모든 것을 무상태로 설계할 수 없는 경우도 있다.
2. 상태 유지를 꼭 해야 하는 경우(로그인)
- 일반적으로 브라우저 쿠키와 서버 세션 등을 사용해 상태 유지
3. 클라이언트가 추가 데이터를 전송해야 한다.
=> 그럼에도 상태 유지는 최소한만 사용하는 것이 낫다.

비연결성(Connectionless)

연결 유지 모델: 서버가 연결을 계속 유지해야 하므로, 서버 자원이 소모된다.

비연결 모델: 서버는 한 클라이언트의 요청을 받고 응답을 하면 TCP/IP 연결을 종료, 연결율 유지하지 않으므로 최소한의 자원을 사용한다.

비연결성 장점
- HTTP는 기본적으로 연결을 유지하지 않는 모델
- 일반적으로 초 단위 이하의 빠른 속도로 응답
- 1시간 동안 수천명이 서비스를 이용해도 실제 서버에서 동시에 처리하는 요청은 수십개 이하로 매우 작음
(물론, 같은 시간에 발생하는 대용량 트래픽을 처리하는 방법 필요 -> 어쨌든 무상태 서버를 지향한다)
- 서버 자원을 효율적으로 사용 가능
비연결성 한계와 극복
- TCP/IP 연결을 새로 맺어야 함(3 way handshake 시간 추가)
- 웹 브라우저로 사이트를 요청하면 HTML 뿐만 아니라 자바스크립트, css, 추가 이미지 등 수많은 자원이 함께 다운로드
- 현재 HTTP 지속 연결(persistent connections)로 문제 해결
(기존에는 HTML, css, 이미지 등 하나씩 연결 및 종료 반복 -> 현재는 웹 페이지 하나의 요청이 끝날 때까지 연결 유지)
- HTTP/2, HTTP/3에서 더 많은 최적화

HTTP 메시지

start-line - 어떤 메시지인지 서술
header - 속성
empty line(CRLF=Carriage Return Line Feed)
message body - 데이터를 담고 있고, 공백일 수도 있음
CR: 커서의 위치를 맨 앞으로 이동
LF: 커서를 한 칸 아래로 이동

시작 라인(요청 메시지)

start-line = request-line / status-line
request-line = method SP(공백) request-target SP HTTP-version CRLF(엔터)
예시) GET /search?q=python&hl=ko HTTP/1.1

메서드
- GET, POST, PUT, PATCH, DELTE 등이 존재

요청 대상
- absolute-path[?query]
- 절대경로= "/" 로 시작하는 경로(참고로 *, http://...?x=y와 같이 다른 유형도 존재)

시작 라인(응답 메시지)

start-line = request-line / status-line
status-line = HTTP-version SP status-code SP reason-phrase CRLF

HTTP 상태 코드
- 요청의 성공 혹은 실패를 나타냄
- 200(성공), 400(클라이언트 요청 오류), 500(서버 내부 오류)
- 이유 문구는 사람이 이해할 수 있는 짧은 상태 코드를 설명할 수 있는 글이어야 함

헤더

header-field = field-name":" OWS field-value OWS (OWS: 띄어쓰기 허용)
예시) Host:www.google.com(요청 메시지) 
         Content-Type: text/html;charset=UTF-8(응답 메시지)
         Content-Length: 3423

field-name은 대소문자 구분 없음

용도
1. HTTP 전송에 필요한 모든 부가정보가 포함됨
(메시지 바디 내용, 크기, 압축, 인증, 요청 클라이언트(브라우저) 정보, 캐시 관리 정보 등...)
2. 필요시 임의의 헤더 추가 가능(단, 약속한 클라이언트만 이해 가능)

메시지 바디

실제 전송할 데이터
(HTML 문서, 이미지, 영상, JSON 등등 byte로 표현할 수 있는 모든 데이터 전송 가능)

HTTP API 설계

가장 중요한 것은 리소스(Resource)를 식별하는 것이다. 회원을 등록하고 수정, 조회하는 것이 리소스가 아니라 회원이라는 개념 자체가 바로 리소스다. 

그렇다면 어떻게 식별하는 것이 좋은가? 바로 회원 등록, 수정, 조회를 모두 배제하고 회원이라는 리소스만 식별하면 된다. 즉 회원 리소스를 URI에 매핑하는 것이다. 

API URI 설계
리소스 식별 / URI 계층 구조 활용
- 회원 목록 조회 /members
- 회원 조회 /members/{id}
- 회원 등록 /members/{id}
- 회원 수정 /members/{id}
...

위의 예시에서 조회, 등록, 수정은 어떻게 구분할까? 

URI는 리소스만 식별하는 것이다. 리소스와 해당 리소스를 대상으로 하는 행위를 분리하는 것이 좋다. 리소스는 회원이 되고, 행위는 조회/등록/삭제/변경이 되는 것이다. 이를 다시 말하면 리소스는 명사이고, 행위는 동사가 된다. 이때 행위를 구분하는 것이 HTTP 메서드이다. 

HTTP 메서드

클라이언트가 서버에 요청을 할 때 기대하는 행동

GET: 리소스 조회
POST: 요청 데이터 처리, 주로 등록에 사용
PUT: 리소스를 대체, 해당 리소스가 없으면 생성
PATCH: 리소스 부분 변경
DELETE: 리소스 삭제

GET

- 리소스 조회
- 서버에 전달하고 싶은 데이터는 query(쿼리 파라미터, 쿼리 스트링)를 통해서 전달
- 메시지 바디를 사용해서 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않음

POST

- 메시지 바디를 통해 서버로 요청 데이터 전달
- 서버는 요청 데이터를 처리(메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을 수행)
- 주로 전달된 데이터로 신규 리소스 등록, 프로세스 처리에 사용

ex) 전달 받은 데이터를 서버가 처리하고 응답 데이터를 클라이언트에 보낼 때는 성공했다면 보통 "201 Created"를 헤더에 표시하고, 데이터 저장 경로(Location: /members/100) 역시 표시해준다.

POST가 하는 일은 사실 굉장히 많다. 따라서 POST 요청이 오면 요청 데이터를 어떻게 처리할지 리소스마다 따로 정해야 한다.
1. HTML 양식에 입력된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
(HTML FORM에 입력한 정보로 회원 가입, 주문 등에서 사용)
2. 게시판, 뉴스 그룹, 메일링 리스트, 블로그 또는 유사한 기사 그룹에 메시지 게시
(게시판 글쓰기, 댓글 달기)
3. 서버가 아직 식별하지 않은 새 리소스 생성
(신규 주문 생성)
4. 기존 자원에 데이터 추가
(한 문서 끝에 내용 추가하기)

정리
1. 새로운 리소스 생성(등록)
- 서버가 아직 식별하지 않은 새 리소스 생성
2. 요청 데이터 처리
- 단순한 데이터를 생성하거나, 변경하는 것을 넘어서 프로세스스를 처리해야 하는 경우
예) 주문 -> 결제 완료 -> 배달 시작 -> 배달 완료 처럼 단순히 값 변경을 넘어 프로세스의 상태가 변경되는 경우
- POST 결과로 새로운 리소스가 생성되지 않을 수도 있음
예) POST /orders/{orderid}/start-delivery (컨트롤 URI) <- 리소스로만 식별하지 못할 때 동사로 이루어진 컨트롤 URI 사용
3. 다른 메서드로 처리하기 애매한 경우
예) JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우(몇몇 서버가 GET을 지원하지 않음)

PUT

- 리소스 대체
-> 리소스가 있으면 대체
-> 리소스가 없으면 생성

- 클라이언트가 리소스를 식별(POST와의 차이점)
-> 클라이언트가 리소스 위치를 알고 URI 지정

주의!
- 리소스를 완전히 대체한다(일부 수정이 안됨, 즉 실수로 데이터 필드를 하나 생략해버리면 해당 필드는 결과값에 반영이 되지 않음)

PATCH

- 리소스 부분 변경

DELETE

- 리소스 제거

HTTP 메서드의 속성

안전(Safe)

- 호출해도 리소스를 변경하지 않는다.(GET이 안전)
(참고) 계속 호출해서 쌓이는 로그로 인한 장애 발생까지는 고려하지 않는다. 안전은 해당 리소스만 고려한다.

멱등(Idempotent)

- f(f(x)) = f(x)
- 한 번 호출하든 두 번 호출하든 100번 호출하든 결과가 똑같다.
ex) 
GET: 한 번 조회하든, 두 번 조회하든 같은 결과 조회
PUT: 결과를 대체한다. 따라서 같은 요청을 여러 번 해도 최종 결과는 같다.
DELTE: 결과를 삭제한다. 같은 요청을 여러 번 해도 삭제된 결과는 같다.
POST: 멱등 X, 두 번 호출하면 같은 결제가 중복해서 발생

활용 사례
- 자동 복구 메커니즘
- 서버가 TIMEOUT 등으로 정상 응답을 주지 못했을 때, 클라이언트가 같은 요청을 다시 해도 되는지에 대한 판단 근거(같은 요청을 해도 결과는 같을 것이라면, 즉 멱등하다면 가능)

만약 재요청 중간에 다른 곳에서 리소스를 변경해버린다면 멱등이 성립될 수 없다. 예를 들어, 처음 GET으로 정보를 가져왔을 때랑 이후 다른 클라이언트가 PUT으로 정보를 바꾼 뒤 GET을 하면 당연히 결과가 다를 것이다. 따라서 멱등은 동일한 사용자가 똑같은 요청을 했을 때 결과값이 달라지는지 여부에 대해서만 해당한다.

캐시가능(Cacheable)

- 응답 결과 리소스를 캐시해서 사용해도 되는가?
- GET, HEAD, POST, PATCH 캐시가능
- 실제로는 GET, HEAD 정도만 캐시로 사용
(POST, PATCH는 본문 내용까지 키로 고려해야 하는데, 구현이 쉽지 않음)

 

728x90
반응형