URI·URL·URN 차이: Query·Fragment와 HTTP Safe Method까지

반응형

URI, URL, URN은 단순히 “상위 개념과 하위 개념” 표 하나로 끝내기 어렵다. RFC 3986은 URI를 resource를 식별하는 문자열로 정의하고, URI가 locator, name, 또는 둘의 성격을 함께 가질 수 있다고 설명한다. URL과 URN은 그 식별 방식과 역사적 용어를 이해하기 위한 분류다.

또 하나 먼저 바로잡을 점이 있다. active parameter와 passive parameter는 URI나 HTTP 표준 용어가 아니다. server 상태를 바꾸는지는 query parameter 이름이 아니라 HTTP method와 resource가 정의한 semantics가 결정한다.

URI 구조부터 읽기

다음 URI를 나눠 보자.

https://example.com:8443/products/42?view=compact&lang=ko#reviews
Component 역할
scheme https 식별자를 해석할 protocol·scheme
authority example.com:8443 host와 optional port
path /products/42 hierarchical resource path
query view=compact&lang=ko scheme·application이 해석하는 query data
fragment reviews representation 안의 secondary resource나 view

RFC 3986의 generic syntax는 다음 형태다.

URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

모든 scheme이 HTTP URL처럼 생기지는 않는다. 예를 들어 mailto:urn:도 각 scheme 규칙에 따라 URI를 만든다.

URI, URL, URN의 관계

URI

resource를 식별하는 전체 개념이다. 식별 대상이 항상 network에서 가져올 수 있는 file일 필요는 없다. 사람, 책의 판본, API resource, abstract concept도 scheme이 정의한 범위에서 식별할 수 있다.

URL

URI 중 resource의 primary access mechanism, 즉 어디에 있고 어떻게 접근할지를 제공하는 locator 성격의 식별자다. web에서 사용하는 https://...가 대표적이다. 현대 browser와 JavaScript의 실제 URL parsing은 WHATWG URL Standard도 함께 봐야 한다.

URN

역사적으로 location이 바뀌어도 유지할 persistent name과 urn scheme의 URI를 가리키는 데 쓰였다.

urn:isbn:9780131103627

URI를 URL과 URN이라는 서로 겹치지 않는 두 상자에 반드시 넣어야 하는 것은 아니다. RFC 3986은 scheme 자체보다 실제 identifier가 name과 locator 중 어떤 성격을 갖는지를 본다.

Query와 Fragment는 Server에서 같게 보이지 않는다

HTTP URL의 query는 request target에 포함되어 server와 intermediary가 볼 수 있다. filtering, sorting, pagination, search condition을 표현할 때 흔히 사용한다.

https://example.com/products?category=book&page=2

fragment는 browser가 representation의 특정 부분을 가리키는 데 사용하며 HTTP request target에는 포함되지 않는다.

https://example.com/docs#installation

single-page application이 fragment routing을 쓰더라도 # 뒤 값은 일반 HTTP request로 origin server에 전달되지 않는다. JavaScript가 읽고 별도 request를 보낼 수는 있다.

Query Parameter가 상태 변경을 결정하지 않는다

다음과 같은 endpoint는 만들 수는 있어도 HTTP semantics를 어긴 위험한 설계다.

GET /users/123?action=delete

GET은 safe method로 정의된다. crawler, link preview, prefetcher, cache가 GET을 자동 수행할 수 있으므로 요청된 동작으로 server 상태를 바꾸면 안 된다. 삭제는 적절한 authorization과 CSRF 방어를 갖춘 unsafe method로 표현한다.

DELETE /users/123 HTTP/1.1
Host: example.com

“조회 요청도 access log를 남기니 상태가 바뀐다”는 반론은 safe semantics와 충돌하지 않는다. HTTP의 safe·idempotent 성질은 사용자가 요청한 intended effect를 기준으로 한다. logging이나 metric 같은 부수 효과는 있을 수 있다.

Method Safe Idempotent 일반적 의도
GET Yes Yes representation 조회
HEAD Yes Yes body 없이 metadata 조회
POST No No로 가정 processing·subordinate resource 생성
PUT No Yes target resource를 요청 representation으로 생성·대체
DELETE No Yes target resource 제거 요청
PATCH No 표준상 자동 보장 안 됨 부분 변경

idempotent는 response가 매번 같다는 뜻이 아니다. 같은 request를 여러 번 보냈을 때 server에 의도한 효과가 한 번 보낸 것과 같다는 성질이다.

Query String을 설계할 때 놓치기 쉬운 것

Encoding

space와 한글, reserved character를 문자열 치환으로 직접 붙이지 않는다. platform의 URL builder를 사용한다.

const url = new URL("https://example.com/search");
url.searchParams.set("query", "분산 시스템");
url.searchParams.set("page", "2");
console.log(url.toString());

Duplicate와 순서

tag=python&tag=linux처럼 같은 key가 반복될 수 있다. framework가 첫 값, 마지막 값, list 중 무엇으로 해석하는지 확인한다. signature나 cache key에서는 parameter sorting·normalization 규칙을 양쪽이 정확히 공유해야 한다.

Secret 노출

password, access token, session identifier를 URL에 넣지 않는다. URL은 browser history, reverse proxy, analytics, Referer, screenshot에 남기 쉽다. secret은 protocol이 정한 authorization header나 secure cookie 등 알맞은 channel을 사용한다.

SEO와 Canonical URL

tracking parameter와 sort option으로 사실상 같은 content의 URL이 늘어나면 crawling과 indexing 신호가 분산될 수 있다. canonical URL, redirect, internal link policy를 일관되게 설계하되 서로 다른 content까지 하나로 합치지 않는다.

HTTP request와 header의 기본 흐름은 Spring MVC 공부의 HTTP 기본, 실제 URL로 network path를 확인하는 방법은 Network Troubleshooting 명령어에서 이어갈 수 있다.

자주 묻는 질문

모든 URL은 URI인가

RFC 3986 용어에서는 resource를 식별하고 access mechanism을 제공하는 URL은 URI의 locator 성격을 가진다. 다만 browser API의 URL은 WHATWG parsing model을 따르므로 문맥을 구분한다.

Fragment는 Server log에 남나

일반 HTTP request target에는 #fragment가 포함되지 않는다. client-side code가 fragment 값을 별도 request parameter로 보내면 그 두 번째 request에는 남을 수 있다.

GET query parameter로 삭제하면 왜 위험한가

GET은 safe하다고 가정돼 crawler와 prefetcher가 자동 호출할 수 있다. 상태 변경 endpoint를 GET으로 만들면 사용자가 의도하지 않은 실행과 CSRF 위험을 키운다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글