URL 인코더 / 디코더
URL 퍼센트 인코딩과 디코딩. 대상을 구성 요소나 URL 전체로 고를 수 있고 URL 분해도 보여 줍니다.
쿼리 매개변수 하나 같은 단일 값용입니다. / ? : @ & = +도 이스케이프합니다. 거기서는 이 문자들이 구조가 아니라 데이터이기 때문입니다.
결과가 여기에 표시됩니다
퍼센트 인코딩은 무엇을 위한 것인가
URL에는 작은 문자 집합만 담을 수 있습니다: A–Z와 a–z 글자, 숫자, - . _ ~ 같은 한 줌의 기호, 그리고 주소에 모양을 주는 예약 문자 집합 — / ? # & = : @ 등 몇 개입니다. 그 밖은 공백에서 한글 단어, 이모지까지 간접적으로 나타내야 합니다. 퍼센트 인코딩이 그 나타냄입니다: 문자를 그 UTF-8 바이트로 바꾸고 각 바이트를 퍼센트 기호에 이어지는 두 자리 16진수로 씁니다. 공백은 %20이, 가는 %EA%B0%80이 됩니다.
예약 문자가 흥미로운 경우입니다. URL에서 합법이지만 구조적인 무언가를 뜻합니다: ?는 쿼리를 시작하고, &는 매개변수를 나누고, =는 키를 값에서 나눕니다. 그 문자 중 하나가 구조의 일부가 아니라 당신의 데이터의 일부일 때 그것은 인코딩되어야 합니다 — 아니면 반대편의 파서가 그것을 구두점으로 읽어 당신의 값을 잘못된 곳에서 나눕니다.
구성 요소냐 URL 전체냐 — 링크를 깨뜨리는 구별
이것이 URL 버그의 단일로 가장 흔한 근원이고, 그래서 이 도구는 추측하는 대신 범위를 고르라고 청합니다.
- 구성 요소 범위는 구분자도 이스케이프합니다: / ? : @ & = + $ , 그리고 #입니다. 더 큰 URL 안에 놓이는 한 조각의 데이터 — 검색어, 리다이렉트 대상, 토큰 — 에 쓰세요.
- URL 전체 범위는 그 구분자를 그대로 두고 정말로 불법인 것 — 공백이나 비ASCII 텍스트 같은 — 만 이스케이프합니다. 이미 올바르게 구조화되어 그저 정리만 필요한 완전한 주소가 있을 때 쓰세요.
어느 방향이든 거꾸로 하면 무언가가 깨집니다. URL 전체를 구성 요소 범위로 인코딩하면 모든 슬래시와 물음표가 %2F와 %3F가 되어, 길고 쓸 수 없는 한 문자열이 나옵니다. 단일 값을 URL 전체 범위로 인코딩하면 그 안의 앰퍼샌드가 구분자로 살아남아, "cats & dogs" 검색이 조용히 두 매개변수가 되고 값의 뒷부분이 사라집니다.
// 값이 구분자를 담고 있으므로 이스케이프되어야 합니다:
const q = 'cats & dogs'
`/search?q=${encodeURIComponent(q)}` // /search?q=cats%20%26%20dogs
`/search?q=${encodeURI(q)}` // /search?q=cats%20&%20dogs ✗
// 더 나은 것은 플랫폼이 조립하게 두는 것입니다:
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'cats & dogs')그 마지막 방식은 습관으로 들일 만합니다. URL과 URLSearchParams는 모든 브라우저와 Node에 내장되어 있고, 각 부분에 올바른 인코딩을 적용해 줍니다 — 매번 손으로 맞히게 하는 대신 결정을 통째로 없앱니다.
공백이 때로는 +이고 때로는 %20인 이유
두 다른 사양이 일하고 있고, 정확히 한 문자에 대해 어긋납니다. URL 일반을 다스리는 RFC 3986은 공백을 %20으로 인코딩합니다. 오래된 application/x-www-form-urlencoded 형식 — HTML 폼이 제출하는 것이자 그래서 대부분의 쿼리 문자열이 그렇게 보이는 것 — 은 공백을 +로 인코딩합니다.
함정은 decodeURIComponent가 첫 규칙만 구현한다는 것입니다. hello+world를 주면 hello+world가 돌아오고, 플러스까지 그대로 — 오류도 경고도 없이 그저 미묘하게 잘못된 값입니다. 그래서 이 도구는 "+ 를 공백으로 처리" 옵션을 제공하고, 그 옵션이 꺼진 채 입력에 플러스가 들었을 때 짚어 줍니다.
도구 아래의 분해 표를 움직이는 URLSearchParams는 폼 규칙을 따라 +를 공백으로 바꿈에 유의하세요. 그래서 같은 쿼리 문자열이 어느 API에 손을 뻗느냐에 따라 다르게 디코딩될 수 있습니다 — 일부러, 그리고 두 경우 모두 옳게.
기억할 만한 한 결과: 플러스가 정말로 당신의 데이터의 일부라면 — 전화번호, 필터 식 — 그것은 %2B로 인코딩되어야 합니다. 쿼리 문자열의 문자 그대로의 +는 잘해야 모호하고 대부분의 파서에 공백으로 읽힙니다.
URL 읽기
절대 URL을 붙여넣으면 이 도구가 그것을 각 부분으로 나눕니다. 각각이 뭐라 불리는지 알아 둘 만합니다. 오류 메시지와 문서가 그 어휘를 전제하기 때문입니다:
- 스킴 — https, mailto, ftp. 콜론 앞의 모든 것이자, 나머지가 어떻게 해석될지 정하는 것입니다.
- 사용자 정보 — 호스트 앞의 선택적 user:password. 여전히 합법이고 여전히 나쁜 생각입니다: 모든 요청과 함께 오가 로그, 브라우저 기록, 리퍼러 헤더에 떨어집니다.
- 호스트 — 도메인 이름이나 IP 주소. 그 뒤의 모든 것은 네트워크가 아니라 그 서버가 다룹니다.
- 포트 — 대개 없습니다. https의 443과 http의 80이 암묵이기 때문입니다.
- 오리진 — 스킴, 호스트, 포트를 합친 것. 이것이 브라우저 보안이 세워진 단위입니다: 동일 출처 정책, CORS, 쿠키 범위가 모두 경로가 아니라 오리진을 비교합니다.
- 경로 — 호스트 뒤, 어떤 ? 나 # 앞의 부분.
- 쿼리 — ? 뒤의, &로 나뉜 키/값 쌍.
- 프래그먼트 — # 뒤의 모든 것. 독특하게도 이것은 결코 서버로 보내지지 않습니다. 전적으로 브라우저가 다룹니다.
프래그먼트 세부는 들리는 것보다 중요합니다. 결코 브라우저를 떠나지 않으므로 # 뒤에 놓은 것은 서버 로그에 보이지 않습니다 — 그래서 일부 단일 페이지 앱이 역사적으로 그것을 라우팅에 썼고, 결코 도착하지 않은 요청을 디버깅할 때 볼 곳이 아닌 이유입니다.
인코딩이 하지 않는 것에 대한 마지막 주의. 퍼센트 인코딩은 텍스트를 URL 안에서 나르기에 안전하게 합니다. 그것은 정화가 아니고 보안 통제가 아닙니다. 값을 인코딩해도 HTML, SQL, 셸 명령에 끼워 넣기에 안전해지지 않고, 신뢰할 수 없는 입력을 디코딩하면 인코딩 형태가 숨기던 문자 — 경로 구분자, 널 바이트 — 가 드러날 수 있습니다. 값이 무엇인지를, 그것이 어떻게 오가는지의 인코딩과 따로 검증하세요.
입력한 주소가 늘 보내지는 주소는 아니다
URL이 어디로 가기 전에 정규화되고, 그 다시 쓰기는 조용합니다. 이 도구는 입력과 다를 때마다 그 결과를 보입니다. 그 문자열 — 입력한 것이 아니라 — 이 서버에 닿고 로그에 나타나기 때문입니다:
HTTPS://Example.COM:443\a\b 입력 https://example.com/a/b 보냄
거기서 네 개의 별개 규칙이 발동했습니다. 스킴과 호스트는 소문자가 됩니다. 어느 것도 대소문자를 구분하지 않기 때문입니다. 포트는 443이 https의 기본이라 빠집니다. 백슬래시는 앞 슬래시가 되는데, Windows 형식 경로를 쓰는 사람을 놀라게 하는 호환 규칙입니다. 그리고 빈 경로는 단일 슬래시가 되었을 것입니다. 이 중 어느 것도 오류가 아니지만, 두 URL의 같음을 비교하거나 허용 목록에 맞추고 있다면 정규화된 형태를 비교해야 하고, 아니면 잘못된 답을 얻습니다.
비ASCII 문자를 가진 호스트 이름도 다시 쓰여, xn--로 시작하는 ASCII 형태 — 퓨니코드라 불립니다 — 가 됩니다. 이 도구는 두 방향을 보입니다: 읽을 수 있는 이름과 실제로 오가는 것입니다. 이것은 건너뛰지 말고 볼 만합니다. 두 다른 유니코드 이름이 화면에서 동일해 보일 수 있고 — 라틴 "a"와 키릴 "а"는 다른 문자입니다 — 완전히 다른 퓨니코드를 내기 때문입니다. ASCII 형태를 비교하는 것이 그런 쌍을 가르는 유일하게 미더운 방법입니다.
결코 정규화되지 않는 것 하나: 반복된 쿼리 매개변수입니다. ?tag=a&tag=b라 쓰는 것은 완전히 합법이고, 표준은 그것이 무엇을 뜻하는지 말하지 않아, 모든 스택이 저마다의 답을 골랐습니다. PHP는 마지막 값을 지키고, Express는 배열로 모으고, 많은 프레임워크와 URLSearchParams.get은 첫 번째를 취합니다. 이 도구는 주석 없이 두 번 나열하는 대신 반복된 키를 표시합니다. 그 결과의 버그 — 한 서비스에서 되고 다른 데서 사라지는 값 — 는 읽어서 알아채기 정말 어렵기 때문입니다.
자주 묻는 질문
- 구성 요소 범위와 URL 전체 범위의 차이는 무엇인가요?
- 구성 요소 범위는 구분자 / ? : @ & = +도 이스케이프하고, 이는 URL 안에 놓이는 단일 값에 옳습니다. URL 전체 범위는 그것들을 그대로 둡니다. 거기서 그것들이 주소의 부분을 나누기 때문입니다. URL 전체에 구성 요소 범위를 쓰면 모든 슬래시가 %2F가 되어 쓸 수 없는 한 문자열이 됩니다.
- 디코딩한 텍스트에 아직 +가 있는 이유는 무엇인가요?
- decodeURIComponent가 RFC 3986을 따르고, 거기서 +는 그저 플러스이기 때문입니다. 폼 제출과 대부분의 쿼리 문자열은 폼 인코딩을 쓰고, 거기서 +는 공백을 뜻합니다. 텍스트가 그 둘 중 하나에서 왔으면 "+ 를 공백으로 처리"를 켜세요.
- 정말로 플러스인 플러스를 어떻게 인코딩하나요?
- %2B로 쓰세요. 쿼리 문자열의 문자 그대로의 +는 대부분의 파서에 공백으로 읽히므로, 정말로 당신의 데이터의 일부인 플러스 — 예를 들어 전화번호 안 — 는 이스케이프되어야 합니다.
- URL 전체를 인코딩해야 하나요, 아니면 부분만 해야 하나요?
- 부분만, 그리고 되도록 손으로 하지 마세요: URL과 URLSearchParams로 주소를 조립하면 각 구성 요소에 올바른 인코딩이 적용됩니다. 조립한 URL을 나중에 인코딩하는 것이 대부분의 이중 인코딩 버그의 근원입니다.
- 분해가 제가 붙여넣은 것과 다른 URL을 보이는 이유는 무엇인가요?
- 그것이 보내지는 것이기 때문입니다. URL은 쓰기 전에 정규화됩니다: 스킴과 호스트는 소문자가 되고, 기본 포트는 빠지고, 백슬래시는 슬래시가 되고, 빈 경로는 슬래시가 되고, 비ASCII 호스트는 퓨니코드가 됩니다. URL을 비교하거나 허용 목록에 맞춘다면 맨 텍스트가 아니라 이 정규화된 형태를 비교하세요.
- example.com/path에 분해가 나타나지 않는 이유는 무엇인가요?
- 절대 URL이 아니기 때문입니다 — 스킴이 없어 식별할 호스트가 없습니다. 이 도구는 대신 추측해 주지 않습니다: example.com:8080은 이미 스킴이 example.com이고 경로가 8080인 유효한 절대 URL이라, 조용히 https://를 앞에 붙이면 그럴듯해 보이면서 잘못된 분해가 나올 수 있습니다. 직접 스킴을 더하면 부분이 나타납니다.
- 이중 인코딩이란 무엇인가요?
- 이미 인코딩된 값을 인코딩해 %20이 %2520이 되는 것 — 퍼센트 기호 자체가 이스케이프됩니다. 대개 페이지에 문자 그대로의 %20 나열이 나타나는 형태로 드러납니다. 한 번 디코딩해 아직 이스케이프가 보이는지 확인하세요. 보이면 두 번 인코딩된 것입니다.
- 값을 인코딩하면 안전해지나요?
- 아니요. 퍼센트 인코딩은 안전이 아니라 전송에 관한 것입니다. 인코딩된 값은 여전히 그것이 무엇이든 그대로이고, HTML, SQL, 셸에 닿기 전에 같은 검증과 같은 문맥에 맞는 이스케이프가 필요합니다.
- 프래그먼트가 서버로 보내지지 않는 이유는 무엇인가요?
- 설계에 따른 것입니다: # 뒤의 모든 것은 브라우저 혼자 다루고 결코 요청에 나타나지 않습니다. 그래서 서버 로그에서 볼 수 없고, 역사적으로 클라이언트 쪽 라우팅에 쓰인 이유입니다.
- 제가 붙여넣은 것이 어딘가로 전송되나요?
- 아니요. 인코딩, 디코딩, URL 분해가 모두 브라우저 자체 내장 함수를 써서 사용자의 기기에서 돕니다. 붙여넣은 것이 그것을 떠나지 않습니다.