URL 인코딩이란?
URL 인코딩(퍼센트 인코딩, Percent-Encoding)은 URL에서 특수한 의미를 가지거나 안전하지 않은 문자를 % 뒤에 16진수 코드로 변환하는 방식입니다. RFC 3986으로 표준화되어 있으며, 웹 브라우저, 서버, API 클라이언트가 URL을 올바르게 해석하기 위해 필수적인 과정입니다.
예를 들어, 공백은 %20으로, 한글 "가"는 UTF-8로 3바이트이므로 %EA%B0%80으로 변환됩니다. URL의 구조적 문자(://, /, ?, &, =)는 각각 고유한 역할을 가지고 있어, 이 문자들이 데이터 값에 포함될 경우 인코딩이 필요합니다.
URL 구조와 인코딩 범위
URL은 여러 구성 요소로 나뉘며, 각 부분에 적용되는 인코딩 규칙이 다릅니다. https://example.com/path/to/page?key=value&name=홍길동#section에서 스킴(https), 호스트(example.com), 경로(/path/to/page), 쿼리(?key=value), 프래그먼트(#section)는 각각 허용되는 문자 집합이 다릅니다.
RFC 3986에서 인코딩 없이 사용할 수 있는 문자(Unreserved Characters)는 영문 대소문자(A-Z, a-z), 숫자(0-9), 하이픈(-), 밑줄(_), 마침표(.), 물결표(~)입니다. 이 외의 문자는 URL의 어느 위치에서든 퍼센트 인코딩이 필요합니다.
JavaScript의 인코딩 함수 비교
encodeURI() vs encodeURIComponent()
encodeURI()는 전체 URL을 인코딩할 때 사용합니다. URL의 구조적 문자(:, /, ?, #, &, = 등)는 변환하지 않고, 비구조적 특수 문자(공백, 한글 등)만 인코딩합니다. 이미 완성된 URL에서 비ASCII 문자만 인코딩할 때 적합합니다.
encodeURIComponent()는 URL의 개별 구성 요소(쿼리 파라미터 값, 경로 세그먼트)를 인코딩할 때 사용합니다. 구조적 문자를 포함한 거의 모든 특수 문자를 인코딩합니다. 쿼리 파라미터 값에 &나 =이 포함된 경우 이 함수를 사용하지 않으면 URL 파싱이 깨집니다.
실무에서는 대부분 encodeURIComponent()를 사용하여 개별 값을 인코딩한 뒤 URL을 조립합니다. 전체 URL을 한 번에 encodeURIComponent()로 인코딩하면 ://과 /까지 인코딩되어 URL이 작동하지 않으므로 주의해야 합니다.
escape() — 사용하지 마세요
escape()는 더 이상 사용하지 않는(deprecated) 레거시 함수입니다. UTF-8이 아닌 UCS-2 인코딩을 사용하며, @, *, / 등을 인코딩하지 않는 비표준 동작을 합니다. 기존 코드에서 escape()를 발견하면 encodeURIComponent()로 교체해야 합니다.
자주 발생하는 문제
이중 인코딩 (Double Encoding)
이미 인코딩된 URL을 다시 인코딩하면 % 자체가 %25로 변환됩니다. 예를 들어, "홍길동"이 %ED%99%8D%EA%B8%B8%EB%8F%99으로 1차 인코딩되고, 이를 다시 인코딩하면 %25ED%2599%258D...이 되어 서버에서 올바르게 디코딩할 수 없습니다.
이중 인코딩은 프레임워크가 자동으로 인코딩하는 것을 모르고 수동으로 다시 인코딩할 때 주로 발생합니다. axios, fetch 등의 HTTP 클라이언트는 쿼리 파라미터를 자동으로 인코딩하므로, 이미 인코딩된 값을 전달하면 이중 인코딩이 됩니다. 디버깅 시 Network 탭에서 실제 전송된 URL을 확인하여 %25가 포함되어 있는지 점검하세요.
공백 인코딩: %20 vs +
URL 경로에서 공백은 %20으로 인코딩하는 것이 RFC 3986 표준입니다. 반면 HTML 폼 데이터(application/x-www-form-urlencoded)에서는 공백을 +로 인코딩합니다. 이 두 방식을 혼용하면 서버에서 "검색어+추가"를 "검색어 추가"로 해석해야 하는지, 문자 그대로의 "+"인지 구분할 수 없게 됩니다.
JavaScript의 encodeURIComponent()는 공백을 %20으로 인코딩합니다. URLSearchParams는 공백을 +로 인코딩합니다. API 서버의 기대 형식에 따라 적절한 방식을 선택해야 합니다.
다국어 URL (한글, 일본어 등)
한글은 UTF-8로 글자당 3바이트를 차지하므로, 인코딩하면 한 글자가 %XX%XX%XX(9자)로 확장됩니다. 주소창에서는 브라우저가 자동으로 디코딩하여 표시하므로 사용자에게는 한글로 보이지만, 실제 HTTP 요청에는 인코딩된 형태로 전송됩니다.
IRI(Internationalized Resource Identifier, RFC 3987)는 URL에서 유니코드 문자를 직접 허용하는 표준이지만, 실제 HTTP 전송에서는 여전히 퍼센트 인코딩으로 변환됩니다. 서버 로그에서 %EA%B0%80 같은 시퀀스를 볼 때 당황하지 말고, UTF-8 퍼센트 인코딩임을 인식하면 됩니다.
예약 문자 인코딩 누락
쿼리 파라미터 값에 &, =, # 같은 예약 문자가 포함된 경우, 인코딩하지 않으면 URL 파싱이 깨집니다. 예를 들어, 검색어가 "A&B"인데 인코딩 없이 ?q=A&B로 전송하면, 서버는 q=A와 별도 파라미터 B로 해석합니다. ?q=A%26B로 인코딩해야 올바르게 전달됩니다.
서버 측 인코딩/디코딩
Node.js에서는 encodeURIComponent()와 decodeURIComponent()를 사용합니다. querystring 모듈(레거시)이나 URLSearchParams 클래스를 사용하면 쿼리 문자열을 자동으로 파싱/생성할 수 있습니다.
Python에서는 urllib.parse.quote()와 urllib.parse.unquote()를 사용합니다. quote()의 기본 safe 파라미터는 /를 인코딩하지 않으므로, 쿼리 값을 인코딩할 때는 quote(value, safe='')로 명시해야 합니다.
Java에서는 URLEncoder.encode()를 사용하되, 이 함수가 공백을 +로 인코딩하는 점에 주의해야 합니다. %20이 필요한 경우 결과에서 +를 %20으로 치환하거나, Java 11+의 URI 클래스를 사용하세요.
디버깅 팁
URL 인코딩 문제를 디버깅할 때 가장 효과적인 방법은 브라우저 개발자 도구의 Network 탭에서 실제 전송된 URL을 확인하는 것입니다. Request URL을 복사하여 URL 디코더에 붙여넣으면 각 파라미터가 올바르게 인코딩되었는지 빠르게 확인할 수 있습니다.
API 응답에서 이상한 문자가 보이면, 응답 본문이 이중 인코딩되었을 가능성을 확인하세요. 디코딩을 한 번 더 수행하여 정상적인 텍스트가 나오면 이중 인코딩 문제입니다. 서버 측 미들웨어나 프록시가 불필요하게 인코딩을 추가하는 경우가 흔합니다.