Back to blog
Nov 06, 2023
5 min read

URL과 리소스 탐색

URL 문법과 리소스 식별 방법

URL(Uniform Resource Locator)은 인터넷 상의 리소스 위치를 나타내는 표준화된 주소 체계다. 전 세계 어디서나 동일한 방식으로 리소스를 찾을 수 있게 해준다. 사용자가 브라우저 주소창에 입력하는 것부터, 백엔드 API를 호출하는 것까지 모두 URL을 기반으로 동작한다.

URL의 구조

URL은 여러 구성 요소로 이루어져 있으며, 각 부분은 특정한 역할을 한다.

https://user:pass@example.com:443/path/to/resource?key=value&foo=bar#section
└─┬─┘  └───┬───┘ └────┬─────┘└┬┘└───────┬────────┘└──────┬───────┘└───┬──┘
scheme  사용자정보    host    port      path            query        fragment
  • Scheme: 리소스에 접근하기 위해 사용할 프로토콜을 지정한다. http, https, ftp, mailto, file 등이 있다. 예를들어 https://는 HTTP over SSL/TLS를 의미하며, 암호화된 통신을 사용한다. ftp://는 파일 전송 프로토콜이다.

  • 사용자 정보(User Info): 리소스 접근에 필요한 인증 정보를 포함한다. user:password 형식으로 작성하지만, 보안상 이유로 현대 웹에서는 거의 사용되지 않는다. URL에 비밀번호를 노출하는 것은 위험하므로, Authorization 헤더나 쿠키를 사용하는 것이 권장된다.

  • Host: 리소스를 제공하는 서버의 위치를 나타내며, 도메인 이름(example.com)이나 IP 주소(192.168.1.1, [2001:db8::1])로 지정할 수 있다. 도메인 이름은 DNS를 통해 IP 주소로 변환된다.

  • Port: 서버에서 실행 중인 특정 프로세스를 식별한다. HTTP는 기본적으로 80번, HTTPS는 443번 포트를 사용한다. 기본 포트를 사용하는 경우 생략할 수 있다. https://example.com:8080처럼 비표준 포트를 사용할 때는 명시해야 한다.

  • Path: 서버 내에서 리소스의 위치를 나타낸다. 슬래시(/)로 구분된 계층 구조를 가진다.

  • Query String: 리소스에 전달할 추가 파라미터를 포함한다. ?로 시작하고, key=value 쌍이 &로 연결된다. /search?q=http&lang=ko는 검색어와 언어를 파라미터로 전달한다. RESTful API에서 필터링, 정렬, 페이지네이션에 사용된다.

  • Fragment: 리소스 내의 특정 위치를 가리킨다. #으로 시작하며, 서버로 전송되지 않고 클라이언트에서만 사용된다. #section2는 페이지 내 특정 섹션으로 스크롤하고, 싱글 페이지 애플리케이션에서는 라우팅에 활용된다.

상대 URL과 절대 URL

절대 URL은 리소스 위치를 완전하게 표현한다. 모든 정보를 포함하므로, 어떤 컨텍스트에서든 동일한 리소스를 가리킨다.

https://example.com/api/users/123

상대 URL은 기준 URL을 기반으로 리소스 위치를 표현한다. HTML 문서나 API 응답에서 다른 리소스를 참조할 때 사용되며, 경로만 포함하거나 일부 구성 요소를 생략한다.

<!-- 현재 페이지: https://example.com/blog/post.html -->
<img src="../images/logo.png" />
<!-- https://example.com/images/logo.png -->
<a href="about.html">About</a>
<!-- https://example.com/blog/about.html -->
<a href="/contact">Contact</a>
<!-- https://example.com/contact -->

상대 URL은 도메인이 변경되어도 링크가 깨지지 않고, URL이 짧아져 HTML 크기가 줄어든다는 장점이 있다. 하지만 기준 URL에 따라 의미가 달라지므로 주의해야 한다.

URL 인코딩

URL은 ASCII 문자만 사용할 수 있다. 한글, 공백, 특수문자는 퍼센트 인코딩(Percent Encoding)으로 변환해야 한다.

알파벳(A-Z, a-z), 숫자(0-9), 일부 특수문자(-, _, ., ~) 같은 안전한 문자는 인코딩 없이 사용할 수 있다.

:, /, ?, #, &, = 등과 같은 예약된 문자는 URL 구조에서 특별한 의미를 가지며 문자 그대로 사용하려면 인코딩해야 한다.

인코딩 방식은 문자를 UTF-8 바이트로 변환한 뒤 각 바이트를 %XX 형식으로 표현한다.

원본: https://example.com/search?q=한글 검색
인코딩: https://example.com/search?q=%ED%95%9C%EA%B8%80%20%EA%B2%80%EC%83%89

?key=hello world?key=hello%20world 또는 ?key=hello+world가 된다. 대부분의 프로그래밍 언어는 URL 인코딩 함수를 제공한다. JavaScript에서는 다음과 같이 인코딩 함수를 제공한다.

// JavaScript
encodeURIComponent("한글 검색");
// "%ED%95%9C%EA%B8%80%20%EA%B2%80%EC%83%89"

URI, URL, URN의 차이

URI(Uniform Resource Identifier)는 리소스를 식별하는 가장 포괄적인 개념이다. URL과 URN을 모두 포함한다.

URL(Uniform Resource Locator)은 리소스의 위치를 나타낸다. https://example.com/users/123는 특정 서버의 특정 경로에 있는 리소스를 가리킨다.

URN(Uniform Resource Name)은 리소스의 이름을 나타낸다. 위치와 무관하게 리소스를 식별하며, URN은 리소스가 어디에 있든 동일하지만, 실제로 접근하려면 별도의 해결 메커니즘이 필요하다.

실무에서는 대부분 URL을 사용한다. URN은 특수한 도메인(도서관, 학술 자료)에서만 사용되며, 일반적으로 URI와 URL은 거의 같은 의미로 사용한다.

REST API에서의 URL 설계

RESTful API는 URL을 통해 리소스를 표현하고, HTTP 메서드로 행위를 나타낸다. API의 직관성과 유지보수성을 위해 좋은 URL 설계가 필요하다.

  1. 명사를 사용하여 리소스를 표현한다. 동사가 아닌 명사로 리소스를 나타내고, 행위는 HTTP 메서드로 표현한다.
좋은 예:
GET /users          # 사용자 목록 조회
POST /users         # 사용자 생성
GET /users/123      # 특정 사용자 조회
PUT /users/123      # 특정 사용자 수정
DELETE /users/123   # 특정 사용자 삭제

나쁜 예:
GET /getUsers
POST /createUser
GET /deleteUser?id=123
  1. 리소스 간의 관계를 계층 경로로 표현하여 직관적으로 만든다.
/users/123/posts           # 사용자 123의 게시글 목록
/users/123/posts/456       # 사용자 123의 게시글 456
/posts/456/comments        # 게시글 456의 댓글 목록
  1. 컬렉션은 복수형으로 표현하여 일관성을 유지한다. /users는 사용자 컬렉션, /users/123는 개별 사용자를 나타낸다.

  2. 쿼리 파라미터로 필터링과 정렬을 처리한다. 리소스의 부분집합을 요청할 때 쿼리 스트링을 사용한다.

/users?role=admin              # 관리자만 조회
/users?sort=created_at&order=desc  # 생성일 내림차순 정렬
/posts?page=2&limit=20         # 페이지네이션
  1. 소문자와 하이픈을 사용한다. URL은 대소문자를 구분하므로, 혼란을 피하기 위해 소문자를 사용한다. 단어 구분은 언더스코어(_)보다 하이픈(-)을 권장한다.
/user-profiles    # 권장
/user_profiles    # 가능하지만 비권장
/UserProfiles     # 비권장
  1. 버전을 명시한다. API 버전을 URL에 포함하여 하위 호환성을 관리한다.
/v1/users
/v2/users

URL과 보안

URL은 평문으로 전송되므로 보안에 주의해야 하며 민감한 정보를 URL에 포함하지 않아야한다. 비밀번호, API 키, 개인정보를 URL에 넣으면 노출될 수 있다. 민감한 정보는 최대한 요청 본문이나 헤더를 사용해야 한다.

나쁜 예:
POST /login?username=user&password=1234

GET /api/data?api_key=secret123

---

좋은 예:
POST /login
Content-Type: application/json
{"username": "user", "password": "1234"}

GET /api/data
Authorization: Bearer eyJhbGc...

HTTP는 URL을 포함한 모든 통신이 평문으로 전송되어 중간자 공격에 취약하다. HTTPS를 사용해 전체 통신을 암호화하여 URL도 보호하는게 좋다.

사용자 입력을 그대로 리다이렉트 URL로 사용하면 피싱 공격에 악용될 수 있어, 오픈 리다이렉트를 방지해야한다.

나쁜 예:
/redirect?url=http://malicious-site.com

좋은 예:
허용된 도메인 목록을 검증하거나,
내부 경로만 허용

HTTP 표준에는 제한이 없지만, 대부분의 서버는 8KB 정도로 제한한다. 긴 데이터는 POST 요청 본문을 사용해야 한다.

URL은 웹의 가장 기본적인 주소 체계로, 모든 HTTP 통신의 시작점이다. URL의 구조와 규칙을 정확히 이해하면 RESTful API를 효과적으로 설계하고, 보안 문제를 예방할 수 있다.