웹 클라이언트
서버에게 무언가를 달라고 하는 모든 것.
Chrome 도 웹 클라이언트이고, curl 한 줄도 웹 클라이언트입니다. 이 장에서는 HTTP 요청을 손으로 직접 써 보고, 그것을 쉘 스크립트로 감싸고, curl 을 제대로 익힌 다음, Chrome 개발자 도구를 열어 똑같은 글이 오가는 것을 눈으로 확인합니다.
먼저 묻는 쪽이 클라이언트입니다
클라이언트와 서버는 기계가 아니라 역할입니다. 말을 먼저 걸어 무언가를 달라고 하는 쪽이 클라이언트이고, 기다렸다가 답하는 쪽이 서버입니다. 브라우저도 클라이언트, curl 도 클라이언트, 휴대폰 앱도, API 를 부르는 다른 서버도 클라이언트입니다.
그래서 웹 클라이언트를 한 줄로 쓸 수 있습니다. 창도, 렌더링 엔진도, 마우스도 필요 없습니다 — 연결하고, 요청을 보내고, 돌아온 것을 읽으면 됩니다.
| 무엇이 | 클라이언트 | 서버 |
|---|---|---|
| 누가 먼저 말하나 | 연결을 열고 요청을 보냅니다. | 포트에서 기다립니다. 누가 연결할 때까지 말이 없습니다. |
| 누가 주소가 필요한가 | 서버가 어디 있는지 알아야 합니다 — 도메인이나 IP, 그리고 포트. | 변하지 않는 주소가 있어야 합니다. 서버가 도메인 이름을 갖고 클라이언트는 대개 갖지 않는 이유입니다. |
| 한 번에 몇이나 | 보통은 한 번에 한 대화입니다. 다만 브라우저는 페이지를 빨리 불러오려고 여러 개를 함께 엽니다. | 한꺼번에 여럿입니다. 진짜 서버 소프트웨어가 복잡해지는 이유의 대부분이 여기 있습니다. |
지도를 받아 오거나 카드를 긁는 웹서버는 그 요청에서는 클라이언트로 행동합니다. 역할은 순식간에 바뀌므로, 양쪽 끝에 어떤 기계가 있느냐가 아니라 지금 이 요청이 어느 방향으로 가느냐를 물어야 합니다.
터미널로 웹 클라이언트 만들기
HTTP 가 글자일 뿐이라는 것을 가장 빨리 믿는 방법은 요청을 직접 쳐 보는 것입니다. 앞 주제의 쉘 웹서버를 띄워 놓고, 터미널을 하나 더 열어 손으로 말을 걸어 보세요.
nc 로 요청을 손으로 쓰기
netcat 은 포트에 연결해 여러분이 친 글자를 그대로 선에 흘려보냅니다. 두 줄을 치고 엔터를 두 번 누르면 서버가 답합니다 — 방금 브라우저의 일을 손으로 한 것입니다.
# connect, then type the two lines below and press Enter once more
nc localhost 8084
GET / HTTP/1.1
Host: localhost
# or send it all at once (printf's \r\n is HTTP's line break)
printf 'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n' | nc localhost 8084끝의 빈 줄이 중요합니다. 요청이 끝났다고 서버에게 알리는 표시이고, 그래서 엔터를 한 번 더 누릅니다. HTTP 는 줄 끝마다 캐리지리턴과 줄바꿈을 요구합니다. 손으로 쳐도 되는 것은 대개의 서버가 너그럽기 때문이고, printf 판은 규격이 요구하는 그대로를 보냅니다. Host 줄은 HTTP/1.1 이 반드시 요구합니다. 진짜 사이트에 이것 없이 보내면 페이지 대신 오류가 돌아오는 것이 보통입니다.
스크립트로 감싸기
요청이 printf 한 줄이 되고 나면, 다시 쓸 수 있는 클라이언트는 몇 줄이면 됩니다. 아래 스크립트는 호스트 · 포트 · 경로를 받아 요청을 보내고, 헤더와 본문을 갈라 보여 줍니다.
#!/bin/bash
# usage: bash client.sh localhost 8084 /
HOST=${1:-localhost}
PORT=${2:-8084}
PATH_=${3:-/}
printf 'GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n' "$PATH_" "$HOST" \
| nc "$HOST" "$PORT" \
| tr -d '\r' \
| awk 'BODY { print; next } /^$/ { BODY = 1; next } { print "[header] " $0 }'tr 은 HTTP 가 줄 끝마다 남기는 캐리지리턴을 걷어 내고, awk 는 빈 줄을 만날 때까지를 헤더로 표시합니다 — 그 뒤는 전부 본문입니다. 브라우저가 화면을 그리기 전에 하는 것과 똑같은 나눔입니다.
한 터미널에 쉘 웹서버를, 다른 터미널에 이 클라이언트를 띄우면 여러분이 직접 쓴 대화의 양쪽 끝을 모두 갖게 됩니다. 서버의 응답을 고치면 클라이언트가 곧바로 알아봅니다 — 사이에 아무것도 없기 때문입니다.
curl — 매일 쓰게 될 클라이언트
우리 스크립트가 대충 하던 것을 curl 은 제대로 합니다. 리다이렉트, HTTPS, 헤더, 업로드, 시간 제한, 쿠키까지. 거의 모든 기계에 들어 있고, 브라우저를 끼우지 않고 서버를 확인하는 표준 방법입니다.
# body only
curl http://localhost:8084
# body plus the response headers
curl -i http://localhost:8084
# the whole exchange, request headers included
curl -v http://localhost:8084중요한 옵션들
| 옵션 | 하는 일 |
|---|---|
| -i | 본문과 함께 응답 헤더도 보여 줍니다. 페이지가 예상과 다를 때 가장 먼저 붙이는 옵션입니다. |
| -v | 자세히 보기. 보낸 요청과 받은 응답을 연결 과정까지 한 줄씩 보여 줍니다. |
| -L | 리다이렉트를 따라갑니다. 없으면 301 이 왔을 때 원하던 페이지 대신 거의 빈 응답을 받게 됩니다. |
| -o FILE | 응답을 화면에 뿌리지 않고 파일로 저장합니다. curl 을 내려받기 도구로 쓰는 방식입니다. |
| -H "Name: value" | 요청 헤더를 더합니다 — 내용의 종류, 인증 토큰, 원하는 언어 같은 것들. |
| -X POST -d "data" | 묻기만 하지 않고 자료를 보냅니다. 자료를 붙이면 POST 가 되고, 터미널에서 양식이나 API 를 시험하는 방법이 이것입니다. |
| -s | 조용히. 진행 막대를 없앱니다. 출력을 다른 명령이나 스크립트로 넘길 때는 늘 붙입니다. |
| -A "name" | 특정 브라우저인 척합니다. 어떤 사이트는 묻는 쪽이 누구냐에 따라 다르게 답합니다. |
API 호출하기
요즘 앱이 말을 거는 상대는 페이지가 아니라 JSON 을 돌려주는 API 인 경우가 많습니다. curl 이 그것을 받아 오고 jq 가 읽기 좋게 다듬습니다. 응용 코드를 한 줄도 쓰기 전에 엔드포인트를 확인하는 방법입니다.
# fetch JSON and pick one field out of it with jq
curl -s https://api.github.com/repos/sveltejs/svelte | jq '.stargazers_count'
# send JSON with POST
curl -X POST https://httpbin.org/post \
-H 'Content-Type: application/json' \
-d '{"name":"PES","level":1}'
# download to a file
curl -L -o page.html https://getpes.com무엇이든 curl 로 먼저 시험하는 버릇을 들이세요. curl 이 제대로 된 답을 받으면 서버는 멀쩡하고 잘못은 내 프로그램에 있는 것이고, curl 도 이상한 답을 받으면 엉뚱한 데를 뒤지며 반나절을 버릴 뻔한 것을 아낀 셈입니다.
Chrome 도 같은 클라이언트입니다, 크기만 다를 뿐
Chrome 은 여러분이 nc 로 보낸 그 요청을 보냅니다. 그러고 나서 돌아온 답을 사람이 볼 수 있는 화면으로 바꾸는 어마어마한 일을 더 합니다 — 진짜 차이는 그 "더" 뿐입니다.
그 단계들을 알아 둘 값어치가 있습니다. 하나하나가 페이지가 어긋날 수 있는 자리이기 때문입니다. 무언가 안 보일 때 물어야 할 것은 언제나 "어느 단계에서 실패했는가" 입니다.
주소창에서 그림까지
- 이름 풀기 DNS 가 도메인 이름을 IP 주소로 바꿉니다. 여기서 실패하면 서버에 닿아 보지도 못하고, 오류 메시지도 페이지가 아니라 이름을 가리킵니다.
- 연결하고 안전하게 만들기 연결을 열고, https 라면 무엇을 보내기도 전에 인증서를 확인합니다. 여기서 나오는 경고는 페이지가 없다는 뜻이 아니라 암호화를 믿을 수 없다는 뜻입니다.
- 요청 보내기 여러분이 손으로 친 것과 같은 몇 줄에 헤더가 훨씬 많이 붙습니다. 쿠키, 읽을 수 있는 형식, 좋아하는 언어 같은 것들입니다.
- 해석하고 나머지 받아 오기 HTML 을 읽어 태그로 요소의 나무를 세우고, 페이지가 가리키는 것을 전부 요청합니다 — 스타일 파일, 그림, 글꼴, 스크립트를 보통 여러 개 동시에.
- 꾸미고, 배치하고, 그리기 CSS 를 입히고, 상자마다 자리를 계산하고, 결과를 그린 다음, 그 모든 것을 다시 바꿀 수 있는 JavaScript 를 돌립니다.
터미널 클라이언트냐 브라우저냐
Chrome · Edge · Brave · Opera 는 모두 Chromium 위에 지어졌으므로, 하나에서 되는 페이지는 대개 나머지에서도 됩니다. Safari 와 Firefox 는 다른 엔진을 씁니다. 사이트를 다 만들었다고 말하기 전에 최소한 두 종류에서 열어 봐야 하는 이유가 그것입니다.
개발자 도구 — 오가는 글을 눈으로 보기
Chrome 에는 세상에서 가장 좋은 웹 디버깅 도구가 들어 있고, 이미 설치되어 있습니다. nc 로 손수 보내던 것이 여기에 요청 하나하나로, 그 옆에 응답까지 함께 늘어섭니다.
F12 를 누르거나, macOS 는 Command-Option-I, 그 밖에서는 Control-Shift-I 입니다. 페이지의 아무 곳이나 오른쪽 눌러 [검사]를 고르면 그 요소가 이미 선택된 채로 열립니다 — 가장 빠른 입구입니다.
패널들
지금 바로 해 볼 세 가지
- 내 요청 찾아보기 Network 패널을 열고 이 페이지를 새로고침한 뒤 맨 첫 항목을 눌러 보세요. 거기 있는 요청·응답 헤더가 여러분이 nc 에 쳐 넣던 바로 그 줄들입니다.
- curl 로 복사하기 요청을 오른쪽 눌러 [Copy as cURL] 을 고르고 터미널에 붙여 넣으세요. 헤더도 쿠키도 그대로, 브라우저가 한 일을 똑같이 재현한 것입니다.
- 일부러 망가뜨려 보기 Elements 패널에서 제목을 지워 사라지는 것을 보고, Network 에서 속도를 느리게 조절한 뒤 새로고침해 보세요. 둘 다 안전하고, 둘 다 글로 읽는 것보다 훨씬 많이 가르쳐 줍니다.
첫 주제에서 만든 HTML 파일을 열어 검사해 보면 나무 속의 태그가 전부 눈에 익을 것입니다. 쓰고 · 내놓고 · 요청하고 · 들여다보는 이 한 바퀴가 웹 개발 전체의 축소판입니다.
스스로 표시하는 칸이에요. 여기서 채점하지는 않아요.