1. HTTP/1.0의 syntax : 기본이 되는 네 가지 요소
HTTP의 4가지 기본요소
•
매서드와 경로
•
헤더
•
바디
•
스테이터스 코드
1.1 HTTP의 역사
HTTP 프로토콜 관련 고유명사
•
IETF
◦
The Internet Engineering Task Force
◦
인터넷의 상호 접속성을 향상시키는 것을 목적으로한 단체
•
RFC
◦
Request For Comments
◦
IETF가 만든 규약 문서
◦
RFC+숫자로 표기
•
IANA
◦
Internet Assigned Numbers Authority
◦
포트 번호와 파일 타입(Content-type)등 웹에 관한 DB를 관리하는 단체
•
W3C
◦
Word Wide Web Consortium
◦
웹 관련 표준화를 하는 비영리 단체
◦
통신 규격이 아닌 브라우저 특화 기능은 IETF에서 이관
◦
HTML, SSE, Web Socket…
•
WHATWG
◦
Web Hypertext Applicatiopn Technology Working Group
◦
웹 관련 규격을 논의하는 단체
1.2 HTTP/0.9로 할 수 있는 것을 시험하다
0.9의 기능은
•
웹사이트의 페이지를 서버에 요청하고, 응답으로 웹사이트의 내용을 받아오기
•
폼, 검색 기능
◦
검색기능은 <isindex> 태그를 붙이면 ? 쿼리문을 생성해주는 기능으로 현재 사라짐
◦
비슷한 명령어는 —get과 —data-urlencode를 붙이면 됩니다.
◦
curl --http1.0 --get --data-urlencode "search world" http://localhost:18888
0.9 프로토콜은 실제로 다룰 수 없으므로 1.0으로 기본 기능을 구현했을 때 아래와 같습니다.
# curl
$ curl --http1.0 http://localhost:18888
# server console
start http listening : 18888
Received request:
GET / HTTP/1.0
Host: localhost:18888
Connection: undefined
Accept: */*
User-Agent: curl/7.86.0
Request body:
Shell
복사
// server.js
const http = require('http');
const port = 18888;
function handler(req, res) {
let body = [];
req.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
console.log(`Received request:\n${req.method} ${req.url} HTTP/${req.httpVersion}`);
console.log(`Host: ${req.headers.host}`);
// 이건 안찍히네요!
console.log(`Connection: ${req.headers.connection}`);
console.log(`Accept: ${req.headers.accept}`);
console.log(`User-Agent: ${req.headers['user-agent']}`);
console.log(`Request body: ${body}`);
console.log('----------------------------------------');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html><body>hello</body></html>\n');
});
}
const server = http.createServer(handler);
server.listen(port, () => {
console.log(`start http listening : ${port}`);
});
JavaScript
복사
1.3 HTTP/0.9에서 1.0으로 여정
0.9에서 요청을하면 데이터를 보내준다가 정의되었지만 기능이 제한되었습니다.
•
하나의 문서만 전송
•
HTML만을 가정해 다운로드 콘텐츠 형식 지정 불가
•
클라이언트가 검색이외 요청 불가
•
새로운 문장 전송 및 갱신 불가
•
요청이 올바른지, 서버가 정상적으로 전송했는지 확인 불가
1.0을 실제로 전송해보겠습니다.
% curl -v --http1.0 http://localhost:18888/greeting
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> GET /greeting HTTP/1.0
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Mon, 01 Apr 2024 09:43:14 GMT
< Connection: close
<
<html><body>hello</body></html>
* Closing connection
Shell
복사
HTTP/0.9에서 1.0으로 넘어오며 요청과 응답 모두에서 변경점이 있습니다.
요청
•
요청 메서드 및 HTTP 버전 추가
> GET /greeting HTTP/1.0
Shell
복사
•
헤더 추가
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
Shell
복사
응답
•
HTTP 버전과 상태값 추가
HTTP/1.1 200 OK
Shell
복사
•
요청과 같은 형식의 헤더 추가
< Content-Type: text/html
< Date: Mon, 01 Apr 2024 09:43:14 GMT
< Connection: close
Shell
복사
1.4 HTTP의 조상 (1) 전자메일
헤더는 요청과 응답 모두에서 사용되는데, 이는 메일 시스템에서 온 것입니다.
G-Mail의 메일 원문 보기를 통해 보면 파일명:값 의 형식으로 헤더들이 담긴 것을 알 수 있습니다.
X-Gm-Message-State: ...
MIME-Version: 1.0
X-Received: by ...:...:... with SMTP id ...; Sun, 31 Mar 2024 18:44:41 -0700 (PDT)
Date: Sun, 31 Mar 2024 18:44:41 -0700
Reply-To: Google Tag Manager <tagmanager-noreply@google.com>
X-Google-Id: ...
Feedback-ID: ...
X-Notifications: ...
X-Notifications-Bounce-Info: ...
Message-ID: <...@google.com>
Subject: Getting the most from Google Tag Manager
From: Google Tag Manager <tagmanager-noreply@google.com>
To: ...@gmail.com
Content-Type: multipart/alternative; boundary="..."
Shell
복사
HTTP에도 똑같은 형식 헤더가 도입되었습니다.
클라이언트 → 서버
•
User-Agent: 클라이언트의 애플리케이션을 나타내고, curl을 사용시 curl/7.4.0와 같이나오고, 브라우저 버전 정보등이 들어갑니다.
•
Referrer: 요청 시 브라우저의 URL등을 보냅니다.
•
Authorization: 인증 정보를 서버에 전달합니다.
서버 → 클라이언트
•
Content-Type: 파일의 종류
◦
MIME 타입: 전자메일을 위한 식별자
•
Content-Length: 바디의 크기로 압축 시 압축된 크기
•
Content-Encoding: 압축의 형식
•
Date: 문서 날짜
X- 로 이루어진 헤더는 자유롭게 사용가능한 헤더입니다.
1.4.1 헤더의 전송
헤더를 추가로 전송할 시 가장 마지막에 전송되며, RFC에선 헤더의 중복을 허용하고 있습니다.
# 헤더 추가
$ curl --http1.0 -H "X-Test: Hello" http://localhost:18888 -v
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> GET / HTTP/1.0
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
> X-Test: Hello
# Agent 추가
$ curl -v --http1.0 -H "User-Agent: Mozilla/5.0" http://localhost:18888
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> GET / HTTP/1.0
> Host: localhost:18888
> Accept: */*
> User-Agent: Mozilla/5.0
Shell
복사
1.4.2 헤더 수신
클라이언트가 전송하는 헤더와 서버에서 반환하는 헤더의 형식은 완전히 같지만, 요청이나 응답에서만 사용되는 필드들도 존재합니다.
$ curl -v --http1.0 -H "User-Agent: Mozilla/5.0" http://localhost:18888
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Mon, 01 Apr 2024 10:13:07 GMT
< Connection: close
Shell
복사
Content-Type은 서버로부터 받아온 파일 형식을 나타내며, MIME 타입으로 불리는 값들이 있는데 이는 메일에서 왔습니다.
1.4.3 MIME 타입
•
RFC 1049: Content-Type 필드 등장
•
RFC 1341: MIME 타입 등장, Content-Type의 형식 지정
◦
대항목/상세
◦
ex.text/html
•
RFC 1590: 새로운 종류의 MIME 타입을 LANE에 신청하는 절차 등장
◦
ex.JSON
•
RFC 3023: 접미사 추가
◦
ex.application/xml → image/svg+xml
1.4.4 Content-Type과 보안
과거엔 CGI를 사용한 접속카운터(이미지를 생성하는 스크립트 언어)를 사용했습니다. 하지만 이는 확장자를 제대로 알 수 없었습니다. IE의 경우 Content-Type이 아닌 내용을 보고 형식을 추측했는데, 이는 의도치 않은 파일 실행으로 보안상 문제로 이어질 수 있었습니다.
1.4.5 전자메일과의 차이
HTTP 통신은 메일이 고속으로 왔다갔다 한다고 볼 수 있으며, 세세한 관계는 다음과 같습니다.
•
헤더 + 본문 구조는 동일
•
HTTP 요청은 선두에 메서드 + 패스 행 추가
•
HTTP 응답은 선두에 스테이터스 코드 추가
•
메일의 경우 긴헤더가 있을 시 줄바꿈이 허용되지만, HTTP에서는 보안상 취약점으로 인해 RFC 7230에서부터 권장하지 않습니다.
1.5 HTTP의 조상 (2) 뉴스그룹
과거 뉴스그룹이란 플랫폼이 있었는데, 분산아키텍터 + 복수의 서버가 마스터/슬레이브 구조로 되어있었습니다. 여기서 클라이언트 서버간 통신과 마스터 서버와 슬레이브 서버간 통신에 사용된 것이 NNTP(Network News Transfer Protocol) 프로토콜입니다. 이는 RFC 977에 정의되었으며, 이또한 전자메일의 영향을 받았습니다.
HTTP는 이곳에서 메서드와 스테이터스 코드를 채용했습니다.
마스터 슬레이브 구조: 마스터가 하나이상의 장치나 프로세스(슬레이브)를 통제하고, 통신 허브 역할을 하는 통신 및 제어 모델
NNTP: 뉴스 서버간 유즈넷 기사를 전송하고 읽도록하기 위한 프로토콜
1.5.1 메서드
HTTP에선 파일 시스템과 같은 설계 철학으로 만들어졌습니다.
GET, HEAD, POST: 흔히 사용되는 메서드입니다.
PUT, POST: 1.0 이후에도 계속 남아있으나, 브라우저 표준기능만으로 사용할 수 있게 된것은 JS의 XMLHttpReqeust가 지원된 이후입니다.
LINK, UNLINK: 1.0에서는 있었으나, 1.1에서 삭제되었습니다.
CHECKOUT, CHECK1N, SHOWMETHOD, TEXTSEARCH, SEARCHJUMP, SEARCH: 1.0에서 삭제되었습니다.
# curl 커맨드로 사용시 —request=Method 나 -X Method를 사용합니다.
$ curl --http1.0 -X POST http://localhost:18888/greeting -v
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> POST /greeting HTTP/1.0
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Mon, 01 Apr 2024 10:52:47 GMT
< Connection: close
# -X HEAD의 단축형으로 -head, -I 가 있습니다.
$ curl --http1.0 -I http://localhost:18888/greeting -v
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> HEAD /greeting HTTP/1.0
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: text/html
Content-Type: text/html
< Date: Mon, 01 Apr 2024 10:54:32 GMT
Date: Mon, 01 Apr 2024 10:54:32 GMT
< Connection: close
Connection: close
<
* Closing connection
Shell
복사
1.5.2 Status code
3자리 숫자를 보고 상태를 확인할 수 있으며, 5가지 종류가 있습니다.
•
100번대: 처리가 계속됨으로 특수한 경우로 한정
•
200번대: 성공
•
300번대: 서버에서 클라이언트로 명령, 오류가 아니라 정상처리를 나타내고, 주로 redirect나 캐시 이용에 나타냅니다.
•
400번대: 클라이언트가 보낸 요청에 오류
•
500번대: 서버내부에 오류
1.6 Redirect
300번 일부는 서버에서 클라이언트에게 리다이렉트를 지시하는데, 300이외의 경우 Location을 통해 갈곳을 지정해줍니다.
Status Code | Method Change | Permanent/Temporary | Cache | Description | |
301 Moved Permanently | o | P | o | 도메인 전송, 웹사이트 이전, HTTPS | 요청된 페이지를 이동으로 301 사용을 권장 |
302 Found | o | T | 지시에 따름 | 일시적 관리, 모바일 기반 | 일시적 이동으로 모바일 페이지등으로 이동 |
303 See Other | 허가 | P | x | 로그인 후 페이지 전환 | 요청된 페이지가 없거나 요청 페이지가 따로 존재할 시 |
307 Temporary Redirect | T | 지시에 따름 | RFC 7231에서 추가 | 일시적 이동으로 모바일 페이지등으로 이동 | |
308 Moved Permanently | P | o | RFC 7538에서 추가 | 요청된 페이지를 이동으로 301 사용을 권장 |
$ curl -L http://localhost:18888 -v
* Trying [::1]:18888...
* Connected to localhost (::1) port 18888
> GET / HTTP/1.1
> Host: localhost:18888
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Mon, 01 Apr 2024 11:09:21 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
<html><body>hello</body></html>
* Connection #0 to host localhost left intact
Shell
복사
HTTP/1.0에서 1.1로 넘어가며 여러 변화가 있었습니다.
•
302의 경우 RFC 2616에선 메서드 변경의 경우 허가가 필요했지만 대부분 Agent는 리다이렉트시 GET으로 한다 라 명시되어있던 것이, 1.1의 RFC 7231에서 301처럼 변경이 허용되었습니다.
•
1.0에서 최초엔 Redirect의 횟수는 5회를 기준으로 했지만, RFC 2616에선 제한이 없어지고 클라이언트에서 이름 감지해야됨을 명시했습니다.
Redirect를 하면 통신의 시간이 길어지기 때문에 Google의 경우 5회이하로 제한하고, 가능하면 3회이하로 할것을 제시합니다.
1.7 URL
URL(Uniform Resource Locator)는 RFC 1738에서, 상대 URL은 RFC 1808에서 정의 되었습니다. 이는 HTTP/1.0보다 빠르게 제시되었으며, HTTP/0.9를 기반으로 발전했습니다. URL은 문서등의 리소스를 특정하는 수단을 제공하는 주소와 같은 것입니다. 이와 혼동하기 쉬운 URI에는 URN이란 이름부여 규칙도 포함되어 있으며, 다음은 RFC 1738을 나타내는 URN입니다. urn:ietf:rfc:1738
제 자동차를를 지정한다 했을 URN은 urn:동우:Avente로 나타낼 수 있습니다. 이때 URL은 성남://야탑역/분당아람고앞 으로 표현할 수 있습니다. 만약 자동차를 회사로 이동시킨다면 URL로는 자동차를 찾을 수 없습니다. 또한 URN은 이름밖에 없어 추가적인 정보가 필요합니다.
웹시스템을 사용할 때 URN은 거의 볼일이 없어 URL과 URI는 거의 같습니다. RFC 3305에서 URL은 관용 표현으로, URI는 공식 표기로 되었지만 URL이 더 많이 사용되는 추세입니다.
1.7.1 URL의 구조
스키마://사용자:패스워드@호스트명:포트/경로#프래그먼트?쿼리
Shell
복사
•
스키마
◦
http, https, mailto, file, ftp
◦
스키마의 해석은 브라우저의 책임
•
호스트명
◦
지정된 서버로, 65535개의 포트가 존재
◦
HTTP라면 80, HTTPS라면 443이 기본 포트
•
사용자:패스워드
◦
FTP등에서 사용되었으나 보안에 취약
•
프래그먼트
◦
HTML 페이지의 링크내 앵커 지정
•
쿼리
◦
검색용어, 파라미터
기호적인 URL보다는 사람이 읽을 수 있는 URL이 더 많은 정보를 전달할 수 있으며, RFC 2738에서 utf-8로 URL로 인코딩해 다국어를 지원해주게 되었습니다. HTTP사양상 길이제한은 없으나, 브라우저 자체적으로 길이제한을 두고 있습니다.
1.7.2 URL과 국제화
RFC 3492에서 국제 도메인 네임(IDN)을 표현하는 퓨니코드가 정해져 다국어를 사용할 수 있게되었습니다. 이는 다국어가 utf-8등 문자가 지원되는 것이 아닌, 각 문자가 영어로 치환되며 반드시 xn—으로 시작합니다.
예를 들어 한글도메인.kr는 xn--bjObj3i97fq8051q.kr로 치환됩니다.
1.8 바디
0.9에선 요청엔 바디를 넣을 수 없었고, 응답은 자체가 콘텐츠 파일이였습니다. 하지만 1.0에서부턴 바디와 헤더를 분리해야만 했습니다.
1.0에서부터 헤더 끝에 빈줄을 넣게되면 바디로 인식됩니다. 이 구조는 전자메일과 같지만, 전송 시 데이터 저장 포멧이 2종류로 용도에 맞게 구분해야합니다.
Content-Length
헤더1: 헤더 값1
헤더2: 헤더 값2
Content-Length: 바디의 바이트 수
여기부터 바이트 수 만큼 바디가 포함
압축 시 압축된 값 표시
JavaScript
복사
curl 바디
•
-d, —data, —data-ascii: 텍스트 데이터(변환 완료)
◦
url에 사용할 수 없는 문자라도 변환하지 않습니다.
•
-0-data-urlendcode: 텍스트 데이터(변환은 curl 커맨드로 실시)
•
—data-binary: 바이너리 데이터
•
-T 파일명, -d @파일명: 보내고 싶은 데이터를 파일에서 읽어옴
바디 넣어서 보내는 예시입니다.
$ curl -d "{\"hello\": \"world\"}" -H "Content-Type: application/json" http://localhost:18888 -v
* Trying 127.0.0.1:18888...
* Connected to localhost (127.0.0.1) port 18888 (#0)
> POST / HTTP/1.1
> Host: localhost:18888
> User-Agent: curl/7.86.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 18
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Tue, 02 Apr 2024 02:36:30 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
<html><body>hello</body></html>
* Connection #0 to host localhost left intact
Shell
복사
# server
> real-world-http@1.0.0 start
> node server.js
start http listening : 18888
Received request:
POST / HTTP/1.1
Host: localhost:18888
Connection: undefined
Accept: */*
User-Agent: curl/7.86.0
Request body: {"hello": "world"}
JavaScript
복사
1.8.1 GET 요청시의 바디
HTTP의 요청 중 바디를 포함하는 것이 기대되지 않는 메서드더라도 강제로 메서드를 바꾸면 바디를 송신할 수 있습니다.
$ curl -X GET ...
JavaScript
복사
단 이는 추천하지 않는 사용법으로, GET에 바디를 함께 보낼 수 있지만 유용한 방법이 아닙니다.
RFC 2616에 “서버는 메시지 바디를 읽어올 수 있어야 하지만, 요청된 메서드가 바디의 시맨틱스를 정하지 않은 경우는 요청을 처리할 때 메시지 바디는 무시돼야 한다.”라 적혀있습니다.
RFC 7231에서는 “GET, HEAD, DELETE, OPTIONS, CONNECT의 각 메서드는 페이로드 바디를 가질 수 있지만, 구현에 따라서는 서버가 이를 받아들이지 않고 거부하는 경우가 있을 수 있다”라 적혀있습니다. 단 “TRACE 메서드는 페이로드 바디를 포함해선 안 된다”라 강하게 적혀있습니다.