Search

2주차

HTTP/1.0의 시멘틱스: 브라우저 기본 기능의 이면

브라우저가 기본 기능을 어떻게 응용하고, 실행하는지 확인합니다.

2.1 단순한 폼 전송(x-www-form-urlencoded)

1.0에선 응답온 컨텐츠를 그대로 저장하는데 그쳤습니다. 하지만 HTTP/1.1부터는 범위 엑세스라는 특수한 요청 방법이 생겼습니다.
범위 엑세스 클라이언트가 서버로부터 파일의 특정 부분만을 요청할 수 있게 해주는 기능입니다. 이는 대형 파일이나 미디어 다운로드 시 일시정지 후 다시 시작하는 기능에 유용하게 사용됩니다.
form 요청이 이루어지게 되면 Content-Type은 application/x-www-form-urlencoded가되고, bodydp =와 &를 통해 담겨옵니다.
# html <form method="POST"> <input name="title"> <input name="author"> </form> # curl -d 옵션은 form으로 전송할 데이터 $ curl --http1.0 -d title="The Art of Community" -d author="Jono Bacon" http://localhost:18888 # server console Received request: POST / HTTP/1.0 Host: localhost:18888 Accept: */* User-Agent: curl/8.4.0 Content-Type: application/x-www-form-urlencoded Request body: title=The Art of Community&author=Jono Bacon
Shell
복사
웹 폼전송과 달리 -d를 통한 전송은 &와 =가 있어도 그대로 연결해버려서 문제가 생길 수 있습니다.
브라우저는 RFC 1866에서 책정한 변환 포맷에 따라 변환 후 전송되어 이러한 문제가 예방됩니다. curl에선 —data-urlencode를 통해 이를 구현되는데 브라우저가 RFC 1866을 따르는 것과 달리 RFC 3986을 따라 문자 종류가 다소 다르고, 공백이 +가 아닌 %20이 됩니다.
# curl $ curl --http1.0 --data-urlencode title="The Art of Community" --data-urlencode author="Jono Bacon" http://localhost:18888 # server console Received request: POST / HTTP/1.0 Host: localhost:18888 Accept: */* User-Agent: curl/8.4.0 Content-Type: application/x-www-form-urlencoded Request body: title=The+Art+of+Community&author=Jono+Bacon
Shell
복사

2.2 폼을 이용한 파일 전송

HTML 폼의 경우 멀티파트 폼 형식이라는 인코딩을 선택할 수 있으며 이는 RFC 1867에 정의되어 있습니다. 이는 -F 옵션을 통해 curl에서 실행할 수 있습니다.
# html <form action="POST" enctype="multipart/form-data"></form> # curl # -F key=@name; # 책에서는 key@name으로 소개하고있으나 mac에서 curl --manual으로 확인시 key=@name 안내 $ curl --form attachment-file=@test.txt http://localhost:18888
Shell
복사
일반적으로 body를 바이트 수만큼만 읽으면 되기때문에 파일간 경계를 신경 쓸 필요가 없지만, 멀티파트 폼은 여러 파일을 전송하기 때문에 받는 쪽에서 경계 문자열을 통해 나눠줍니다.
# 경계 문자열 Content-Type: multipart/form-data; boundary=------------------------pGGGkh1EZzPsg5jQRIg6pv # 응답 바디 --------pGGGkh1EZzPsg5jQRIg6pv Request body: --------------------------pGGGkh1EZzPsg5jQRIg6pv Content-Disposition: form-data; name="attachment-file"; filename="test.txt" Content-Type: text/plain test message --------------------------pGGGkh1EZzPsg5jQRIg6pv--
Shell
복사
데이터는 파일 전송 이름, 파일명, 파일 종류, 파일 내용 가 전송됩니다. 만약 enctype에 multipart/form-data를 지정하지 않을 시 파일 이름만 전송해버리기 때문에 정상적으로 파일 전송이 이루어지지 않습니다.
-d를 파일을 전송 시 파일 내용만 전송되고, -F일 때 파일 첨부가아닌 내용만 보내고 싶을 땐 =<를 사용합니다.
# curl curl -F "attachment-file=< test.txt" http://localhost:18888 # server Content-Type: multipart/form-data; boundary=------------------------6FtQbsFrYNLHNrLqmD0p1r Request body: --------------------------6FtQbsFrYNLHNrLqmD0p1r Content-Disposition: form-data; name="attachment-file" test message --------------------------6FtQbsFrYNLHNrLqmD0p1r--
Shell
복사
enctype=text/plain enctype의 기본값은 www-form-urlencoded입니다. 파일 전송시엔 multipart/form-data를 사용하고, text/plain의 경운 변환없이 개행으로 값을 구분해서 전송합니다. 다만 이는 실용적으로 사용하기엔 보안적으로 위험합니다.

2.3 폼을 이용한 리다이렉트

URL은 글자수 제한도 있고, 로그가 남아 보안상 취약하기 때문에 HTML form을 이용해서 리다이렉트가 가능합니다.

2.4 Content Negotiation

content negotiation이란 서버와 클라이언트간 최고의 설정을 공유하는 시스템을 일컫습니다. 주로 헤더를 이용합니다.
요청헤더
응답
대상
Accept
Content-Type 헤더
MIME 타입
Accept-Language
Content-Language 헤더 / html 태그
표시 언어
Accept-Charset
Content-Type 헤더
문자의 문자셋
Accept-Encoding
Content-Encoding 헤더
바디 압축

2.4.1 파일 종류 결정

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Shell
복사
image/webp로 예를 들어 설명하면 콤마로 항목 구분하고 각 항목은 각자 의미가있습니다.
image/webp
* /*;q=0.8
품질 계수로 0~1까지 수치로 결정, 생략 시 1
분석하자면 이미지가 WebP를 지원한다면 WebP로, 아니라면 다른 포멧(우선순위0.8)을 사용할 것을 설명합니다.
우선순위 값이 클수록 클라이언트가 선호하는 타입이라는 뜻
만약 일치하는 형식이 없을 시 서버가 406 Not Acceptable 오류를 반환합니다.

2.4.2 표시 언어 결정

ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6
Shell
복사
원하는 언어 순서대로 적혀있으며, Content-Language 라는 헤더에 언어 정보를 담지만 이는 잘 사용되지 않고 주로 <html lang=”ko”> 로 처리됩니다.

2.4.3 문자셋 결정

Accept-Charset 이란 헤더가 존재하지만, 브라우저들은 문자셋 인코더를 내장하고있어 이가 필요없습니다. 주로 MIME 타입과 세트로 Content-Type 헤더에 실려 옵니다.
HTML의 경우 문서안에서 작성해줄 수도 있습니다.
<!-- meta http-equiv 태그는 http 헤더와 똑같은 지시를 문서내부에 삽입해서 반한하는 상자입니다. --> <meta http-equiv-"content-Type" content="text/html; charset-UTF-8"> <meta charset="UTF-8">
HTML
복사

2.4.4 압축을 이용한 통신 속도 향상

—compressed 옵션을 통해 아래 옵션을 지정해줄 수 잇습니다.
# Accept-Encoding: deflate, gzip % curl --http1.0 --compressed 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: */* > Accept-Encoding: deflate, gzip > < HTTP/1.1 200 OK < Content-Type: text/html < Date: Fri, 05 Apr 2024 12:21:26 GMT < Connection: close < <html><body>hello</body></html> * Closing connection
Shell
복사
서버는 전송받은 목록 중 지원하는 방식이 있다면, 응답할 때 그 방식으로 압축합니다.
클라이언트에서 서버로 업로드할 때 또한 압축을 이용한 방식을 사용하는데, 서버 → 클라이언트 시 첫 웹페이지 반환시에 Accept-Encoding 헤더를 부여하고, 클라이언트가 업로드할 때 Content-Encoding을 부여해주는 방식을 사용합니다.

2.5 쿠키

쿠키는 브라우저 쪽에서 저장하는 가장 작은 파일입니다.
서버 프로그램이 볼 땐 DB처럼 외부에 데이터를 저장해두고, 클라이언트가 액세스할 때마다 데이터를 로드하는 것과 같습니다. 이를통해 Stateless한 HTTP 통신을 Statefull하게 할 수 있습니다.
-c/—cookie-jar 옵션으로 지정한 파일에 수신한 쿠키를 저장하고, -b/—cookie 옵션으로 지정한 파일에서 쿠키를 읽어와 전송합니다.
$ curl --http1.0 -c cookie.txt -b cookie.txt -b "name=value" http://localhost:18888 -v
Shell
복사

2.5.1 쿠키의 잘못된 사용법

1.
영속성
a.
여러 사유로 확실하게 저장되는 것이 아니므로, 사라지더라도 문제가 없는 정보를 사용해야합니다.
2.
용량
a.
최대 4kb이므로 주의해야합니다.
3.
보안
a.
HTTPS에선 암호화되지만 HTTP에선 평문으로 노출될 위험성이 있습니다.

2.5.2 쿠키에 제약을 주다

쿠키의 속성은 세미콜론으로 구분해 나열하며, 대소문자를 구별하지 않습니다.
Expires, Max-Age: 쿠키의 수명
Domain: 클라이언트에서 쿠키를 전송할 대상 서버
Path: 클라이언트에서 쿠키를 전송할 대상 서버의 경로
Secure: https 일 때만 클라이언트에서 서버로 쿠키를 전송함
HttpOnly: 쿠키는 JS로 다룰 수 있으니, 이 옵션을 통해 JS에서 숨길 수 있음
SameSite: RFC에는 없고, 같은 출처의 도메인에 전송하게됩니다.

2.6 인증과 세션

2.6.1 BASIC 인증과 Digest 인증

BASIC인증은 BASE64로 유저명과 패스워드를 인코딩한 것입니다. 단 SSL/TSL 통신을 사용하지 않은 상태에선 보안에 위험이 있습니다.
# -u, --user 옵션으로 유저명과 패스워드를 보냅니다. # --basic으로 BASIC 인증임을 명시할 수 있지만, default가 BASIC 모드입니다. $ curl --http1.0 --basic -u user:pass http://localhost:18888 -v # server host: 'localhost:18888', authorization: 'Basic dXNlcjpwYXNz', 'user-agent': 'curl/8.4.0', accept: '*/*'
Shell
복사
Digest 인증은 해시 함수를 이용해 더 보안에 강력합니다. 만약 브라우저가 보호된 영역에 접근하려 할 시 401 Unauthorized 응답이 돌아옵니다. 이때 아래와 같은 헤더가 부여됩니다.
WWW-Authenticate: Digest realm="영역명", nonce="123456789Ø", algorithm=MD5, qop="auth"
Shell
복사
realm: 보호되는 도메인
nonce: 서버가 매번 생성하는 랜덤한 데이터
qop: 보호 수준
클라이언트는 주어진 값과 무작위로 생성한 cnonce 값을 바탕으로 다음처럼 계산해서 response를 구합니다.
A1 = 유저명 ":" realm ":" 패스워드" A2 = HTTP 메서드 ":" 콘텐츠 URI response = MD5( MD5(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" MD5(A2) )
Shell
복사
nc: nonce 값을 사용해 전송한 횟수 - 서버가 리플레이 공격을 탐지 가능
서버 측에선 이 데이터를 기반으로 계산을 실시해 사용자 인증을 진행합니다.
$ curl --http1.0 --digest -u user1:password1 http://localhost:18888/digest -v
Shell
복사

2.6.2 쿠키를 사용한 세션 관리

지금은 BASIC과 Digest 둘 다 많이 사용되지 않습니다.
1.
톱페이지에 사용자 고유 정보를 제공할 수 없어, 초기 방문자에게 적합한 페이지가 아닙니다.
2.
요청마다 유저명과 패스워드를 인증해야합니다.
3.
명시적 로그오프가 불가능합니다.
최금엔 폼을 이용한 로그인과 쿠키를 이용한 세션 관리를 조합합니다.
[client] ID, Password → SSL/TSL 필수 → [server] 인증 후 토큰 발행 후 저장 → 쿠키에 담아 [client]에 전달
CSRF의 위험이 있어 랜덤키도 전송해줍니다.(10장에서 소개)

2.6.3 서명된 쿠키를 이용한 세션 데이터 저장

통신 속도가 빨라지며, 쿠키의 데이터양은 걱정할 필요가 없어졌습니다. 따라서 이를 이용한 데이터 관리도 많아졌습니다.
서버측에선 세션 스토리지 암호화 방식을 공통화해 데이터 저장 시스템을 준비할 필요가 없어졌고, 클라이언트 입장에선 쿠키를 가지고있는한 임시데이터가 유지되빈다.

2.7 프록시

HTTP/1.0 규격 곳곳에서 프록시 서버를 언급했고, 중계, 캐시, 네트워크 보호등의 역할을 합니다. 프록시의 구조는 단순해 GET 등의 메서드 다음에 오는 경로명 형식만 /helloworld 에서 프로토콜이 붙은 URL 형식이 됩니다.
프록시서버에 인증이 붙는 경우엔 Proxy-Authenticate 헤더에 인증 방식에 맞는 값이 들어가며, 중계되는 프록시는 중간의 호스트 IP 주소를 특정헤더에 기록해갑니다. X-Forworded-For 가 많이 이용되었지만, 2014년에 RFC 7239로 표준화되며 Foirwarded 헤더가 도입되었습니다.
# -x/--proxy 프록시 설정 # -U/--proxy-user 프록시 인증용 유저명과 패스워드 $ curl --http1.0 -x http://localhost:18888 -U user1:password1 http://example.com/helloworld
Shell
복사
—proxy-basic, —proxy-digest 등의 옵션으로 프록시 인증 방식을 변경할 수도 있습니다. 비슷한 것으로 게이트웨이가 있습니다.
프록시
통신 내용을 이해, 필요에따라 수정 or 응답
게이트웨이
통신 내용 전송, 수정x, 클라이언트는 존재를 모름

2.8 캐시

초기에는 GET과 HEAD 메서드 이외는 캐시되지 않았습니다.

2.8.1 갱신 일자에 따른 캐시

웹 브라우저가 캐시된 URL을 읽을 때는 서버에서 반환된 일시를 그대로 If-Modified-Since 헤더에 넣어 요청합니다. 웹 서버는 If-Modified-Since의 일시외 서버의 콘텐츠 일시를 비교 후 변경되었으면 200 OK를, 변경되지 않았으면 304 Not Modified를 반환하고, 바디를 응답에 포함하지 않습니다.

2.8.2 Expires

Expires 헤더에는 날짜와 시간이 들어가는데, 지정한 기간내라면 강제로 캐시를 이용하고, 요청을 보내지 않습니다. HTTP/1.0 RFC에는 없지만 HTTP/1.1을 정의한 RFC 2068에서는 변경할 일이 없는 콘텐츠라도 최대 1년의 캐시 수명을 설정하자는 가이드라인이 추가되었습니다.

2.8.3 Pragma: no-cache

HTTP/1.0부터 Pragma 헤더가 정의되어있고, 유일하게 no-cache가 정의되어있습니다. 이는 ‘요청한 콘텐츠가 이미 저장되어 있어도, 원래 서버에서 가져오라’라고 프록시 서버에 지시하는 것 입니다. HTTP/1.1에 Cache-Control로 통합되어있지만 호환성을 위해 유지하고 있습니다.
캐시 메커니즘에 클라이언트에서 지시하는 몇가지들이 있지만, 클라이언트가 정보의 수명과 품질을 일일이 관리하는 것은 부자연스러워 적극적으로 사용되는 사항은 아닙니다.

2.8.4 ETag 추가

동적으로 바뀌는 요소가 늘어날수록 캐시의 유효성 판단이 어려워집니다. 이때 RFC 2068의 HTTP/1.1에서 추가된 ETag(entity tag)가 사용하기 좋습니다. 이는 순차적인 갱신 일시가아닌 파일의 해시 값으로 비교합니다. 일시를 이용할 때처럼 서버는 응답에 ETag 헤더를 부여하고, 이후 클라이언트는 If-None-Match 헤더에 다운로드 된 캐시에 들어있던 ETag 값을 추가해 요청합니다. 서버는 ETag와 비교해 같으면 304 Not Modified로 응답합니다.
ETag는 서버가 자유롭게 결정해서 반환할 수 있고, 갱신 정보등을 선택해서 사용할 수 있습니다. (갱신 일시 - 파일 크기 등으로 부여) 또한 정적 파일의 경우 Last-Modified로 수정이 됩니다.

2.8.5 Cache-Control (1)

ETag와 같이 HTTP/1.1에서 추가된 것이 Cache-Control 입니다.
public: 같은 컴퓨터를 사용하는 복수의 사용자간 캐시 재사용 허가
private: 재사용 미허용. 같은 URL에서 사용자마다 다른 컨텐츠가 돌아오는 경우에 이용
max-age=n: 캐시의 신선도를 초단위로 설정, 지정 시간동안은 서버를 통하지 않고 캐시를 이용하며, 만료 이후 서버에 요청 후 304 Not Modified가 반환되었을 때만 캐시 이용
s-maxage=n: max-age가 같으나 공유 캐시에 대한 설정값
no-cache: 캐시가 유효한지 매번 문의, max-age=0과 동일
no-store: 캐시하지 않는다.
모순된 설정을 동시에 할 경우(no-cache와 max-age) 우선순위까지는 RFC에 없어 어떻게 될지 모릅니다.

2.8.6 Cache-Control (2)

Cache-Control 헤더를 요청 헤더에 포함함으로써 프록시에 지시할 수 있습니다.
클라이언트 → 서버
no-cache: Pragma: no-cache와 동일
no-store: 응답의 no-store와 같고, 프록시 서버에 캐시를 삭제하도록 요청
max-age: 프록시에 저장된 캐시가 최초로 저장되고나서 지정 시간 이상 캐시는 사용하지 않도록 프록시에 요청
max-stale: 지정한 시간만큼 유지시간이 지났어도, 클라이언트는 지정한 시간 동안은 저장된 캐시를 재사용하라고 프록시에 요청. 만약 생략시 영원히 유효하다는 의미
min-fresh: 캐시의 수명이 지정된 시간 이상 남아 있을 때, 캐시를 보내도 좋다고 프록시에 요청
no-transform: 프록시가 콘텐츠를 변형하지 않도록 프록시에 요청
only-if-cached: 캐시된 경우에만 응답을 반환하고, 캐시된 콘텐츠가 없을 땐 504 Gateway Timeout 오류 메시지를 반환하도록 프록시에 요청. 만약 설정되면 처음을 제외하곤 오리진 서버에 엑세스 하지않는다.
서버 → 클라이언트
no-transform: 프록시가 콘텐츠를 변경하는 것을 제어
must-revalidate: no-cache와 비슷하지만 프록시 서버에 보내는 지시가 됨. 프록시 서버가 서버에 문의했을 때 서버의 응답이 없으면 프록시서버가 클라이언트에 504 Gateway Timeout이 반환되길 기대함

Vary

같은 URL이더라도 클라이언트에 따라 반환 결과가 다름을 나타내는 헤더입니다. 모바일용 브라우저등을 표시해줘, 잘못된 콘텐츠의 캐시로 사용되지 않게합니다. 또한 검색 엔진용 힌트로도 사용됩니다.

2.9 referer

사용자가 어느 경로로 웹사이트에 도달했는지 서버가 파악할 수 있도록 클라이언트가 서버에 보내는 헤더입니다. RFC 1945 당시 오타가 그대로 남아 사용되고 있습니다.
만약 북마크에서 선택하거나, 주소창에서 직접 입력했을 때는 Referer 태그를 전송하지 않거나 Referer:about:blank를 전송합니다.
사용자의 통신 내용을 비밀로 하는 HTTPS가 HTTP/1.1에서 추가되었지만, 유출을 막고자 리퍼러 전송을 제한하는 규약이 RFC 2616으로 제정되었습니다.
출발지
목적지
전송 여부
HTTPS
HTTPS
O
HTTPS
HTTP
X
HTTP
HTTPS
O
HTTP
HTTP
O
단, 이를 엄격히 적용 시 서비스간 연계에 문제가 생겨, 제안은 되었지만 대기상태이다가 최근 브라우저의 경우(ie 제외) 이 규정을 따라 구현되었습니다. 설정 가능 방법입니다.(철자 오류가 수정됨)
Referrer-Policy 헤더
<meta name=”referrer” content=”설정값”>
<a> 태그 등 몇 가지 요소의 referrer policy 속성 및 rel=”noreferrer” 속성
리퍼러 정책으로서 설정할 수 있는 값에는 다음과 같은 것이 있습니다.
no-referrer: 전혀 보내지 않습니다.
no-referrer-when-downgrade: 현재 기본 동작과 마찬가지로 HTTPS→HTTP일 때는 전송하지 않습니다.
same-origin: 동일 도메인 내의 링크에 대해서만 리퍼러를 전송합니다.
oirigin: 상세 페이지가아닌 톱 페이지에서 링크된 것으로 해 도메인 이름만 전송
strict-origin: origin과 같지만 HTTPS→HTTP 일 때는 전송하지 않습니다.
origin-when-crossorigin: 같은 도메인 내에서는 완전 리퍼러를, 다른 도메인에는 도메인 이름만 전송합니다.
strict-origin-when-crossorigin: origin-when-crossorigin과 같지만 HTTPS→HTTP일 때는 송신하지 않습니다.
unsafe-url: 항상 전송
이외에 Content-Security-policy 헤더로 지정가능합니다.
Content-Security-Policy: referrer origin

2.10 검색 엔진용 콘텐츠 접근 제어

2.10.1 robots.txt

서버 콘텐츠 제공자가 크롤러에 접근 허가 여부를 전하기 위한 프로토콜로, 크롤러 개발자들의 신사협정(구속력을 갖지 않는 협약)입니다.
다음과 같은 방식으로 읽기를 금지할 크롤러의 이름과 장소를 지정합니다.
User-agent: * Disallow: /cgi-bin/ Disallow: /tmp/
Shell
복사
이는 메타 태그로도 가능합니다.
<meta name="robots" content="noindex" />
Shell
복사
noindex: 검색 엔진이 인덱스하는 것을 거부
nofollow: 크롤러가 페이지 내의 링크를 따라가는 것을 거부
noarchive: 페이지 내 콘텐츠를 캐시하는 것을 거부
같은 티렉티브는 HTTP의 X-Robots-Tag 헤더에도 쓸 수 있습니다.

2.10.2 robots.txt의 재판결과

현재 정식 RFC는 아니지만, 메타 태그에 따른 크롤링 의사 표시를 법적으로도 인정하고 있습니다.

2.10.3 사이트맵

웹사이트에 포함된 페이지 목록과 메타데이터를 제공하는 XML로 2005년에 구글이 개발해 여러곳에서 이용중입니다. robots.txt가 블랙리스트처럼 사용된다면, 사이트맵은 화이트리스트처럼 사용됩니다. 이는 크롤러가 페이지를 찾기어려운 동적페이지 등 크롤러가 페이지를 찾기 어려운 경우를 보완합니다.
이는 XML로도 작성이 가능하고, robots.txt에도 쓸 수 있습니다.
# robots.txt Sitemap: http://뻐vw.example.org/sitemap.xml
Shell
복사

3. GoNode 언어를 이용한 HTTP/1.0 클라이언트 구현

3.1 Go 언어를 이용하는 이유

3.2 Go 언어의 API 구성

3.3 이 장에서 다룰 레시피

3.4 GET 메서드 송신과 바디, 스테이터스 코드, 헤더 수신

app.get('/get', (req, res) => { try{ console.log(`Received request: ${req.method} ${req.url} HTTP/${req.httpVersion}`); console.log(`Host: ${req.headers.host}`); console.log(`Accept: ${req.headers.accept}`); console.log(`User-Agent: ${req.headers['user-agent']}`); console.log(`Content-Type: ${req.headers['content-type']}`); console.log(`Request body: ${JSON.stringify(req.body)}`); console.log('----------------------------------------'); res.status(200).send('<html><body>hello</body></html>\n'); }catch(e){ res.status(200).send('error') } })
Shell
복사

3.4.1 io.Reader → fs.createReadStream

fs.readFile('test.txt', (err, data) => { if (err) { res.writeHead(404); res.end("File not found"); } else { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(data); } });
JavaScript
복사

3.5 GET 메서드+쿼리 전송

# 사용 불가 문자가 없을 시 --data, -d 사용가능 $ curl -G --data-urlencode "query=hello world" http://localhost:18888/get/query # [["node","js"],["data","less"]]
Shell
복사
router.get('/query', (req, res) => { try{ const data = req.query; const arr = []; for(const [key, value] of Object.entries(data)){ console.log(key, value); arr.push([key, value]) } res.status(200).send(arr) }catch(e){ res.status(500).send('error') } }) // Received request: GET /?node=js&data=less HTTP/1.1
JavaScript
복사

3.6 HEAD 메서드로 헤더 가져오기

HEAD의 정의대로 바디를 가져올 수 없으며, 읽어와도 오류가 되지않지만 길이가 0인 바이트 배열이 반환됩니다.
$ curl --head http://localhost:18888/head -v
JavaScript
복사
router.get('/', (req, res) => { try{ res.status(200).send(); }catch(e){ res.status(500).send('error') } }); // Received request: HEAD / HTTP/1.1
JavaScript
복사

3.7 x-www-form-urlencoded 형식의 POST 메서드 전송

$ curl -d data=please http://localhost:18888/post/form -v
Shell
복사
// express 4.16.0 버전 이후부턴 body-parser 기능이 내장 // app.use(express.urlencoded({ extended: true }));으로 처리 가능 router.post('/form', (req, res) => { try{ const data = req.body; console.log(data) res.send('<html><body>hello</body></html>\n'); }catch(e){ res.status(500).send('error') } });
JavaScript
복사

3.8 POST 메서드로 임의의 바디 전송

HTTP/1.1 이후 등장한 XMLHttpRequest를 이용해 콘텐츠를 바디에 넣어 보낼 수 있습니다.
$ curl -X POST "data= please" http://localhost:18888/post/read-file -v * Trying [::1]:18888... * Connected to localhost (::1) port 18888 > POST /post/read-file HTTP/1.1
Shell
복사
router.post('/read-file', (req, res) => { try{ fs.readFile('test.txt', 'utf8', (err, data) => { if (err) { console.error(err); res.writeHead(500); res.end('Server Error'); return; } res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(data); }); }catch(e){ console.log(e) res.status(500).send('error') } });
JavaScript
복사

3.9 multipart/form-data 형식으로 파일 전송

$ curl -F "name=Michel" -F "thumbnail=@test.txt" http://localhost:18888/post/form -v
Shell
복사
router.post('/form', (req, res) => { try{ const data = req.body; console.log(data) res.send('<html><body>hello</body></html>\n'); }catch(e){ res.status(500).send('error') } }); // Content-Type: multipart/form-data; boundary=------------------------tPbD5qdPcGRKQ2xpD0gLfL
JavaScript
복사

3.10 쿠키 송수신

# client $ curl http://localhost:18888/get/cookie -v * Trying [::1]:18888... * Connected to localhost (::1) port 18888 > GET /get/cookie HTTP/1.1 > Host: localhost:18888 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 200 OK < X-Powered-By: Express < Connection: close < Set-Cookie: key=value; Max-Age=900; Path=/; Expires=Tue, 09 Apr 2024 11:59:12 GMT; HttpOnly < Content-Type: text/html; charset=utf-8 < Content-Length: 39 < ETag: W/"27-0nyqe8+SR4HowkpaSg6cg1KjBBY" < Date: Tue, 09 Apr 2024 11:44:12 GMT <
Shell
복사
// server import cookieParser from 'cookie-parser'; app.use(cookieParser()); router.get('/cookie', (req, res) => { try{ res.cookie('key', 'value', { maxAge: 900000, httpOnly: true }); res.send('<html><body>hello cookie</body></html>\n'); }catch(e){ res.status(500).send('error') } });
JavaScript
복사

3.11 프록시 이용

# curl porxy $ curl -x http://localhost:18888/get/proxy http://github.com
Shell
복사
router.get('/proxy', (req, res) => { try{ proxy.web(req, res, {target: 'http://example.com'}); }catch(e){ res.status(500).send('error') } });
JavaScript
복사

3.12 파일 시스템 액세스

# curl file access $ curl file://main.txt
Shell
복사
fs.writeFile() fs.readFile() fs.appendFile()
JavaScript
복사

3.13 자유로운 메서드 전송

# curl method make $ curl -X DELETE http://localhost:18888
JavaScript
복사
request.post('http://localhost:18888/post/read-file');
JavaScript
복사

3.14 헤더 전송

$ curl -H "Content-Type=image/jpeg" -d "@image.jpeg" http://localhost:18888
Shell
복사
res.setHeader('Content-Type', 'image/jpeg');
JavaScript
복사

3.15 국제화 도메인

자체적으론 존재하지 않아 라이브러리 사용 필요