HTTP(Hypertext Transfer Protocol)는 텍스트 기반의 통신 규약으로 인터넷에서 데이터를 주고받을 수 있는 프로토콜입니다.
클라이언트-서버 모델을 따르며, 애플리케이션 레벨의 프로토콜로 TCP/IP위에서 작동합니다.
HTTP는 클라이언트가 HTTP 메시지를 통해 서버에 요청을 보냅니다.
서버가 요청에 대한 결과를 만들어서
응답이 오면 클라이언트가 응답 결과를 열어서 동작을 하게됩니다.
클라이언트와 서버들은 (데이터 스트림과 대조적으로) 개별적인 메시지 교환에 의해 통신합니다.
보통 브라우저인 클라이언트에 의해 전송되는 메시지를 요청(requests)이라고 부르며,
그에 대해 서버에서 응답으로 전송되는 메시지를 응답(responses)이라고 부릅니다.
이 요청과 응답 사이에는 여러 개체들이 있는데,
예를 들면 다양한 작업을 수행하는 게이트웨이, 캐시나 필터링등 여러 역할을 하는 프록시 등이 있습니다.
Request Response
구조3-tier 아키텍처: 서버에서 데이터베이스를 분리하여 따로 두는 방법을 3-tier 아키텍처라 합니다. 서버는 리소스만 내려주고 실제 리소스들은 데이터베이스에서 가져옵니다.
TCP/IP는 패킷 통신 방식의 인터넷 프로토콜인 IP (인터넷 프로토콜)와 전송 조절 프로토콜인 TCP (전송 제어 프로토콜)로 이루어져 있습니다.
IP는 패킷 전달 여부를 보증하지 않고, 패킷을 보낸 순서와 받는 순서가 다를 수 있습니다.
TCP는 IP 위에서 동작하는 프로토콜로, 데이터의 전달을 보증하고 보낸 순서대로 받게 해줍니다.
HTTP, FTP, SMTP 등 TCP를 기반으로 한 많은 수의 애플리케이션 프로토콜들이 IP 위에서 동작하기 때문에, 묶어서 TCP/IP로 부르기도 합니다.
비연결성은 클라이언트와 서버가 한 번 연결을 맺은 후, 클라이언트 요청에 대해 서버가 응답을 마치면 맺었던 연결을 끊어 버리는 성질을 말합니다.
서버와 클라이언트의 연결을 유지하는 모델:
클라이언트가 서버에 요청을 보내면
응답을 받고 난 후에도 계속 서버에 접속된 상태로 남아있습니다.
따라서 서버에 접속하는 클라이언트가 많아질수록 연결을 유지하는 서버의 자원이 계속해서 소모합니다.
서버와 클라이언트의 연결을 유지하지 않는 모델:
클라이언트가 서버에 요청을 보내고
응답을 받게되면 바로 클라이언트와 서버의 연결을 끊어버립니다.
따라서 서버는 최소한의 자원만을 사용하게 됩니다.
웹이 진화하면서, 각 웹 사이트에서 필요한 자원 (CSS, 자바스크립트, 이미지 등등)의 양이
해가 갈수록 증가함에 따라 웹 페이지를 다운로드받고 렌더링하기 위해
브라우저는 더 많은 동시성이 필요로 하게 되었습니다.
그때마다 TCP/IP를 위한 3 way Handshake시간이 추가되므로
현재는 여러가지 방법으로 문제를 해결합니다.
해당 내용은 아래에서 좀더 자세히 설명하겠습니다.
연결 요청시. 3Way HandShake :
연결 종료시, 4 Way HandShake:
서버는 클라이언트 상태를 보존하지 않음(Stateless)
간단하게 서버가 클라이언트의 상태를 보존하지 않는다는것을 뜻합니다.
상태 유지
클라이언트의 요청을 받은 특정 서버만이
해당 유저를 기억하고있기에
항상 그 서버만 응답해야합니다.
만일 이 특정서버가 장애가 난다면,
유저의 상태가 사라지기 때문에
처음부터 다시 서버에 요청해야합니다.
무상태
상태가 유지되지 않기 때문에, 아무 서버에서 호출이 가능하고,
서버에서 장애가 생기더라도 다른 서버에서 응답을 전달 할수 있습니다.
즉 응답 서버를 쉽게 바꿀수 있기 때문에 수평적인 확장에 유리합니다.
초기에는 버전 번호가 존재하지 않았지만,
이후에 다른 버전들과 구분하기 위해서 0.9라는 버전을 붙이게 되었습니다.
GET /mypage.html
0.9 버전은 단일 라인으로 구성 되었으며 메소드는 GET이 유일했습니다.
또한 상태코드들 역시 존재하지 않았으며 응답 파일 역시 단순하게 파일 내용 자체로만 구성되었습니다
<HTML>
A very Simple HTML page
</HTML>
시간이 지나 1900년대에 인터넷 시장이 점차 커지기 시작하자 W3C가 만들어지며 HTML의 발전이 시작되었고,
이로 인해 HTTP 프로토콜이 개선되기 시작했습니다.
[
RFC 1945 - Hypertext Transfer Protocol -- HTTP/1.0
Hypertext Transfer Protocol -- HTTP/1.0 (RFC )
https://datatracker.ietf.org/doc/html/rfc1945
](https://datatracker.ietf.org/doc/html/rfc1945)
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
A page with an image
<IMG SRC="/myimage.gif">
</HTML>
HTTP 1.0에는 여러가지 특징들이 추가되어 좀더 융통성 있게 확장되었습니다.
Content-Type
: Content-Type은 리소스의 media type을 나타내기 위해 사용됩니다
상태 코드는 3자리 숫자로 만들어져 있으며, 첫번째 자리는 1에서 5까지 제공됩니다.
첫번째 자리가 4와 5인 경우는 정상적인 상황이 아니기 때문에 사이트 관리자가 즉시 알아야 하는 정보입니다.
간략하게 상태코드에 대해 설명하자면 다음과 같습니다
HTTP는 TCP 기반에서 이루어진 프로토콜이기 때문에 연결을 하고, 연결을 끊을때마다 핸드셰이킹이 발생해 다량의 오버헤드가 발생할수 있습니다.
점점 웹의 추세가 텍스트에서 미디어로 변화 하면서 그에 따른 오버헤드가 커지기 시작했습니다.
그리고 사용자의 상태를 유지해야하는 기술들도 요구 되다 보니, 그에 따른 성능개선이 필요해졌습니다.
...현재는 여러가지 방법으로 문제를 해결합니다....
위에서 언급한 해당 부분에 대해 좀더 이야기 해보겠습니다.
TCP의 다양한 제어 기능을 수행하기 위해서 TCP 헤더에는 다양한 정보가 필요합니다. 그러다 보니 데이터를 전송할 때에 꼭 필요하지 않은 처리나 정보가 포함될 수도 있습니다. 이처럼 필요 없는 처리나 정보를 통틀어서 오버헤드(Overhead)
라고 부릅니다. TCP의 데이터 전송에는 많은 오버헤드가 발생할 가능성이 있습니다.
따라서 1.1의 가장 큰 특징은 다음과 같습니다.
언급 했던것 처럼, 초창기에는 컨텐츠의 수가 그렇게 많지 않아 TCP연결이 부담스럽지 않았지만, 컨텐츠의 발달로 인해 많은 양의 컨텐츠를 보여줘야 했습니다. 따라서 TCP의 부담을 줄이기 위해 TCP연결을 재사용 해야하는 필요성이 대두 되었습니다.
왼쪽과 같이 HTTP 1.0 버전에서는 요청마다 TCP를 새로 연결 시켜줘야 하지만, 오른쪽과 같은 HTTP 1.1버전에선
지속 연결(persistent Connetion)을 통해 하나의 TCP 연결을 통해 여러개의 콘텐츠를 요청할 수 있습니다.
덕분에 TCP 세션에 대한 부하를 줄일수 있고, 클라이언트의 응답속도 역시 개선 할 수 있습니다.
웹에서의 커넥션 재사용을 Keep-alive 또는 Connection reuse 라 하며,
HTTP/1.0 에서는 클라이언트가 서버에게 요청하는 Request Header에
다음과 같은 값을 통해 연결을 유지했습니다.
Connection: keep-alive
HTTP/1.1 에서는 이 헤더를 사용하지 않더라도 모든 요청/응답이
Connection을 재사용하도록 설계되어 있으며, 필요없는 경우에만
TCP 연결을 종료하는 방식으로 변경되었습니다.
Connection: close
해제 시에 위와 같은 명시적 Connection 헤더를 이용하며, 그렇지 않은 경우 무조건 연결은 재사용합니다.
위의 그림에서 연결되었을 때를 보겠습니다
파이프라이닝(PipeLining)은 하나의 연결에서 한번에 순차적인 여러 요청을 연속적으로 하고
그 순서에 맞게 응답을 받는 순으로 서버는 응답하게 됩니다.
HTTP/1.1 에서 클라이언트는 각 요청에 대한 응답을 기다리지 않고,
여러개의 HTTP Request 를 하나의 TCP/IP Packet 으로 연속적으로 Packing 해서 요청을 보냅니다.
파이프라이닝이 적용되면, 하나의 Connection 으로
다수의 Request 와 Response 를 처리할 수 있게끔 Network Latency를 줄일 수 있습니다.
하지만 다르게 생각해보면 응답처리를 미루는 방식이기에, 각 응답에 대한 처리는 순차적으로 진행되어야하고, 그에 따라 후순위의 응답은 지연될 수 밖에 없습니다.
이를 HOL (Head Of Line) Blocking - 특정 응답의 지연이라 합니다
| --- a.png --- |
| --- b.png --- |
| --- c.png --- |
순서대로 첫번째 이미지를 요청하고 응답 받고 다음 이미지를 요청하게 되는데
만약 첫번째 이미지를 요청하고 응답이 지연되면 아래 그림과 같이 두, 세번째 이미지는
당연히 첫번째 이미지의 응답 처리가 완료되기 전까지 대기하게 되며
이와 같은 현상을 HTTP의 HOL Blocking (Head of Line Blocking) 이라 부르며 pipelining의 큰 문제점 중 하나입니다.
| ---------- a.png ------------| | -b.png-|
| -c.png- |
RTT (Round Trip Time, 왕복 시간)
는 패킷망(인터넷) 위에서 패킷을 보내고자 하는 측에서 패킷을 목적지에 보낼 때, 패킷이 목적지에 도달하고 나서 해당 패킷에 대한 응답이 출발지로 다시 돌아오기까지의 시간.
즉, RTT는 패킷 왕복 시간
입니다. 네트워크 성능을 측정할 때, RTT
는 네트워크 연결의 속도와 안정성을 진단할 때 일반적으로 사용됩니다.
파이프라이닝은 또한 RTT가 증가합니다.
매 요청별로 connection을 만들게 되고
TCP상에서 동작하는 HTTP의 특성상 3-way Handshake 가 반복적으로 일어나고
또한 불필요한 RTT증가와 네트워크 지연을 초래하여 성능이 저하되는 문제점이 발생합니다.
따라서 당시에는 최선의 방법이였으나, 현재에는 산발적으로 존재하는 우려점 때문에
현재 HTTP2.0을 지원하는 요즈음의 브라우저들은 파이프라이닝 기능을 막고있습니다.
이런것이 존재했다 라는 점을 간단히 시사하기 위해 적습니다.
파이프라이닝을 개선하기 위해 브라우저들은 여러개의 Connection을 생성해서 병렬로 요청을 보냅니다.
이를 Domain Sharding이라고 부릅니다.
브라우저별로 Domain당 Connection 개수의 제한이 존재합니다.
호스트 헤더의 등장으로 동일한 IP주소에 다른 도메인을 호스트하는 기능이 가능해졌습니다.
GET /en-US/docs/Glossary/Simple_header HTTP/1.1
ACCEPT: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101
호스트 헤더가 없던 HTTP1.0 버전에는 위의 요청이 서버로 바로 요청이 될것입니다.
웹서버는 위의 정보로는 어떤 회사의 홈페이지로 접속했는지 알 수 없기 때문에 IP주소로만 구분이 가능합니다.
즉 이전에는 도메인마다 IP를 준비해야했고, 이는 도메인 만큼 서버의 개수도 늘어났습니다.
GET /en-US/docs/Glossary/Simple_header HTTP/1.1
ACCEPT: */*
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101
HTTP 1.1의 등장으로 이젠 IP주소 대신 Host의 헤더값으로 고객사를 구별 할수 있게 되어
하나의 웹서버에 여러개의 애플리케이션을 구동할 수 있게 되었습니다.
SPDY는 Google에서 개발한 시험용 프로토콜입니다.
구글은 더 빠른 Web을 실현하기 위해 Latency 관점에서 HTTP를 고속화한
SPDY(스피디) 라는 새로운 프로토콜을 구현했습니다.
SPDY는 실제로 HTTP/1.1에 비해 상당한 성능 향상과 효율성을 보여줬고 이는 HTTP/2 초안의 참고 규격이 되었습니다.
HTTP/2의 주요 목표는 다음과 같습니다
이러한 요구사항을 구현하기 위해 다양한 보조 프로토콜들을 개선하였습니다.
HTTP/2는 HTTP의 애플리케이션 의미 체계를 어떤 식으로도 수정하지 않습니다.
모든 핵심 개념(예: HTTP 메서드, 상태 코드, URI 및 헤더 필드)은 그대로 유지됩니다.
그 대신 HTTP/2는 클라이언트와 서버 간에 데이터 서식(프레임)이 지정되는 방식과 데이터가 전송되는 방식을 수정합니다.
클라이언트와 서버는 전체 프로세스를 관리하며 애플리케이션의 모든 복잡성을
바이너리 프레이밍계층 내에 숨깁니다.
따라서 기존의 모든 애플리케이션을 수정 없이 전달할 수 있습니다.
이 모든 내용을 간단하게 말하자면
모든 노력을 속도향상에 쏟아부었다고 해도 과언이 아닐정도로
속도향상에 많은 노력을 기울인 버전입니다.
주요 특징은 다음과 같습니다.
HTTP/2 는 TCP 계층과의 사이에 새로운 바이너리 프레이밍 계층 Binary Framework을 통해
네트워크 스택을 구성합니다.
기존에 텍스트 기반으로 Header 와 Data 가 연결되고 있던 1.1 이하 버전과 다르게
HTTP/2 는 전송에 필요한 메시지들을 Binary 단위로 구성하며 필요 정보를
더 작은 프레임으로 쪼개서 관리합니다.
여기서 데이터를 Binary Encoding 방식으로 관리하는데,
인코딩된 데이터를 다루기 위해서는 반드시 Decoding 과정이 필요하게 됩니다.
즉, HTTP/1.1 버전의 클라이언트는 HTTP/2 버전의 서버와 통신이 불가능합니다.
HTTP2.0에서는 Message 외에 새로운 전송 단위가 추가되었습니다.
즉 여러개의 프레임이 하나의 메세지를 이루고, 그 메세지들이 모여 하나의 스트림을 만듭니다.
HTTP/1.x의 요청과 응답의 메세지가 HTTP/2.0에서는 요청과 응답을 스트림으로 묶을수 있는 구조가 된것입니다.
수많은 증기(프레임이)들이 하나의 물방울(메세지)가 되고, 그것들이 모여 양쪽으로 흐르는 물줄기(스트림)가 됩니다.
파이프라인 및 도메인 샤딩으로 해결하기 힘들었던
HOL (Head Of Line) Blocking - 특정 응답의 지연문제를
한 커넥션으로 동시에 여러 개의 메세지를 주고 받을 있으며, 응답은 순서에 상관없이
바이너리 Frame의 스트림 전송으로 처리 할수 있게 되었는데,
이를 멀티플렉싱MultiFlexing이라 합니다
요청, 또는 응답은 뒤섞여 도착하게 되고, 뒤섞인것이 도착한곳. 즉 서버나 클라이언트 단에서
해당 프레임을 다시 재조립합니다.
이처럼 바이너리 프레이밍과 멀티플렉싱을 사용하여 여러 요청과 응답을 병렬처리 할 수 있습니다.
HTTP 메시지가 많은 개별 프레임으로 분할될 수 있고 여러 스트림의 프레임을
멀티플렉싱(Multiplexing)할 수 있게 되면서, 스트림들의 우선순위를 지정할 필요가 생겼습니다.
클라이언트는 우선순위 지정을 위해 ‘우선순위 지정 트리'를 사용하여 서버의 스트림처리 우선순위를 지정할 수 있고,서버는 우선순위가 높은 응답이 클라이언트에 우선적으로 전달될 수 있도록 대역폭을 설정합니다.
클라이언트가 만일 두번의 요청을 보낸다고 가정하면
HTTP/1.x의 경우 두개의 요청 Header에 중복 값이 존재해도 그냥 중복 전송합니다.
그러나 HTTP/2에선 Header에 중복값이 존재하는 경우 Static/Dynamic Header Table 개념을 사용하여
중복 Header를 검출하고 중복된 Header는 index값만 전송하고
중복되지 않은 Header정보의 값은 Huffman Encoding 기법으로 인코딩 처리 하여 전송합니다.
어떤 원리인지 조금 더 자세한 설명입니다
HTTP/2의 Header 압축은 내부적으로 HPACK이라고 불리는 Module이 담당하는데 HPACK은 Huffman Algorithm과 Static Table, Dynamic Table을 통해서 압축을 수행한다. Huffman Algorithm은 자주 나오는 문자열 순서대로 짧은 Bitmap으로 Mapping하여 Data를 압축하는 기법이다. Static Table은 HTTP/2 Spec에 정의된 Table로 HTTP/2 Header로 자주 사용되는 Key-value 값 쌍을 저장하고 있는 Table이다. Dynamic Table은 한번 전송/수신한 Header의 Key-value 값을 임의로 저장하는 Buffer 역할을 수행하는 Table이다. - Static Header Table : 모든 연결에 사용될 가능성이 있는 공용 HTTP 헤더 필드를 제공(ex.올바른 헤더 이름)
- Dynamic Header Table : 처음에는 비어있으며, 특정 연결에서 교환되는 값에 따라 업데이트 된다.
HTML을 구성하는데는 많은 컨텐츠가 존재합니다.
서버 푸시 기능이 존재하지않는 HTTP/1.x버전에서 컨텐츠를 요청햇을때, HTML을 요청하고 응답받게 되는데,
그 후 해당 HTML 안에 내장되어있는 자바스크립트파일, CSS파일 등을 또 추가로 요청하는 작업이 필요합니다.
그러나 HTTP/2.0버전에서는 서버 푸쉬를 지원합니다.
이는 요청한 HTML파일 내에 자바스크립트 파일, CSS파일 등이 존재할경우 응답을 전송할때 클라이언트가 요청하지 않은 컨텐츠 까지 같이 보내줄수 있습니다.
이로인해 전송에 필요한 RTT가 줄어들게 되어 응답속도를 개선시킵니다.
약간 시계를 옛날로 돌려 생각하자면
TCP는 70년대에 만들어진 규약이고, TLS은 보안을 가미시킨 통신 프로토콜입니다.
TCP를 사용하여 수많은 컨텐츠를 교환한다거나, TLS이 일반적으로 사용하는 암호화가 될지 몰랐습니다.
점차 하이퍼텍스트에서 하이퍼 미디어를 넘어가는 현대 사회에서는 더 빠른 통신의 필요성이 대두되기 시작했습니다.
HTTP/2.0의 단점은 다음과 같습니다
HTTP의 진화 과정중 항상 빠지지 않고 등장하는것은 바로 연결의 최소화입니다.
HTTP/1.x에선 파이프라이닝, 도메인 샤딩.
HTTP/2.0에선 바이너리 프레이밍 계층과 멀티플렉싱으로 TCP 핸드쉐이킹을 줄였지만
만일 HTTPS연결(HTTP + TLS + TCP)을 한다면 추가적으로 세션의 보안을 위해
SSL의 핸드쉐이킹이 발생합니다.
SSL의 핸드쉐이킹이 어떤 원리인지 조금 더 자세한 설명입니다
클라이언트가 서버에 연결을 시도하면서 전송하는 패킷입니다.
자신이 사용가능한 Cipher suite 목록, 세션아이디, ssl 프로토콜버전, 랜덤 바이트를 전달합니다.
Cipher suite의 알고리즘에 따라 데이터를 암호화합니다.
클라이언트가 보낸 ClientHello 패킷을 받아 Cipher suite중 하나를 선택하여 클라이언트에게 알립니다.
또한 서버의 SSL 프로토콜 버전을 보냅니다.즉 클라이언트가 보낸 여러개의 Cipher Suite중 하나를 선택해 전송합니다.
여기서 서버가 선택하는 Cipher Suite는 서버가 사용가능한 가장 높은 등급의 Suite입니다.
서버가 자신의 SSL 인증서를 클라이언트에게 전달합니다. 해당 인증서 내부는 서버가 발행한 공개키가 들어가 있습니다.
이후 클라이언트는 인증기관의 개인키로 암호화된 해당 인증서를 인증기관의 공개키를 사용하여 복호화를 합니다.
복호화에 성공한다면 이는 인증기관이 서명한것이 맞다는 것입니다. 즉 인증서 검증을 합니다.
만일 서버의 공개키가 SSL내부에 존재하지 않는다면 서버의 공개키를 전달하는 Server Key Exchange가 일어납니다. 존재한다면 해당 과정이 생략됩니다.
모든 과정이 끝날경우 서버가 행동을 마쳤음을 알립니다.
대칭키(데이터를 실제 암호화하는 키, 비밀키)를 클라이언트가 생성하여 서버의 공개키를 사용해 해당 대칭키를 암호화 한 후 서버에 전달합니다.
여기서 전달된 이 대칭키가 바로 SSL Handshake의 목적이자, 데이터를 실제 암호화할 대칭키(비밀키)입니다.
이제부터 이 키를 사용해 클라이언트와 서버간 전송할 데이터를 암호화합니다.
클라이언트와 서버 모두 서로에게 보내는 패킷으로, 교환할 정보들을 모두 교환하였으니 이제 통신할 준비가 되었음을 알리는 패킷입니다.
그리고 Finished 패킷을 보내 SSL HAndshake를 종료합니다.
요악하자면 다음과 같습니다
위에서 앞서 설명 한것처럼 파이프라이닝은 keep-alive를 전제로하여
하나의 커넥션에서 한번에 순차적인 여러 요청을 연속적으로 하고
그 순서에 맞춰 응답을 받는 방식으로 지연 시간을 줄이는 방법입니다.
허나 중간에 패킷이 유실되거나, 패킷의 파싱속도가 느리게된다면 후에 들어온 요청들은
앞서 들어온 요청들이 끝날때까지 기다려야만 합니다.
이를 해결하기 위한 가장 간단한 방법은 이를 모두 지원하고 보안하는
새로운 프로토콜을 만들면 되지 않는가 하는 생각을 할 수 있습니다.
실제 인터넷 트래픽은 거의 TCP와 UDP가 차지하고 있습니다.
가령 HTTP는 TCP 위에서 동작하고 DNS는 UDP나 TCP위에서 동작 합니다.
이러다 보니 TCP나 UDP가 아니면 아예 통과하지 못하는 라우터나 방화벽들도 존재하고 방화벽 설정을 할 때 TCP나 UDP 트래픽이 아니면 모두 막도록 설정하는 경우도 많습니다.
또한 가정용 라우터와 같이 NAT환경을 기본적으로 만드는 경우 TCP나 UDP가 아니라면 제대로 주소 변환이 안 될 수도 있습니다.
어떤 기업이든 나라든 새 프로토콜을 하나 정의했다고 해도
그것이 실제 인터넷상에서 항상 전송 가능한지 보장이 안된다는 것입니다.
그래서 구글에서는 눈을 돌려 UDP기반의 새로운 프로토콜을 설계했습니다.
이름부터 빠를것같은 퀵(QUIC)프로토콜은 다음과 같은 장점이 있습니다.
정보처리기사의 문제나, 다른 정의집들을 보면 UDP는 TCP에 비해 신뢰도가 없다고 그러는데 왜 UDP를 사용하나요?
정보처리기사를 준비하시거나, 학부에서 공부를 하셧거나 아니면 여러 호기심때문에 검색하셧다면
TCP와 UDP의 차이점은,
TCP는 신뢰 가능한 전송 프로토콜,
UDP는 신뢰성이 없다고 배웠을 것입니다.
신뢰성이 없다는 의미를 더 생각해 보면, UDP는 그냥 데이터를 실어 보낼 수 있을 뿐
그 이외의 기능은 아무것도 정의해 놓지 않았습니다.
UDP는 User Datagram Protocol의 약자인데 U를 Unreliable이라고 생각하는 사람도 적지 않을 것입니다.
TCP 헤더 포맷
UDP 헤더 포맷
전송 순서를 알기 위해서 시퀀스나 ACK 번호를 헤더에 정의해 놓은 TCP와는 달리
UDP 헤더에는 포트 번호와 패킷 크기, 체크섬만 있고, 체크섬은 굳이 채워넣지 않아도 됩니다.
따라서 신뢰성을 보장할 수단이 존재하지 않는 것입니다.
다만 달리 생각하면 이건 백지와도 같은 것입니다.
User Datagram 이라는 의미가 바로 사용자가 정의해서 사용하라는 의미입니다.
즉 특정한 기능을 넣는다면, 원하는대로 데이터를 전송할수 있습니다.
신뢰성 있는 전송을 원한다면 TCP와 같이 최대 전송 패킷수라던지,
서버와 클라이언트 간에 패킷 유실이나 순서 바뀜에 어떻게 대처할 것인지를 상호 정의 한다면
UDP기반의 신뢰성 있는 전송 프로토콜을 만들 수 있습니다.
대표적인 예로 UDT가 있습니다.
UDT: UDP-based Data Transfer Protocol, UDT is built on top of User Datagram Protocol (UDP), adding congestion control and reliability control mechanisms.
기존의 연결. TCP TLS을 사용하여 HTTPS연결을 시도할때는 TLS의 핸드쉐이킹이 각각 발생합니다.
TCP+TLS는 세션키를 주고 받은후, 암호화 연결을 한 뒤 세션키와 데이터를 교환하지만,
QUIC의 경우에는 데이터의 전달과 암호화 과정이 동시에 수행됩니다.
TCP는 연결을 식별하는 연결식별자Connection Identifier로 IP,Port를 사용하지만
Quic은 Connection Id를 사용하여 Initial Key 를 생성하여 데이터와 함께 전송합니다.
그 후, 클라이언트가 디피-헬먼 키 교환값을 반환하여 서버와 클라이언트가 initial key에 동의합니다.
동의한 즉시 데이터 교환이 시작되며 동시에 최종 세션키를 설정합니다.
한번 연결에 성공할 경우 해당 디피-헬먼 키교환값을 저장하고. 다음 연결때 마다 해당 값을 같이 보내
바로 연결을 또 시작하기에 0 RTT로 통신을 할수 있습니다.
핸드폰에서 와이파이를 사용하다가, 셀룰러 데이터를 사용해 인터넷에 접속시
일시적으로 약간 느리게 켜지는 경우를 다들 경험해 보셨을겁니다.
이는 클라이언트의 IP가 바뀌기 때문에 새로운 TCP 핸드쉐이킹을 통해 클라이언트를 식별해야하기 때문입니다.
반면 QUIC은 Connection ID를 사용하여 서버와 연결을 생성합니다.
Connection ID는 랜덤한 값일 뿐, 클라이언트의 IP와는 전혀 무관한 데이터이기 때문에
클라이언트의 IP가 변경되더라도 기존의 연결을 계속 유지할 수 있습니다.
이는 새로 연결을 생성할 때 거쳐야하는 핸드쉐이크 과정을 생략할수 있다는 뜻입니다.
HTTP/1.1과 HTTP/2의 전송에 있어서 큰 차이점은
멀티플렉싱 기능의 지원입니다. 여러개의 바이너리 프레임으로 병렬 처리하여 받기 때문에,
빠르게 컨텐츠를 받아올수 있었으나 이는 다르게 생각해보면 HOL Blocking에 취약하다는걸 알수있습니다.
멀티플렉싱은 모든 데이터를 하나의 파일중 일부라 생각하기 때문에, 만일 하나의 패킷이 손실난다면
그 뒤의 모든 패킷들이 지연되는 문제가 발생합니다.
허나 QUIC에서는 여러개의 동시 바이트스트림이 존재하고, 스트림별로 손실을 관리하기 때문에
손실이 존재하는 스트림을 제외한 다른 스트림에서의 데이터 전송은 정상적으로 도달할수 있습니다.
네이버
아직 HTTP/3.0은 그렇게 많은 곳에서 사용되고 있지 않습니다.
몇가지 산발된 문제점이 있어 기업들이 도입하기를 주저하고 있습니다.
TLS 1.3은 여전히 TCP 위에서 독립적으로 실행할 수 있지만 QUIC는 대신 TLS 1.3을 캡슐화합니다.
또한 이전에 암호화 하지않는 헤더 부분을 QUIC은 암호화 하기 때문에 이를 해소해야하는 등 여러가지 걸림돌이 존재합니다
하지만 구글,유튜브는 현재 HTTP/3.0으로 통신하고있습니다
구글
만약 HTTP/3.0의 걸림돌들이 해소된다면
아마 근 시일 안으로 HTTP의 대전제가 바뀔것입니다.
아마도 가까운 미래엔... : HTTP는
TCP/IPUDP기반으로 동작하는 프로토콜입니다
HTTP 메시지는 서버와 클라이언트 간에 데이터가 교환되는 방식입니다. 메시지 타입은 두 가지가 있습니다.
요청(request)은 클라이언트가 서버로 전달해서 서버의 액션이 일어나게끔 하는 메시지고,
응답(response)은 요청에 대한 서버의 답변입니다.
요청과 응답의 구조는 서로 닮았으며 다음과 같은 구조를 가지고있습니다.
HTTP 메세지의 시작줄과 HTTP 헤더를 묶어 요청 헤드(head)
HTTP 메세지의 페이로드는 본문(body)라고 합니다
HTTP 요청은 클라이언트가 서버로 보내는 메세지입니다.
Method:
GET
method는 리소스를 받아야 하고, POST
method는 데이터를 서버로 전송합니다특정 형식이란 : HTTP method 마다 다른 형식으로 정의됩니다.
?
와 쿼리 문자열이 붙는 절대 경로입니다.POST / HTTP 1.1
GET /goingHome.png HTTP/1.0
HEAD /destiny.html?query=Kenneth HTTP/1.1
OPTIONS /index.html HTTP/1.0
GET <
https://www.redprinting.co.kr/ko
> HTTP/1.1
foo://example.com:8042/over/there?name=ferret#nose
CONNECT
와 함께 사용할 수 있습니다.CONNECT
developer.mozilla.org:80
HTTP/1.1
OPTIONS
와 함께 별표(*
) 하나로 서버 전체를 표현합니다.OPTIONS * HTTP/1.1
헤더는 클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송할 수 있도록 해줍니다.
User-Agent, Accept-Type, Accept-Language
과 같은 헤더는 요청을 보다 구체화합니다.Referer
처럼 컨텍스트를 제공하거나 If-None
과 같이 조건에 따라 제약을 추가할 수 있다.Connection
등을 포함합니다Content-Length
와 같은 헤더는 body에 적용됩니다.GET, HEAD, DELETE, OPTIONS
처럼 서버에 리소스를 요청하는 경우에는 본문이 필요하지 않습니다.POST
나 PUT
과 같은 일부 요청은 데이터를 업데이트하기 위해 사용합니다.응답메세지의 시작줄은 상태줄이라 부르며 다음과 같은 정보와 형식을 가지고 있습니다.
200
, ,404
혹은 302
와 같이 상태코드를 돌려줍니다상태 줄은 일반적으로 HTTP/1.1 404 Not Found.
같이 생겼습니다.
:
), 값을 입력합니다.Vary, Accept-Ranges
와 같이 상태 줄을 넘어서는 추가정보를 제공합니다Content-Length
와 같은 헤더는 body에 적용된다. body가 비어있는 경우, Entity headers는 전송되지 않습니다.201, 204
와 같은 상태 코드를 가지는 응답에는 본문이 필요하지 않습니다.CORS는 Cross-Origin Resource Sharing
의 줄임말로, 교차 출처 리소스 공유입니다. 즉 다른 출처 접근하여
특정한 자원을 사용할수 있도록 해주는 권한을 부여하고, 그것을 브라우저에 알려주는 체제입니다.
CORS가 없이 모든 곳에서 데이터를 요청할 수 있게 되면,
다른 사이트에서 원래 사이트를 흉내낼 수 있게 됩니다.
예를 들자면 기존 사이트와 완전히 동일하게 동작하도록 하여 사용자가 로그인을 하도록 만들고,
로그인 했던 세션을 탈취하여 악의적으로 정보를 추출하거나 다른 사람의 정보를 입력하는 등 공격을 할 수 있습니다. 이렇게 공격을 할 수 없도록 브라우저에서 보호하고, 필요한 경우에만 서버와 협의하여 요청할 수 있도록 하기 위해서 필요합니다.
여기서 서로 다른 출처라는것을 판단하는 기준에 대해 예를 들어 알아보겠습니다
https://www.velog.io:443/fnrkp089?page=1#docker
여기서 프로토콜과, 호스트 그리고 포트번호 이 3가지가 같다면 동일한 호스트라 판단하고
아닐경우 다른 호스트로 판단합니다
URL | 같은 출처인가 | 이유 |
---|---|---|
https://www.velog.io/moon | O | 프로토콜,호스트,포트번호 같음 |
https://www.velog.io/moon?q=index | O | 프로토콜,호스트,포트번호 같음 |
https://www.velog.io/moon#sun | O | 프로토콜,호스트,포트번호 같음 |
http://www.velog.io/moon | X | 프로토콜 다름 |
https://www.velog.io:99/moon | X | 포트번호 다름 |
https://www.github.io | X | 호스트 다름 |
CORS는 CSRF라고 불리는 브라우저 취약점 공격으로부터 브라우저 사용자를 보호하기 위해만든 기능입니다.
즉, CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에,
브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않습니다.
그렇기에 포스트맨에서는 cors를 설정하지 않아도 통신할수 있는 이유입니다.
(사전요청 Preflight Request)
...HTTP method는 수행할 작업(GET, PUT, POST 등)이나 방식(HEAD or OPTIONS)을 알려줍니다....
단순 요청은, 요청(request)이 아래의 3가지 조건을 만족해야 단순 요청으로 동작합니다.
첫 번째 조건은 어렵지 않은 조건이지만 두번째, 세번째 조건은 이야기가 틀려집니다.
두번째 조건은 사용자 인증에 사용되는 Authorization
헤더도 포함되지 않아 까다로운 조건이며,
3번 조건은 많은 REST API들이 Content-Type
으로 application/json
을 사용하기 때문에 지켜지기 어려운 조건입니다.
따라서 단순요청이 아닌 요청은 사전요청Preflight Request을 통해 접근합니다
사전요청(Preflight)
은 일반적으로 웹 애플리케이션을 개발할 때 자주 마주치게 됩니다.
사전요청 방식은, 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송합니다.
이때 브라우저가 본 요청을 보내기 전에 보내는 사전 요청을 Preflight라고 부르며,
이 사전 요청에는 HTTP 메소드 중 OPTIONS
메소드가 사용됩니다.
크롬 개발자 도구의 네트워크 탭에 OPTIONS
메서드로 요청이 보내지는 것을 보신 적 있으시다면
CORS를 경험하셨던 것입니다.
Preflight 요청은 실제 리소스를 요청하기 전에 OPTIONS
라는 메서드를 통해 실제 요청을 전송할지 판단합니다.
OPTIONS /resources/post-here/ HTTP/2.0
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: content-type,x-pingaruner
Access-Control-Request-Method: POST
Connection: keep-alive
Host: aruner.net
Origin: http://arunranga.com
Referer: http://arunranga.com/
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
이렇게 본 요청을 보내기전 OPTION를 통해 예비 요청을 보내게 됩니다.
Origin
: Origin
헤더는 cross-site 접근 요청 또는 preflight request의 출처를 나타냅니다.Access-Control-Request-Method
: Access-Control-Request-Method
헤더는 실제 요청에서 어떤 HTTP 메서드를 사용할지 서버에게 알려주기 위해, preflight request 할 때에 사용됩니다.Access-Control-Request-Method: <method>
Access-Control-Request-Headers:
예비 요청을 보낼 때 포함되어, 본 요청에서 어떤 HTTP Header를 사용할 지 서버에게 알려줍니다.해당 사전 요청들에 대해 서버 역시 응답을 해줍니다.
프로그래머가
Access-Control-
계열의 Response Header만 적절히 정해주면,
OPTIONS 요청으로 오는 예비 요청과 GET, POST, HEAD, PUT, DELETE 등으로 오는 본 요청의 처리는 서버가 알아서 처리합니다.
HTTP/2.0 200 OK
Access-Control-Allow-Headers: X-PINGARUNER, CONTENT-TYPE
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Max-Age: 1728000
Cache-Control: max-age=172800
Connection: Upgrade, Keep-Alive
Content-Length: 0
Content-Type: text/plain;charset=UTF-8
Date: Mon, 07 Mar 2022 07:15:06 GMT
Expires: Wed, 09 Mar 2022 07:15:06 GMT
Keep-Alive: timeout=2, max=100
Server: Apache
Upgrade: h2
Vary: User-Agent
Access-Control-Allow-Origin:
해당 출처를 허용하겠다는 헤더입니다.Access-Control-Allow-Headers
: 실제 요청에서 사용가능한 헤더를 알려줍니다Access-Control-Allow-Methods
: 실제 요청에서 사용가능한 메소드를 알려줍니다Access-Control-Max-Age
: 예비 요청의 수명을 알려줍니다. 해당 수명 기간동안 예비요청은 캐싱되므로 따로 예비요청을 보내주지 않아도 됩니다.조금 생각해보면 예비요청과 본요청을 두번 보낸다는건 통신이 두번 일어난다는 것이고. 이는 서버에 부담이 될수 있습니다.
허나 이 이유는 CORS를 모르는 서버를 위함입니다.
만일 CORS를 모르는 서버가 있다고 가정하고
클라이언트는 브라우저를 통해 특정 메서드로 요청을 보냅니다. 그리고 서버는 CORS
를 모르기 때문에 일단 요청을 처리하고 응답하게 되는데 당연히 응답해야 할 Access-Control-Allow-Origin
이 없을 것입니다.
브라우저는 서버는 Access-Control-Allow-Origin
**이 없기 때문에** cross-origin
**에러를 발생**시킵니다.
하지만 이미 특정 요청을 처리하였기 때문에 이는 문제가 됩니다.
따라서 preflight는 이러한 사태를 미연에 방지하고자, 사전요청을 통해 요청의 유효함을 판단하고, 그제서야 서버에 본요청을 날리는 것입니다.
마지막으로 인증 정보 및 쿠키를 전송하는 지에 대한 여부에 따라,
즉 CORS는 인증 정보를 포함한 요청으로도 나뉩니다.
쿠키는 요청을 할 때에 자동으로 요청에 포함되지만, CORS 요청에서는 아닙니다.
일반적으로 브라우저는 쿠키와 인증 정보에 관해 민감하기 때문에 CORS선 몇가지 설정을 해주어야합니다
일단 쿠키 헤더의 Same-Site 속성에 대해 알아보겠습니다
Same-Site 속성은 다른 도메인 간의 통신에 대한 보안에 대한 설정이고, 3가지 설정값이 있습니다.
Top-level-Navigation : 해당 탭의 URL이 바뀌는 요청에 Lax 쿠키가 포함됩니다. 이를태면 a태그와 link태그를 클릭했을 때 발생하는 GET 요청에 쿠키가 포함되고, 현재 탭의 URL을 바꾸진 않으나 Form의 GET 요청에도 Lax 쿠키가 포함됩니다.
Secure 속성이 활성화되면 HTTPS 프로토콜 간의 통신에만 쿠키가 포함될 수 있습니다. Same-Site를 None으로 면시 시에는 최소한의 보안 수단으로 Secure 속성을 활성화 해달라는 뜻입니다
다음과 같은 조건이 필요합니다
Access-Control-Allow-Credentials
헤더 활성화Access-Control-Allow-Origin
헤더에 도메인 명시, 와일드 카드 사용 불가)credentials
옵션을 설정해야합니다.예를 들자면 Express에선 다음과 같이 작성할수 있습니다.
//SERVER.js
const express = require("express");
const server = express();
const cors = require("cors");
...
server
.use(cors({ origin: true, credentials: true }))
.use(express.json())
.use(cookieParser());
...
origin:true
를 작성합니다.Access-Control-Allow-Credentials
가 true
인 응답은 반드시 Access-Control-Allow-Origin
이 명시되어 있어야 하기 때문입니다.origin:true
는 origin 값이 Access-Control-Allow-Origin
로 설정되기 때문에 와일드 카드로 설정되지 않습니다.......ROUTER
const requestRefresh = async () => {
const response = await axios.get(
"쿠키가 필요한/API/EndPoint",
{
withCredentials: true
}
);
console.log(response);
};
.....
클라이언트에서 외부서버로 바로 요청하는것이 아니라 프록시 서버를 사용하여 우회하게 한다면 CORS를 해결할수 있습니다.
클라이언트가 프록시 서버를 통해 다른 네트워크 서비스에 간접적으로 접근시 중간에서 요청을 프록시서버가 가로채서 Access-Control-Allow-Origin
에 자동으로 설정해 줄 수 있습니다
예를들어 직접 구현한 프록시서버가 아닌 다른 프록시서버를 사용할 경우
요청해야할 URL 앞에 프록시 서버 URL을 붙일 수 있습니다
const url_sample = "https://cors-anywhere.herokuapp.com/https://foo.com")
다만 중간에 프록시 서버를 거치기 때문에 속도가 느려질수 있습니다
서버에 Access-Control-Allow-Origin 헤더를 세팅해 주는것이 기본적인 해결방안입니다.
import * as express from 'express';
const app() = express();
app.get(('/boardList', (req:express.Request, res:express.Response) => {
res.header('Access-Control-Allow-Origin', '허용하고자 하는 도메인');
res.send(data)
})
Nodejs Express같은 경우에는 CORS 미들웨어를 사용하면 편하게 CORS를 설정할 수 있습니다.
const http = require('http');
const express = require('express');
const app = express();
const server = createServer(app);
const cors = require('cors');
const PORT = 8080;
app.use(cors(corsOptions));
app.get('/', (req, res) => {
res.send('Hello, World!');
});
server.listen(PORT, () => {
console.log(`Server running on ${PORT}`);
});
......
///
const corsOptions = {
origin: function (origin, callback) {
// db.loadOrigins is an example call to load
// a list of origins from a backing database
db.loadOrigins(function (error, origins) {
callback(error, origins)
})
}
}
글이 너무 길어요!
그래서 오히려 좋아요!