HTTP

옥영진·2020년 12월 23일
0

HTTP(HyperText Transfer Protocol)는 서버와 클라이언트가 서로 데이터를 주고받을 때 사용하는 프로토콜이다. 오늘날 웹을 구성하는 프로토콜이며, HTML, CSS, 자바스크립트, JSON, XML 등의 텍스트 기반 데이터와 이미지, 동영상, 파일과 같은 바이너리 데이터도 주고받을 수 있다. 바이너리 데이터는 HTTP 멀티파트나 Base64로 인코딩하여 사용한다.
HTTP는 여러 다른 기술과 함께 사용되는데, JSON 등을 함께 사용하는 Restful API가 있고, JSON이 범용적으로 사용되기 전에는 XML과 함께 사용하는 SOAP도 있었다. 또한 데이터를 안전하게 주고받기 위해 HTTP에 전송 계층 보안(Transport Layer Security, TLS)을 더해 만든 HTTPS 프로토콜을 사용한다.

Stateless

HTTP는 요청 메시지를 보내기 직전까지 대상 컴퓨터와 통신이 가능한 상태인지 알 수 있는 방법이 없다. 따라서 HTTP를 상태가 없는(Stateless) 프로토콜이라고 한다. 반대로 상태가 있는(Stateful) 프로토콜에는 TCP(Transmission Control Protocol) 프로토콜이 있다.
HTTP 메시지 통신 과정을 살펴보면 HTTP 메시지는 요청과 응답이 일대일로 대응되어야 하므로 요청을 받은 서버는 반드시 응답을 보내야만 한다. 그래서 클라이언트는 자신이 보낸 요청의 실패여부를 알 수 있어 로직이 단순해지는 장점이 있다. 하지만 클라이언트는 서버로 HTTP 요청을 보내기 직전까지 실제로 서버가 동작하는지 알 수 있는 방법이 없고, 서버가 요청을 받더라도 시간 안에 응답을 보내지 못할 수 있기 때문에 클라이언트에서 각 상황에 대해 어떻게 처리할 것인지 결정해야 한다. 클라이언트는 어떤 이유로든 서버로부터 일정 시간 안에 HTTP 응답을 받지 못하면 타임아웃으로 간주하고 실패한 것으로 처리한다.
TCP 통신 과정은 HTTP와 같이 연결을 끊지 않고 명시적으로 연결을 닫기 전까지 계속 메시지를 주고받는다. 한쪽에 문제가 생기거나 주고받을 메시지가 없기 전까지는 연결을 유지하므로 연결의 맺고 끊음을 클라이언트와 서버 모두가 알 수 있다.
정리하면, TCP처럼 서버와 클라이언트 간 세션의 상태에 기반하여 클라이언트에 응답을 보내기 위해 세션 상태를 포함한 클라이언트와의 세션 정보를 서버에 저장하는 방식이 Stateful 프로토콜이고, HTTP처럼 서버와 클라이언트 간 세션 상태가 서로 독립적이여서 단순히 클라이언트에서 보내는 요청을 서버에서는 응답을 보내는 역할만 수행하여 세션 정보를 서버에 저장하지 않는 구조를 Stateless 프로토콜이라고 한다.

HTTP 요청

HTTP 요청 메시지 구조를 살펴보기 위해 Burp Suite를 사용하여 google에 접속하는 요청 패킷을 확인해보았다.

첫 번째 줄을 요청 줄이라고 부른다. 요청 줄은 다음과 같은 구조를 가져야 하며 순서대로 명시해야 한다.

<요청 메서드> <URL 경로> <HTTP 버전>

두 번째 줄부터 나열되는 Host, Connection, Cache-Control 등을 HTTP 헤더라고 하며, 각종 정보에 대한 값들이 나열되며 순서는 바뀔 수 있다.
위 캡처 화면에 있는 요청 메시지는 GET 메서드이므로 바디가 존재하지 않지만, 헤더 다음에는 메시지 바디가 존재한다. 헤더와 바디를 구분하기 위해 사이에 구분자 역할을 하는 공백라인이 있고 그 다음에 메시지 바디 데이터가 위치한다.

정리하면, HTTP 요청 메시지는 크게 요청 라인, 헤더, 바디로 구성된다. 그림으로 나타내면 아래와 같다.

GET 메서드는 서버에 웹 페이지를 요청할 때 사용한다. GET 메서드로 서버에 요청하여 서버로부터 웹 페이지를 구성하는 HTML, CSS, 자바스크립트를 가져온 다음 브라우저를 통해 보여주는 것이다. 위 예제 패킷에 대해 설명했듯이 GET 메서드는 읽기 요청이므로 메시지 바디를 넣을 수 없다. 대신 요청 URL에 ?, & 문자를 구분자로 하여 쿼리 파라미터를 추가할 수 있다. ? 문자는 첫 번째 파라미터를 구분할 때, & 문자는 파라미터 사이를 구분할 때 사용한다.

네이버에 abc 라는 문장열을 검색했을 때 보내는 요청 URL 이다. ? 다음에 쿼리 파라미터를 나열하며, & 문자로 파라미터들을 구분하고 있음을 알 수 있다.

요청 메서드

요청 메서드는 요청의 형태를 정의하며, 엄격히 지켜야 하는 건 아니지만 상황에 맞게 사용하는 것이 관례이다.

GET

GET 메서드는 서버에 웹 페이지를 요청할 때 사용한다. GET 메서드로 서버에 요청하여 서버로부터 웹 페이지를 구성하는 HTML, CSS, 자바스크립트를 가져온 다음 브라우저를 통해 보여주는 것이다. 위 예제 패킷에 대해 설명했듯이 GET 메서드는 읽기 요청이므로 메시지 바디를 넣을 수 없다. 대신 요청 URL에 ?, & 문자를 구분자로 하여 쿼리 파라미터를 추가할 수 있다.
? 문자는 첫 번째 파라미터를 구분할 때, & 문자는 파라미터 사이를 구분할 때 사용한다.

POST

POST 메서드는 데이터가 포함된 요청을 보낼 때 사용한다. 예를 들어, 로그인을 할 때 아이디와 패스워드를 서버에 보내거나, 쇼핑몰에서 상품 정보를 가지고 주문을 요청할 때 등의 과정에서 POST 메서드를 사용한다.

DELETE, PUT

DELETE와 PUT 메서드는 POST 메서드와 기술적으로 큰 차이는 없으나, 각각 데이터 삭제, 데이터 업데이트를 의미한다. 두 메서드는 웹 페이지 요청보다는 RESTful API에서 많이 사용된다.

URL

URL(Uniform Resource Locator)은 웹 주소라고 하며 HTTP에서 통신할 대상 컴퓨터를 식별할 때 사용한다. 이는 사람이 기억하기 쉽게 만든 식별자에 불과하고 실제로 컴퓨터가 통신할 때는 IP 주소를 사용하기 때문에 통신을 위해서는 URL을 IP 주소로 변환하는 작업이 필요하다. 이 때 IP 주소로 변환하는 작업을 DNS(Domain Namer System)에서 할 수 있다. 하나의 URL이 여러 IP 주소를 가질 수도 있는데, DNS는 클라이언트가 IP 주소를 요청할 때마다 여러 IP 주소 중 하나를 보내주게 된다.
URL은 아래와 같이 주소와 경로로 나눠 표기할 수 있다.

http://www.example.com/<경로>/<경로2>/<파일>

HTTP 버전

HTTP 버전은 HTTP 표준을 정의하는 버전으로, 버전에 따라 사용할 수 있는 기능이나 통신 방법이 약간 다르다. 가장 많이 사용하는 버전은 1.0, 1.1, 2.0이다.1.1 버전에서는 1.0 버전보다 효율적인 연결을 위해 소켓 재사용을 요청하는 keep-alive 헤더와 언어 및 인코딩 지원을 위한 헤더가 추가되었다. 1.1 버전은 하나의 요청에 대해 하나의 응답만 보낼 수 있다. 따라서 웹 페이지 하나를 조회할 때 너무 많은 여러 요청들이 필요할 때가 있어, 이러한 문제를 해결하기 위해 한 번의 요청에 수십 개의 응답을 병렬로 보낼 수 있도록 개선하고 불필요한 오버헤드를 제거한 것이 2.0 버전이다.

요청 헤더

Host

Host 헤더는 URL 경로를 제외한 주소가 저장되는 헤더이다. 포트 번호도 함께 넣을 수 있지만 없으면 기본 포트 번호인 80(HTTP), 443(HTTPS)를 사용한다.

Accept

Accept 헤더는 클라이언트가 처리할 수 있는 데이터 형식을 알려주는 헤더이다. 이 외에 Accept가 붙은 헤더로 Accept-Encoding, Accept-Language, Accept-Charset 등이 있다.

User-Agent

User-Agent 헤더는 HTTP 요청을 발생시킨 웹 브라우저 프로그램 정보를 담은 헤더이다. 브라우저마다 지원하는 표준이 다르기 때문에 웹 서비스 개발 시, 특정 웹 브라우저를 기준으로 개발하고 문제가 발생하면 기준이 되는 브라우저에서 발생한 에러인지 확인하기 위해 User-Agent 헤더 값을 확인한다.

Content-Type

Content-Type 헤더는 메시지 바디에 있는 데이터의 형식을 알려주는 헤더이다. 이는 요청 헤더 뿐만 아니라 응답 헤더에서도 사용한다. 웹 서비스 개발 시 사용자 요청에 대한 응답을 보낼 때는 반드시 Content-Type 헤더 값을 정확하게 명시해야 한다. 이 헤더의 값에 따라 브라우저의 동작이 바뀔 수 있기 때문이다.

Content-Length

HTTP 헤더를 제외한 바디 데이터의 길이를 담는 헤더이다. 이 값이 실제 메시지 길이와 다를 경우 응답을 보내는 서버가 응답 메시지를 모두 보내기 전에 알 수 없는 예외로 종료했을 가능성이 있다.

Connetction:keep-alive

HTTP 1.1 버전부터 지원하는 헤더 정보로, keep-alive가 설정된 경우, 요청을 받은 서버는 응답을 보낸 후 타임아웃 시간 전까지 소켓 연결을 끊지 않는다. 이시간이 지나기 전에 동일한 클라이언트에서 다시 요청이 오면 새 세션을 만들지 않고 기존 소켓을 통해 통신한다.

메시지 바디

메시지 바디에는 ID, 비밀번호와 같은 단순 문자열을 비롯하여 이미지, 동영상, 파일과 같은 바이너리 파일 데이터를 담는다. 예외적으로 GET, OPTIONS 메서드는 서버의 상태를 변경하지 않고 데이터를 요청할 때만 사용하는 메서드이기 때문에 메시지 바디를 사용할 수 없다. 이 메서드들은 서버로 보내야할 데이터가 있을 경우 URL 뒤에 쿼리 파라미터를 사용하여 데이터를 보낼 수 있다.

HTTP 응답

HTTP 응답 메시지의 구조를 확인하기 위해 이전에 구글 접속 시 보냈던 요청의 응답 패킷을 확인해보자.

요청 메시지와 비슷하게 첫 번째 줄은 응답 라인, 두 번째 줄부터 공백 라인 전까지 응답 헤더, 공백 라인 다음에 있는 HTML 소스 부분이 메시지 바디 부분이다.

상태 코드와 메시지

클라이언트에서 요청을 보낸 후 정상적으로 처리되었는지 확인하기 위해 서버에서는 HTTP 응답 메시지 첫 번째 응답라인에 상태 코드와 상태 메시지를 담아서 보낸다. 상태코드는 세 자리로, 첫 번째 자릿수로 성공인지 실패인지 알 수 있고, 나머지 두 개의 숫자로 세부적인 내용을 확인할 수 있다.
예를 들어, 2xx 코드는 성공을 의미하고, 3xx는 서버 이동, 4xx는 클라이언트 쪽 오류, 5xx는 서버 쪽 오류로 볼 수 있다.

세션과 쿠키

가장 처음에 언급했다시피, HTTP는 상태가 없는(Stateless) 프로토콜이다. 서버로 HTTP 요청을 보내면 각 요청이 새로운 클라이언트로부터 온 것인지, 기존에 연결을 맺었던 클라이언트로부터 온 것인지 알 수 없다. 모든 요청은 독립적으로 소켓 1개를 사용하고, 다른 요청과 독립적이기 때문이다.
이러한 문제를 해결하기 위해 HTTP 웹 서버는 쿠키와 세션 ID를 사용해 클라이언트를 구분한다. 쿠키를 사용하면 새 요청인지, 이전에 보낸 요청이 있었는지 구분할 수 있다.

쿠키 안전하게 관리하기

  • 쿠키는 해커들이 자주 공격하는 곳이다. 쿠키를 이용한 공격에는 세션 가로채기, XSS나 CSRF와 같은 스크립트 삽입 등이 있다. 이러한 해킹 공격을 방지하기 위해 쿠키를 보호할 수 있는 HTTPS를 필수적으로 사용해야 하고, HTTP로 접속해도 HTTPS로 다시 접속하도록 처리하는 것이 좋다.

  • 클라이언트는 언제든지 쿠키값을 마음대로 생성하거나 변조할 수 있다. 때문에 서버에서는 쿠키가 언제든지 변조될 수 있다는 사실을 염두하여 관련 프로세스를 개발해야 한다.

  • 쿠키 설정 시, Secure 속성을 추가하면 HTTPS로 통신할 때만 쿠키를 서버로 전송한다. 그리고 HttpOnly 속성을 추가하면 웹 브라우저에서 자바스크립트를 통해 쿠키에 접근하는 것을 방지할 수 있다.

스티키 세션

웹 서비스에 사용자가 많아지면 그만큼 서버에 부하가 커지게 된다. 이 때 서버 관리자는 서버의 하드웨어를 업그레이드 하는 대신 더 많은 서버를 추가해 확장할 수도 있다. 이를 수평적 확장이라고 하는데, 이러한 형태로 서비스를 운용하기 위해서는 로드밸런스 서비스가 있어야 한다. 로드밸런스 서비스는 사용자가 접속했을 때 부하가 가장 적은 웹 서버로 연결해주고 동작하지 않는 서버를 발견하면 서버 목록에서 자동으로 제외시켜준다.
하지만 여기서 한 가지 문제가 발생하는데, 사용자가 웹 페이지를 요청할 때마다 실제로 접속하게 되는 서버가 바뀌게 되면 쿠키나 세션 정보가 사라질 수도 있다. 그렇다고 웹 서버 전체가 데이터를 수시로 동기화한다면 그만큼 또다른 부하가 발생하기 때문에 서버를 여러 대로 확장할 이유가 사라진다.
스티키 세션 은 이러한 문제를 해결하기 위한 것으로, 이 기능을 활성화하면 하나의 브라우저는 하나의 웹 서버에만 연결하게 된다. 로드밸런스는 쿠키가 없는 첫 요청이 들어올 때 쿠키에 값을 등록하고 웹 서버를 지정한다. 이후 요청이 올 때 등록된 값을 기준으로 연결할 웹 서버를 구분한다.

CORS

CORS(Cross-Origin Resource Sharing)는 HTTP 서버의 웹 페이지, 이미지 파일이나 API 등을 특정 호스트로 접속한 웹 브라우저만 사용할 수 있게 제한하는 정책이다.
오늘날 웹 서비스는 수많은 서브 도메인으로 구성되어 있는데, 웹 서버(프론트엔드)와 API 서버(백엔드)를 구분할 때 사용하기도 하고 제공하는 기능을 단위로 웹 서비스를 나눌 때도 사용한다.
네이버로 예를 들면, 블로그와 카페의 도메인은 각각 section.blog.naver.com, section.cafe.naver.com 을 사용한다. 이 때 네이버 블로그에서 자바스크립트를 이용해 카페 주소로 이미지 또는 API 요청을 보내면 동일 출처 정책(Same Origin Policy)대문에 네이버 블로그는 자신의 도메인을 제외한 다른 서브 도메인으로 HTTP 요청을 할 수 없다.
이 때 CORS 정책을 설정하면 다른 서브 도메인으로 리소스를 요청할 수 있게 된다.

profile
안녕하세요 함께 공부합시다

0개의 댓글