URL 인코딩과 디코딩, percent encoding 이해하기
공백이 %20이 될 때와 +가 될 때의 차이, 이중 인코딩 문제, URL 전체를 인코딩하면 안 되는 이유를 정리했습니다.
URL에 아무 문자나 넣을 수 없는 이유
URL 규격은 사용할 수 있는 문자를 제한합니다. 영문자와 숫자, 그리고 하이픈, 밑줄, 마침표, 물결표 정도만 그대로 쓸 수 있습니다.
제한이 있는 이유는 두 가지입니다. 첫째, URL은 주소창뿐 아니라 이메일 본문, 로그 파일, 프로토콜 헤더 등 다양한 환경을 거치는데 이 모든 곳에서 안전하게 취급되는 문자 집합이 좁습니다. 둘째, ?, &, =, #, / 같은 문자는 URL 구조를 나누는 구분자로 이미 의미가 정해져 있습니다.
값 안에 이런 구분자가 들어가면 주소가 잘못 해석됩니다. 예를 들어 검색어가 a&b 인 경우 인코딩 없이 q=a&b 로 보내면 서버는 q라는 값 a와 b라는 별도 파라미터로 읽습니다.
percent-encoding의 동작 방식
percent-encoding은 문자를 바이트로 나눈 뒤 각 바이트를 퍼센트 기호와 16진수 두 자리로 표현합니다. 공백은 %20, 물음표는 %3F 가 됩니다.
중요한 점은 문자가 아니라 바이트를 인코딩한다는 것입니다. 한글은 UTF-8에서 글자당 3바이트를 차지하므로 한 글자가 %XX 세 덩어리로 표현됩니다. 검색이라는 두 글자가 %EA%B2%80%EC%83%89 여섯 덩어리가 되는 이유입니다.
그래서 원본 문자열의 인코딩이 UTF-8인지 EUC-KR인지에 따라 결과가 완전히 달라집니다. 오래된 시스템과 연동할 때 한글이 깨지는 문제는 대부분 여기에서 시작됩니다.
%20과 + 의 차이
공백을 인코딩하는 방식이 두 가지라는 점이 실무에서 가장 자주 혼란을 일으킵니다.
RFC 3986을 따르는 일반적인 URL 인코딩에서는 공백이 %20이 됩니다. JavaScript의 encodeURIComponent가 이 방식입니다. 반면 HTML 폼이 데이터를 전송할 때 쓰는 application/x-www-form-urlencoded 형식에서는 공백을 + 로 표현합니다.
문제는 두 방식이 같은 URL 안에서 섞여 쓰인다는 것입니다. 경로 부분은 %20을 쓰고 쿼리스트링은 + 를 쓰는 서버도 있습니다. 값에 + 기호 자체가 들어가야 한다면 반드시 %2B로 인코딩해야 합니다. 그러지 않으면 받는 쪽에서 공백으로 해석해 버립니다.
URL 전체를 인코딩하면 안 된다
흔한 실수 중 하나가 완성된 URL 전체를 인코딩하는 것입니다. 그러면 https:// 의 슬래시가 %2F로 바뀌고 파라미터를 구분하는 & 가 %26이 되어 주소 자체가 망가집니다.
인코딩은 파라미터 값 하나하나에만 적용해야 합니다. 즉 URL을 조립하기 전에 각 값을 인코딩하고, 그 다음에 ?, &, = 로 이어 붙이는 순서입니다.
예외는 URL 자체를 다른 URL의 파라미터로 넘길 때입니다. 로그인 후 돌아갈 주소를 redirect_uri 파라미터로 전달하는 경우가 대표적인데, 이때는 넘길 URL 전체가 하나의 값이므로 통째로 인코딩하는 것이 맞습니다.
이중 인코딩 문제
이미 인코딩된 문자열을 한 번 더 인코딩하면 퍼센트 기호 자체가 %25로 바뀝니다. %ED%95%9C 이 %25ED%2595%259C 이 되는 식입니다.
이 문제는 여러 계층을 거치는 시스템에서 자주 발생합니다. 프런트엔드에서 인코딩한 값을 프레임워크가 한 번 더 인코딩하거나, 프록시를 거치면서 다시 처리되는 경우입니다. 증상은 값이 이상하게 길어지고 %25 가 눈에 띄는 것입니다.
해결하려면 어느 계층에서 인코딩이 일어나는지 파악해 중복을 제거해야 합니다. 임시로는 디코딩을 두 번 해서 원본을 확인할 수 있지만, 근본적으로는 인코딩 지점을 한 곳으로 정리하는 것이 맞습니다.