vgrok — ngrok과 다른 두 가지

내 컴퓨터에서 서버를 띄워도 밖에서는 못 들어온다. 전화기는 있는데 번호가 없는 것과 같다 — 내가 거는 건 되는데 남이 나에게 걸 수는 없다(NAT·방화벽 뒤라서).

터널링의 요령은 이 비대칭을 뒤집는 것이다. 내가 먼저 밖으로 걸어놓고 끊지 않는다. 번호가 공개된 쪽이 그 통화를 붙들고 있다가, 자기한테 온 요청을 그 통화에 대고 흘려보낸다.

외부 요청 ──HTTP──▶ [공개 머신] ──┐
                                │ WebSocket (내 컴퓨터가 먼저 걸어둔 것)
              내 컴퓨터 ◀──HTTP──┘

여기까지는 ngrok도 같다. vgrok이 다른 건 둘이다.

하나 — 공개 머신을 빌린다

ngrok은 그 “번호가 공개된 쪽”을 자기 인프라로 굴린다. vgrok은 잠깐 빌리는 컴퓨터(Vercel Sandbox)를 그 자리에 끼워넣는다. 마침 필요한 두 가지가 다 딸려 오기 때문이다 — 공개 주소가 그냥 나오고(도메인도 인증서도 설정 0), 코드를 올려 돌릴 수 있다.

그러니 터널 머신은 특별한 물건이 아니다. “공개 주소가 나오고 코드가 돌면” 무엇이든 된다. Sandbox는 그걸 가장 싸게 얻는 한 가지 방법일 뿐이다.

핵심 코드가 220줄쯤인 것이 그 증거다 — 네트워킹·보안·HTTPS·DNS를 빌린 쪽이 다 떠안아서 남는 게 이만큼이다.

대가는 수명이다. 빌린 거라 오래 못 쓴다 — Hobby는 한 세션에 45분.1 다만 세션 단위라 멈췄다 이어붙이면 총 수명은 늘릴 수 있다.

둘 — 바이트가 아니라 메시지를 나른다

ngrok은 TCP 터널도 준다. 바이트를 그대로 흘려보내니 SSH·데이터베이스·MQTT·게임 서버까지 나른다.2

vgrok은 그렇지 않다. HTTP 요청을 뜯어서 method·url·headers·body로 만들고, 반대편에서 다시 조립한다. WebSocket은 끊기지 않는 통로 역할만 하고, 그 위로 흐르는 건 직렬화된 HTTP 메시지다.

이 한 가지 선택에서 나머지가 전부 따라 나온다.

그래서 왜냐면
번호표를 붙인다 통로 하나에 여러 요청·응답을 섞어 싣기 때문. 바이트 터널이면 연결마다 흐름이 따로라 필요 없다
바이너리를 글자로 바꾼다 메시지를 JSON으로 만드니까. 이미지를 그대로 못 싣는다
HTTP만 된다 HTTP를 뜯어서 나르니까. 뜯을 수 없는 프로토콜은 못 나른다
다 모아야 보낸다 메시지 한 덩어리로 부치니까. 양쪽 다 on('data')로 모았다가 on('end')에서 한 번에 넘긴다3

이미지·CSS 같은 static 리소스도 특별 취급이 없다. 똑같이 바이트를 글자로 바꿔 실었다 되돌린다. 되긴 되는데 하나씩 통째로 통과한다 — 점진 렌더가 안 되고, 큰 파일은 메모리에 통째로 올라가며, base64라 전송량이 3분의 1쯤 늘어난다. 끝나지 않는 응답(SSE 같은 것)은 아예 못 다룬다.


그 밖에 클라이언트는 하나만 붙는다.

Footnotes

  1. 최대 세션 길이는 Hobby 45분 · Pro/Enterprise 24시간. “The maximum duration applies to a single session, not to the sandbox itself. The limit resets every time a sandbox stops and resumes, so the total lifetime of a persistent sandbox is effectively unbounded.” (Vercel Sandbox — pricing and limits)

  2. “TCP endpoints enable you to deliver any network service with a TCP-based protocol” — SSH·VNC·RDP, MySQL·Postgres, MQTT, 게임 서버 등. (ngrok — TCP endpoints)

  3. server.tsreq.on('data', chunk => chunks.push(chunk)) 로 모아 req.on('end') 에서 Buffer.concat(chunks).toString('base64url') 로 부치고, 받는 쪽은 res.end(Buffer.from(body, 'base64url')) 로 한 번에 쓴다. client.ts 도 응답을 같은 방식으로 모은다. (vgrok — styfle)

#552

localtunnel은 쉽게 테스트하고 공유할 수 있도록 로컬 호스트를 공개합니다! 다른 사람들이 변경 사항을 테스트하도록 하기 위해 DNS를 엉망으로 만들거나 배포할 필요가 없습니다.

app.listen(PORT, async () => {
  const tunnel = await localtunnel({
    port: PORT,
    subdomain: name,
  })

하지만 너무 느려서 ngrok 쓰는게 현실적일수도 있겠다. -20220917



Footnotes

  1. 로컬 환경에서 웹훅 테스트를 위해, API 엔드포인트를 만들고 localtunnel이나 ngrok을 사용하기.

#68