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 분해가 모두 브라우저 자체 내장 함수를 써서 사용자의 기기에서 돕니다. 붙여넣은 것이 그것을 떠나지 않습니다.