개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › 네트워크

URL

URL은 어떤 부분들로 이루어져 있는가

주소창에 적는 http://localhost:8080/hello?name=kim 같은 URL은 한 덩어리의 글자처럼 보이지만, 정해진 기호(://, :, /, ?)로 나뉘는 부분들의 모음입니다. 스킴(http)은 어떤 규칙으로 대화할지, 호스트(localhost)는 어느 컴퓨터인지, 포트(8080)는 그 컴퓨터의 어느 프로그램인지, 경로(/hello)는 그 프로그램에게 무엇을 달라는지, 쿼리(?name=kim)는 덧붙여 보내는 값입니다. 클라이언트(브라우저)는 URL을 이 부분들로 나눈 뒤 호스트와 포트로 연결하고, 경로와 쿼리만 HTTP 요청의 첫 줄에 적어 서버에 보냅니다. 이 실습은 부분을 하나씩 지우거나 바꿔 가며 같은 서버(/hello 주소만 만들어 둔 서버)에 요청을 보내, 어느 부분이 어디에 쓰이는지와 부분이 없으면 무슨 일이 생기는지를 봅니다.

실행으로 대조 · Node.js 24.18.0에서 스물네 가지 조합을 실행해 대조 (포트를 생략한 조합은 80번에 연결하지 않고 연결하려던 곳만 가로채 확인)

이 실습에 나오는 개념

URL의 구성 요소
URL은 정해진 기호를 기준으로 여러 부분으로 나뉜다. https://shop.example:8443/shop/list?q=a#review 에서 https가 스킴, shop.example이 호스트, 8443이 포트, /shop/list가 경로, q=a가 쿼리, review가 프래그먼트다. 포트를 적지 않으면 스킴의 기본 포트(http는 80, https는 443)로 접속한다. 호스트와 포트는 어디에 연결할지를 정하고, 경로와 쿼리는 요청 줄에 실려 서버로 간다. 프래그먼트는 브라우저가 받은 문서 안의 위치를 찾는 데만 쓰며 서버로 보내지 않는다.
스킴과 기본 포트
스킴은 URL의 맨 앞에서 :가 나오기 전까지의 이름으로, 이 URL로 어떤 규칙에 따라 대화할지를 알려 준다. http://localhost/hello의 http가 스킴이며, 영문자로 시작한다(RFC 3986 3.1절). 스킴은 기본 포트도 정할 수 있어서(3.2.3절), http의 기본 포트는 80이고 URL에 포트를 적지 않으면 클라이언트가 80번으로 연결한다(RFC 9110 4.2.1절). 스킴이 없으면 :나 /가 무엇을 나누는 기호인지 알 수 없어, URL 표준(WHATWG)에 따르면 기준이 되는 다른 주소가 없는 한 주소로 읽히지 않는다. localhost:8080/hello처럼 :가 있으면 localhost를 스킴으로 읽어 버리는 일도 생긴다.
경로와 쿼리
경로는 호스트와 포트 다음의 /부터 ? 앞까지, 쿼리는 첫 ?부터 #이나 URL의 끝까지다(RFC 3986 3.3절, 3.4절). 쿼리는 name=kim처럼 이름=값을 적어 경로에 덧붙이는 값으로, 값이 여러 개면 &로 잇는 것이 널리 쓰이는 관례다. 클라이언트는 URL 가운데 이 둘만 요청의 첫 줄(요청 줄)에 적어 보내고(RFC 9112 3.2.1절의 origin-form), 경로가 비어 있으면 /를 보내야 한다. 스킴, 호스트, 포트는 첫 줄에 들어가지 않는다. 호스트와 포트는 연결에 쓰이고 Host 헤더에도 적혀 간다(RFC 9110 7.2절).
포트
한 컴퓨터에서 돌고 있는 여러 프로그램 가운데 누가 데이터를 받을지 구분하는 0부터 65535까지의 번호다. IP 주소가 컴퓨터를 가리킨다면 포트는 그 컴퓨터 안의 프로그램을 가리키므로, 접속할 곳은 "IP 주소:포트"로 적는다. 서버 프로그램은 정해 둔 포트에서 연결을 기다리며, 웹 서버의 80번(HTTP)과 443번(HTTPS)처럼 널리 약속된 번호도 있다. 한 포트에서 연결을 기다릴 수 있는 프로그램은 보통 하나뿐이어서, 이미 쓰이는 포트에 서버를 또 띄우면 "주소가 이미 사용 중"이라는 오류가 난다. TCP의 포트와 UDP의 포트는 따로 관리되므로 번호가 같아도 서로 다른 것이다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 클라이언트(브라우저)가 "http://localhost:8080/hello?name=kim"라는 URL을 받습니다. URL은 한 덩어리의 글자가 아니라, 정해진 기호(://, :, /, ?)로 나뉘는 부분들의 모음입니다. 클라이언트는 이 기호를 보고 왼쪽부터 부분으로 나눕니다. 서버는 /hello 주소만 만들어 둔 서버입니다.
  2. 맨 앞에서 ://까지가 스킴이고, 여기서는 "http"입니다. 스킴은 서버와 어떤 규칙으로 대화할지를 알려 줍니다. http는 HTTP 규칙, 곧 요청 줄과 헤더를 보내고 응답을 받는 규칙으로 대화한다는 뜻입니다.
  3. ://다음부터 / 앞까지가 호스트와 포트입니다. 이 가운데 :의 앞이 호스트이고, 여기서는 "localhost"입니다. 호스트는 어느 컴퓨터에 연결할지를 알려 줍니다.
  4. :의 뒤가 포트이고, 여기서는 "8080"입니다. 한 컴퓨터에서는 여러 프로그램이 돌 수 있어서, 포트는 그 가운데 어느 프로그램에 연결할지를 알려 줍니다. 우리 서버 프로그램은 8080번에서 기다리고 있습니다.
  5. 그다음 /부터 ?의 앞까지가 경로이고, 여기서는 "/hello"입니다. 경로는 연결한 프로그램에게 무엇을 달라는지를 말합니다.
  6. ?의 뒤가 쿼리이고, 여기서는 "name=kim"입니다. 이름=값 꼴로, 경로에 덧붙여 보내는 값입니다.
  7. 클라이언트가 호스트와 포트로 연결합니다. localhost 컴퓨터(이 PC)의 8080번 포트에서 기다리는 서버 프로그램에 연결합니다. 스킴, 호스트, 포트는 이 연결에 쓰였습니다.
  8. 연결되면 클라이언트가 요청의 첫 줄에 경로와 쿼리를 적어 보냅니다: "GET /hello?name=kim HTTP/1.1". 스킴(http), 호스트(localhost), 포트(8080)는 첫 줄에 들어가지 않습니다. 호스트와 포트는 연결에 쓰였고, 요청의 Host 헤더에도 적혀 갑니다. 이 실험에서 서버는 Host 줄을 "Host: localhost:8080" 그대로 받았습니다.
  9. 서버가 첫 줄의 경로를 봅니다. /hello는 만들어 둔 주소라서 200 응답을 만듭니다. 본문의 이름은 쿼리에서 읽습니다. name의 값이 kim이라 "안녕하세요, kim"입니다. 이 서버 코드는 화면에 보이지 않지만, 그렇게 답하도록 만들어 두었습니다.
  10. 브라우저가 200 응답을 받습니다. 본문은 "안녕하세요, kim"입니다.

실행 결과

코드

http://localhost:8080/hello?name=kim

이 모형의 전제

서버는 Node.js 24.18.0의 http 모듈로 만든 것 하나로, GET /hello에 200과 본문 "안녕하세요, <name 값>"(name이 없으면 "안녕하세요, 손님")을, 그 밖의 경로에는 404를 돌려줍니다. 스물네 가지 조합을 모두 실행해 대조했습니다. URL을 부분으로 나눈 결과(스킴, 호스트, 포트, 경로, 쿼리)는 Node.js의 new URL(글자)이 낸 값입니다. 이 해석기는 브라우저와 같은 URL 표준(WHATWG URL)을 따릅니다. 요청은 Node.js의 http.request에 그 URL을 주어 보냈고, 서버가 받은 요청의 첫 줄과 Host 줄은 글자 그대로 기록했습니다. 화면의 8080은 예시입니다. 포트를 적은 조합은 비어 있는 포트에 서버를 띄우고 URL의 8080 자리에 그 번호를 넣어 localhost라는 이름으로 실제로 연결했고, 서버가 받은 Host 줄의 번호는 8080으로 바꿔 대조했습니다. 같은 URL 글자(8080 그대로)를 연결만 시험 서버로 돌려 보내, 클라이언트가 연결하려던 곳도 기록했습니다(8080번이었습니다). 포트를 생략한 조합은 80번에 실제로 연결하지 않았습니다. 연결을 가로채 클라이언트가 연결하려던 곳이 localhost의 80번이라는 것과, 그때 보내려던 요청의 첫 줄과 Host 줄("Host: localhost", 포트 번호 없음)만 기록했습니다. 그래서 응답 칸의 "80번 포트에 서버가 없으면 연결할 수 없음"은 이 PC의 80번을 확인한 것이 아니라 연결하려던 곳이 80번이라는 사실에서 나온 조건문이고, 이 PC의 80번 포트 사정은 보지 않았습니다. 스킴을 지운 조합은 실제 결과를 따랐습니다. localhost:8080/hello처럼 콜론(:)이 있으면 new URL이 localhost를 스킴으로 읽고 호스트와 포트는 비워 둔 채 8080/hello를 경로로 읽었고, localhost/hello처럼 콜론(:)이 없으면 Invalid URL 오류(ERR_INVALID_URL)였습니다. 앞의 경우 http.request는 ERR_INVALID_PROTOCOL로, fetch는 unknown scheme으로 요청을 보내지 못했습니다(연결이 일어나지 않는 경우라 실제로 시도해 보았습니다). 이 PC에서 localhost는 ::1과 127.0.0.1 두 주소로 풀렸고(::1이 먼저), 시험 서버는 127.0.0.1에서만 열었는데도 Node.js의 기본 설정(여러 주소를 차례로 시도)에서는 127.0.0.1로 연결되었습니다. 그 시도를 끈 설정에서는 ::1에서 연결이 거절되었습니다. 브라우저가 어떻게 하는지는 보지 않았습니다. 문서로 확인한 것은 RFC 3986의 3절(URI = scheme ":" hier-part [ "?" query ], scheme, authority, path, query의 구분), 3.1절(스킴), 3.2.3절(스킴이 기본 포트를 정할 수 있고 포트가 비면 생략), 3.3절(경로), 3.4절(쿼리는 첫 ?부터 #이나 끝까지), RFC 9110의 4.2.1절(http에서 포트가 비었거나 없으면 TCP 80번이 기본), 7.2절(Host는 대상 URI의 호스트와 포트를 알림), RFC 9112의 3.2.1절(origin-form은 경로와 쿼리만 요청 대상으로 보내고, 경로가 비면 /를 보내야 한다), WHATWG URL 표준의 4.4절(스킴을 찾지 못하고 기준 주소가 없으면 실패, 규칙 있는 스킴 뒤에 //가 없으면 나머지를 경로로 읽음, 기본 포트와 같은 포트는 비워 둠)입니다. 문서는 요약으로만 열려 원문 전체를 대조하지는 못했습니다. 브라우저의 주소창은 http://를 적지 않아도 알아서 붙여 주는 편의 기능이 있는데, 이 실습은 URL 자체의 규칙만 다뤘고 주소창의 동작은 실행해 확인하지 않았습니다. 이 실습의 클라이언트는 Node.js이고 실제 브라우저로는 실행하지 않았습니다. 프래그먼트(#)와 퍼센트 인코딩, 상대 URL 같은 세부 규칙은 "세부 동작 실습: URL의 구조"에서 다룹니다.

앞 실습: TCP 연결 · 다음 실습: DNS

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요