웹 클라이언트
에게 무언가를 달라고 하는 모든 것.
Chrome 도 웹 이고, 한 줄도 웹 클라이언트입니다. 이 장에서는 을 손으로 직접 써 보고, 그것을 스크립트로 감싸고, curl 을 제대로 익힌 다음, Chrome 를 열어 똑같은 글이 오가는 것을 눈으로 확인합니다.
먼저 묻는 쪽이 클라이언트입니다
클라이언트와 서버는 기계가 아니라 역할입니다. 말을 먼저 걸어 무언가를 달라고 하는 쪽이 클라이언트이고, 기다렸다가 답하는 쪽이 서버입니다. 도 클라이언트, curl 도 클라이언트, 휴대폰 앱도, 를 부르는 다른 서버도 클라이언트입니다.
그래서 웹 클라이언트를 한 줄로 쓸 수 있습니다. 창도, 렌더링 엔진도, 마우스도 필요 없습니다 — 연결하고, 요청을 보내고, 돌아온 것을 읽으면 됩니다.
| 무엇이 | 클라이언트 | 서버 |
|---|---|---|
| 누가 먼저 말하나 | 연결을 열고 요청을 보냅니다. | 에서 기다립니다. 누가 연결할 때까지 말이 없습니다. |
| 누가 주소가 필요한가 | 서버가 어디 있는지 알아야 합니다 — 이나 , 그리고 포트. | 변하지 않는 주소가 있어야 합니다. 서버가 도메인 이름을 갖고 클라이언트는 대개 갖지 않는 이유입니다. |
| 한 번에 몇이나 | 보통은 한 번에 한 대화입니다. 다만 브라우저는 페이지를 빨리 불러오려고 여러 개를 함께 엽니다. | 한꺼번에 여럿입니다. 진짜 서버 소프트웨어가 복잡해지는 이유의 대부분이 여기 있습니다. |
지도를 받아 오거나 카드를 긁는 웹서버는 그 요청에서는 클라이언트로 행동합니다. 역할은 순식간에 바뀌므로, 양쪽 끝에 어떤 기계가 있느냐가 아니라 지금 이 요청이 어느 방향으로 가느냐를 물어야 합니다.
터미널로 웹 클라이언트 만들기
가 글자일 뿐이라는 것을 가장 빨리 믿는 방법은 요청을 직접 쳐 보는 것입니다. 앞 주제의 쉘 웹서버를 띄워 놓고, 을 하나 더 열어 손으로 말을 걸어 보세요.
nc 로 요청을 손으로 쓰기
은 포트에 연결해 여러분이 친 글자를 그대로 선에 흘려보냅니다. 두 줄을 치고 엔터를 두 번 누르면 서버가 답합니다 — 방금 브라우저의 일을 손으로 한 것입니다.
# 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" | 묻기만 하지 않고 자료를 보냅니다. 자료를 붙이면 가 되고, 터미널에서 양식이나 API 를 시험하는 방법이 이것입니다. |
-s | 조용히. 진행 막대를 없앱니다. 출력을 다른 명령이나 스크립트로 넘길 때는 늘 붙입니다. |
-A "name" | 특정 브라우저인 척합니다. 어떤 사이트는 묻는 쪽이 누구냐에 따라 다르게 답합니다. |
API 호출하기
요즘 앱이 말을 거는 상대는 페이지가 아니라 을 돌려주는 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":"SONUB","level":1}'
# download to a file
curl -L -o page.html https://sonub.com
무엇이든 curl 로 먼저 시험하는 버릇을 들이세요. curl 이 제대로 된 답을 받으면 서버는 멀쩡하고 잘못은 내 에 있는 것이고, curl 도 이상한 답을 받으면 엉뚱한 데를 뒤지며 반나절을 버릴 뻔한 것을 아낀 셈입니다.
Chrome 도 같은 클라이언트입니다, 크기만 다를 뿐
Chrome 은 여러분이 nc 로 보낸 그 요청을 보냅니다. 그러고 나서 돌아온 답을 사람이 볼 수 있는 화면으로 바꾸는 어마어마한 일을 더 합니다 — 진짜 차이는 그 "더" 뿐입니다.
그 단계들을 알아 둘 값어치가 있습니다. 하나하나가 페이지가 어긋날 수 있는 자리이기 때문입니다. 무언가 안 보일 때 물어야 할 것은 언제나 "어느 단계에서 실패했는가" 입니다.
주소창에서 그림까지
- 이름 풀기 가 도메인 이름을 IP 주소로 바꿉니다. 여기서 실패하면 서버에 닿아 보지도 못하고, 오류 메시지도 페이지가 아니라 이름을 가리킵니다.
- 연결하고 안전하게 만들기연결을 열고, https 라면 무엇을 보내기도 전에 를 확인합니다. 여기서 나오는 경고는 페이지가 없다는 뜻이 아니라 암호화를 믿을 수 없다는 뜻입니다.
- 요청 보내기여러분이 손으로 친 것과 같은 몇 줄에 헤더가 훨씬 많이 붙습니다. 쿠키, 읽을 수 있는 형식, 좋아하는 언어 같은 것들입니다.
- 해석하고 나머지 받아 오기 을 읽어 로 의 나무를 세우고, 페이지가 가리키는 것을 전부 요청합니다 — 스타일 파일, 그림, 글꼴, 스크립트를 보통 여러 개 동시에.
- 꾸미고, 배치하고, 그리기 를 입히고, 상자마다 자리를 계산하고, 결과를 그린 다음, 그 모든 것을 다시 바꿀 수 있는 를 돌립니다.
터미널 클라이언트냐 브라우저냐
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 파일을 열어 검사해 보면 나무 속의 태그가 전부 눈에 익을 것입니다. 쓰고 · 내놓고 · 요청하고 · 들여다보는 이 한 바퀴가 웹 개발 전체의 축소판입니다.
스스로 표시하는 칸이에요. 여기서 채점하지는 않아요.
이 단계를 이해한 것으로 표시할까요?
아직 취소할 수 없으니, 정말 이해했을 때만 표시해 주세요.
이해한 것으로 표시했어요
스스로 표시하는 칸이에요. 여기서 채점하지는 않아요.