서비스를 운영하다 보면 “이 자원은 어디에 있고 무엇을 가리키며 어떻게 식별되는가?”가 항상 문제의 시작점이다. URL은 단순한 문자열이 아니라 리소스 식별자이자 라우팅, 캐시, 보안, SEO까지 건드리는 핵심 인터페이스다. 이번 장을 읽고 URL을 “문자열”이 아니라 규칙과 의미를 가진 주소 체계로 바라보게 됐다.
Accept/Accept-Language 등에 따라 표현이 달라질 수 있다.scheme://userinfo@host:port/path?query#fragment
http, https, ws, wss, mailto, file, data…)userinfo@host:port (대부분 host[:port]만 사용, 기본 포트는 생략)예시
https://api.moomoo-gift.com:443/v1/products?category=lighting&minPrice=20000#reviews
#fragment는 클라이언트 전용이므로 서버 라우팅·권한·로그 설계에 영향을 주지 않는다. 서버에서 fragment에 의존하는 설계는 지양한다.
절대 URL은 스킴부터 호스트까지 모두 포함해 어디서 참조하든 동일한 의미를 가진다. 반면 상대 URL은 현재 문서의 베이스 URL을 기준으로 해석되며 브라우저는 베이스와 상대 경로를 결합한 뒤 ./.. 같은 점 세그먼트를 제거하고 정규화한다. 베이스가 <base href>로 명시되지 않았다면 현재 문서의 URL이 베이스가 된다. 운영 관점에서는 정적 리소스 경로를 상대 URL로 남발하면 배포 경로 변경 시 오류가 발생하기 쉬우므로 API 호출, 정적 리소스 CDN 경로는 절대 URL을 권장한다.
예시
베이스: https://example.com/a/b/c.html
상대: ../img/logo.png
결과: https://example.com/a/img/logo.png
경로에는 현재 디렉터리를 뜻하는 .과 상위 디렉터리를 뜻하는 ..가 포함될 수 있다. 서버와 프록시는 접근 제어, 캐시 판단 전에 반드시 이 점 세그먼트를 제거해 정규화된 경로로 판단해야 한다. 예를 들어 /a/b/../c/./d는 /a/c/d로 해석된다. 이 과정을 생략하면 /../../etc/passwd 같은 경로 트래버설 우회가 가능해질 수 있고 캐시 키가 달라져 동일 리소스가 중복 저장되는 비효율도 생긴다.
URL에는 예약 문자(/ ? # & = 등)와 비 ASCII 문자를 그대로 넣을 수 없다. 일반적으로 UTF-8로 바이트 변환 후 %HH 형태로 퍼센트 인코딩한다. 경로의 공백은 %20이 표준적이며 +는 주로 쿼리에서 application/x-www-form-urlencoded일 때 공백으로 해석된다. 인코딩은 한 번만 수행해야 하며(%25가 다시 인코딩되어 %2525가 되는 이중 인코딩 방지) 서버/클라이언트/프록시가 같은 규칙을 적용하도록 일관성을 유지해야 한다.
% 자체인 %25를 또 인코딩해 %2525가 되면 같은 리소스가 다른 URL처럼 보이는 문제가 생긴다(캐시 미스/우회 취약점 유발). 입력, 리라이트, 로그 중 어느 단계에서 인코딩/디코딩을 수행했는지 한 번만 어디서 할지를 팀 규칙으로 고정하자.
예시
/search/%ED%99%88%ED%8E%98%EC%9D%B4%EC%A7%80 # "홈페이지"
?q=wireless+mouse # query 공백=+
도메인에 한글·일본어 같은 유니코드 문자를 쓸 수 있지만 전송 시에는 Punycode로 변환된 ASCII 레이블(xn--…)이 사용된다. 경로·쿼리의 한글은 UTF-8로 인코딩 후 퍼센트 인코딩하여 보낸다. 현대 브라우저는 IRI(Internationalized Resource Identifier)를 표시, 입력에서 지원하지만 전송, 로그, 서버 측 처리 단계에서는 ASCII 기반 URL로 변환된다는 점을 전제로 로깅/모니터링 체계를 설계해야 한다.
동일 리소스를 가리키더라도 표기가 다르면 캐시 미스와 SEO 중복 이슈가 생긴다. 운영 정책으로 호스트 소문자화, 기본 포트 생략(HTTP 80, HTTPS 443), 트레일링 슬래시 규칙 통일, 쿼리 파라미터의 무의미한 순서/중복 제거 같은 규범화를 정해 캐시 키 계산과 링크 생성에 일관 적용한다. 검색 엔진에는 rel="canonical"을 통해 대표 URL을 명시하고 서버에서는 비선호 URL을 301 리다이렉트로 수렴시켜 신호를 모아준다.
HTTP://EXAMPLE.COM:80/Products/ → https://example.com/products
(스킴 고정, 호스트 소문자화, 기본 포트 제거, 경로 정책 적용 → 캐시/SEO 신호 수렴)
/products, /orders/{id}/categories/{id}/products/products?category=lighting&minPrice=20000GET/POST/PUT/PATCH/DELETE/products/123 + Accept: application/jsonURL은 히스토리, 리퍼러, 로그에 남는다. 따라서 토큰, 세션ID, 개인정보 같은 민감정보를 쿼리에 넣지 말고 가능하면 헤더나 바디를 사용한다. http://user:pass@host와 같은 URL 내 자격증명은 금지한다. 파일 다운로드 링크는 서명 URL(HMAC) + 만료시간을 사용하고 redirect 파라미터는 허용 도메인 화이트리스트나 서명 검증으로 오픈 리다이렉트를 차단한다. 경로는 정규화 후 접근 제어를 수행하여 경로 트래버설을 막고 URL 디코딩을 중복으로 수행해 우회가 생기지 않도록 입력 검증 순서를 명확히 한다. 민감 쿼리 파라미터 노출 가능성이 있는 페이지는 Referrer-Policy: strict-origin-when-cross-origin으로 리퍼러에 쿼리를 싣지 않도록 한다.
URL은 문자열이 아니라 식별, 전송, 표시의 규칙이 겹겹이 얽힌 인터페이스다. 같은 리소스를 일관되게 가리키려면 정규화와 규범화가 선행되어야 하고 캐시, SEO, 보안을 모두 고려해 경로/쿼리/프래그먼트의 역할을 분리해야 한다. 실무에서는 “베이스 URL + 절대 경로”, “정규화 후 권한 체크”, “민감정보 쿼리 금지”, “301로 대표 URL 수렴” 같은 체크리스트만 지켜도 많은 장애와 중복비용을 예방할 수 있음을 깨달았다.