Założę się, że miałeś już bug, gdzie URL ze spacjami lub znakami specjalnymi coś zepsuł. Może & w wyszukiwaniu spowodował dziwne zachowanie. Albo widziałeś %20 w URL-u i zastanawiałeś się, o co chodzi. Wyjaśnijmy to wszystko raz na zawsze.

Dlaczego URL-e wymagają kodowania

URL-e mogą zawierać tylko ograniczony zestaw znaków. Specyfikacja RFC 3986 definiuje znaki „niezarezerwowane", które są zawsze bezpieczne: A-Z, a-z, 0-9, -, _, . i ~. Wszystko inne — spacje, &, =, ?, znaki spoza ASCII jak ñ czy 日本語 — musi być zakodowane procentowo.

Jak działa kodowanie procentowe

To proste: bierzesz wartość bajtów UTF-8 znaku i przedstawiasz każdy bajt jako % z dwoma cyframi szesnastkowymi.

Przykłady:

  • Spacja → %20
  • &%26
  • =%3D
  • é%C3%A9 (dwa bajty w UTF-8)
  • %E6%97%A5 (trzy bajty w UTF-8)

Więc Hello World w ścieżce URL zamienia się w Hello%20World.

Najczęstszy błąd: podwójne kodowanie

To najczęstszy błąd kodowania URL, jaki widzę, i jest podstępny. Kodujesz ciąg znaków, przekazujesz go do funkcji, która koduje ponownie. Teraz %20 (już zakodowana spacja) zamienia się w %2520, bo sam % zostaje zakodowany.

Przykład problemu:

javascript

Rozwiązanie: koduj wartości raz, na właściwym poziomie. Nie koduj tego, co jest już zakodowane.

Znak plus vs. %20 — Tak, to jest mylące

W ciągach zapytań URL spacje mogą być + lub %20. Konwencja + pochodzi z kodowania formularzy HTML (application/x-www-form-urlencoded). W ścieżkach URL tylko %20 jest prawidłowe.

Więc https://example.com/hello world → ścieżka koduje się do https://example.com/hello%20world

Ale https://example.com/search?q=hello world → zapytanie może być ?q=hello+world lub ?q=hello%20world

encodeURI vs. encodeURIComponent

JavaScript daje ci dwie funkcje, a użycie niewłaściwej to klasyczny błąd:

  • encodeURI() — Koduje cały URL. Pozostawia :, /, ?, #, &, = bez zmian, bo są strukturalnymi częściami URL-a.
  • encodeURIComponent() — Koduje składnik URL (np. wartość parametru zapytania). KODUJE :, /, ?, #, &, =, bo mogą być częścią danych.

Zasada: używaj encodeURIComponent() dla wartości, nigdy dla całych URL-i. Dokumentacja MDN świetnie tłumaczy tę różnicę.

Inne języki programowania

  • Python: urllib.parse.quote() dla ścieżek, urllib.parse.urlencode() dla ciągów zapytań — dokumentacja tutaj
  • Java: URLEncoder.encode() (używa + dla spacji — uwaga!)
  • PHP: urlencode() dla ciągów zapytań, rawurlencode() dla ścieżek

Szybkie wskazówki

  • Zawsze koduj wartości parametrów, nigdy całych URL-i
  • Po stronie odbiorczej dekoduj tylko raz — nie przepuszczaj zakodowanych wartości przez wiele warstw
  • Testuj z przypadkami granicznymi: spacje, &, =, #, znaki spoza ASCII i emoji

Bezpieczne budowanie URL-i w JavaScript

Prawidłowy sposób konstruowania URL-i z parametrami zapytań to użycie wbudowanych API URL i URLSearchParams. Zajmują się one całym kodowaniem za ciebie:

javascript

Zwróć uwagę, jak URLSearchParams używa + dla spacji (konwencja kodowania formularzy HTML) i prawidłowo koduje & w wartości, żeby nie pomylić go z & oddzielającym parametry. To o wiele bezpieczniejsze niż łączenie stringów.

Typowe pułapki kodowania URL

1. Kodowanie całego URL-a zamiast samych wartości. Jeśli uruchomisz encodeURIComponent() na pełnym URL-u, zakoduje znaki ://, /, ? i & — czyniąc URL całkowicie bezużytecznym. Koduj tylko poszczególne wartości parametrów.

2. Zapominanie o kodowaniu fragmentów hash. Znak # w URL-u rozpoczyna identyfikator fragmentu. Jeśli twoje dane zawierają # i go nie zakodujesz, wszystko po nim znika z perspektywy serwera. Serwer nigdy nie widzi identyfikatorów fragmentów — działają one tylko po stronie klienta.

3. Nieobsługiwanie międzynarodowych nazw domen (IDN). Nazwy domen ze znakami spoza ASCII, jak example.日本, wymagają specjalnej obsługi zwanej kodowaniem Punycode. Jest to niezależne od kodowania procentowego i dotyczy tylko części hostname URL-a.

4. Niespójne kodowanie spacji. Niektóre części twojego systemu mogą kodować spacje jako +, a inne jako %20. Zwykle to działa dobrze przy dekodowaniu, ale może powodować problemy z porównywaniem URL-i i cache'owaniem. Wybierz jedną konwencję i trzymaj się jej.

Szybka ściągawka kodowania procentowego

ZnakZakodowanyDlaczego wymaga kodowania
Spacja%20 lub +Niedozwolony w URL-ach
&%26Oddziela parametry zapytania
=%3DOddziela klucz od wartości
?%3FRozpoczyna ciąg zapytania
#%23Rozpoczyna identyfikator fragmentu
%%25Sam znak ucieczki
/%2FSeparator ścieżki
@%40Używany w sekcji userinfo

Kodowanie URL w różnych kontekstach

Kodowanie URL to nie tylko URL-e przeglądarki. Spotkasz się z nim w tych typowych sytuacjach:

  • Żądania API: Parametry zapytań w wywołaniach REST API wymagają prawidłowego kodowania, zwłaszcza jeśli zawierają dane od użytkownika
  • URL-e przekierowań: Gdy przekazujesz URL powrotny jako parametr (np. ?redirect=https://...), cały URL przekierowania musi być zakodowany jako wartość
  • Przepływy OAuth: URL-e callbacków OAuth i parametry state są szczególnie podstępne, bo obejmują wiele warstw kodowania URL
  • Deep linki: Mobilne deep linki stosują te same zasady kodowania URL, ale niektóre platformy mają dodatkowe wymagania

Debugowanie problemów z kodowaniem URL

Gdy coś idzie nie tak z kodowaniem URL, oto moja lista kontrolna:

1. Sprawdź podwójne kodowanie — Szukaj %25 w URL-u, co oznacza, że % został zakodowany dwukrotnie

2. Sprawdź surowe żądanie — Użyj zakładki Network w DevTools przeglądarki, aby zobaczyć dokładnie jaki URL został wysłany, zanim przeglądarka wyświetli zdekodowaną wersję

3. Porównaj zakodowane i zdekodowane — Wklej problematyczny URL do dekodera, aby zobaczyć, co faktycznie zawiera

4. Sprawdź dekodowanie po stronie serwera — Niektóre frameworki automatycznie dekodują parametry URL, a ręczne dekodowanie na dodatek powoduje problemy

Anatomia URL-a

Dobra, cofnijmy się o krok. Zanim zagłębimy się bardziej w kodowanie, musisz naprawdę zrozumieć, z czego składa się URL. Wiem, wiem — używasz URL-i przez całą swoją karierę. Ale założę się, że nie znasz wszystkich części po ich oficjalnych nazwach. Większość programistów ich nie zna, i od tego zaczyna się zamieszanie z kodowaniem.

Według RFC 3986 URL ma taką strukturę:

plaintext

Omówmy każdy element:

  • Scheme (https, ftp, mailto) — Protokół. Nie wymaga kodowania, to zawsze litery ASCII.
  • Authority — Obejmuje opcjonalne user:password@ (którego w 2026 roku praktycznie nigdy nie powinieneś używać), host (nazwa domeny lub IP) i opcjonalny numer portu.
  • Path (/search/results) — Część hierarchiczna. Ukośniki / oddzielają segmenty. W każdym segmencie musisz kodować znaki specjalne, ale NIE kodujesz samych ukośników.
  • Query (?q=hello&lang=en) — Pary klucz-wartość po ?. & oddziela pary, = oddziela klucze od wartości. Kodujesz klucze i wartości, ale nie strukturalne & i =.
  • Fragment (#section-2) — Część po #. Ta jest ciekawa — nigdy nie jest wysyłana do serwera. Jest czysto kliencka. Ale nadal musisz kodować znaki specjalne wewnątrz niej.

Oto co myli ludzi: różne części URL-a mają różne zasady kodowania. / jest zupełnie w porządku w ścieżce (to separator!), ale musi być zakodowany jako %2F, jeśli pojawia się w wartości parametru zapytania. Znak @ jest w porządku w sekcji authority, ale powinien być zakodowany w ścieżce. Dlatego uniwersalne funkcje kodujące powodują tyle błędów.

Pomyśl o tym tak: URL to zdanie z zasadami gramatyki. Znaki specjalne to interpunkcja. Nie kodujesz przecinka, który naprawdę pełni rolę przecinka — kodujesz tylko przecinek, który jest częścią danych i mógłby zostać pomylony z interpunkcją.

encodeURI vs. encodeURIComponent: Pole minowe JavaScript

Dobra, ten temat doprowadza mnie do szału, bo widzę, jak programiści mylą się CAŁY CZAS. JavaScript daje ci dwie funkcje kodujące, brzmią prawie identycznie, ale użycie niewłaściwej zrujnuje ci dzień.

Wyjaśnijmy to jasno:

encodeURI() jest zaprojektowana do kodowania pełnego URL-a. Koduje niebezpieczne znaki, ale zostawia strukturalne w spokoju — rzeczy jak :, /, ?, #, &, =, @. Bo jeśli kodujesz pełny URL, oczywiście nie chcesz zniszczyć struktury URL-a.

encodeURIComponent() jest zaprojektowana do kodowania pojedynczej wartości, która trafia do URL-a. Koduje WSZYSTKO oprócz liter, cyfr i - _ . ~. To obejmuje :, /, ?, #, &, = — bo gdy te znaki pojawiają się w wartości, są danymi, nie strukturą.

Oto porównanie, które wszystko wyjaśnia:

ZnakencodeURI()encodeURIComponent()
:: (bez zmian)%3A
// (bez zmian)%2F
?? (bez zmian)%3F
## (bez zmian)%23
&& (bez zmian)%26
== (bez zmian)%3D
@@ (bez zmian)%40
Spacja%20%20
é%C3%A9%C3%A9

A teraz robi się niebezpiecznie. Zobacz, co się dzieje, gdy użyjesz niewłaściwej:

javascript

A odwrotny błąd jest równie zły:

javascript

Złota zasada: encodeURIComponent dla wartości, encodeURI dla pełnych URL-i. Albo jeszcze lepiej, po prostu użyj API URL i pozwól przeglądarce to obsłużyć. Serio, klasy URL i URLSearchParams istnieją nie bez powodu. Sprawdź dokumentację MDN encodeURIComponent i dokumentację MDN encodeURI po pełne szczegóły.

Kodowanie URL w różnych językach programowania

JavaScript nie jest jedynym językiem z mylącymi funkcjami kodowania URL. Każdy język ma swoje dziwactwa, i nie żartuję, niektóre są jeszcze bardziej mylące niż para z JavaScript.

Python — Właściwie dość rozsądny, gdy znajdziesz odpowiedni moduł:

python

Java — Tutaj robi się dziwnie. URLEncoder został zaprojektowany do kodowania formularzy HTML, nie do ogólnego kodowania URL. Więc spacje stają się + zamiast %20:

java

C# — .NET faktycznie daje ci odpowiednie narzędzia, ale jest jakieś pięć różnych metod i każda robi nieco co innego:

csharp

PHP — Ach PHP. Oczywiście masz dwie funkcje o prawie identycznych nazwach, które robią nieco różne rzeczy:

php

Go — Czysty i rozsądny, jak to Go ma w zwyczaju:

go

Wniosek? Każdy język obsługuje temat + vs. %20 inaczej, i każdy język ma co najmniej jedną funkcję, która cię zaskoczy. Zawsze sprawdzaj dokumentację języka, z którym pracujesz. Nie zakładaj, że działa tak samo jak w JavaScript.

Unicode w URL-ach: Dziki Zachód

OK, teraz robi się NAPRAWDĘ ciekawie. Co się dzieje, gdy umieścisz znaki spoza ASCII w URL-u? Na przykład, co jeśli chcesz URL z japońskim, arabskim albo — nie żartuję — emoji?

Odpowiedź obejmuje dwa kompletnie różne systemy, a pomylenie ich to klasyczny błąd.

Dla ścieżki i zapytania: Używasz kodowania procentowego. Bierzesz bajty UTF-8 znaku i kodujesz każdy. Więc café staje się caf%C3%A9. Twoja przeglądarka robi to automatycznie i zwykle pokazuje ci ładną wersję w pasku adresu, ale pod spodem wysyła wersję zakodowaną procentowo.

Dla nazw domen: Kodowanie procentowe NIE jest używane. Zamiast tego istnieje ten szalony system zwany Punycode. Konwertuje on nazwy domen Unicode na ciągi kompatybilne z ASCII, zaczynające się od xn--.

Spójrz na to:

  • café.comxn--caf-dma.com
  • münchen.dexn--mnchen-3ya.de
  • 例え.jpxn--r8jz45g.jp

Dlaczego dwa różne systemy? Bo DNS (system zamieniający nazwy domen na adresy IP) został zbudowany w latach 80. i obsługuje tylko ASCII. Musieli więc wymyślić sposób na upakowanie Unicode w ciągi ASCII — i Punycode jest tym wynalazkiem. Ścieżki i zapytania URL-i natomiast są obsługiwane przez serwery web, które radzą sobie z bajtami zakodowanymi procentowo.

Istnieje nawet cała specyfikacja dla Unicode w URL-ach o nazwie IRI (Internationalized Resource Identifiers) zdefiniowana w RFC 3987. IRI to w zasadzie URL, który pozwala bezpośrednio na znaki Unicode. Przeglądarki konwertują IRI na URI za kulisami.

I tak, domeny z emoji istnieją. 💩.la to prawdziwa domena (albo była nią w pewnym momencie). Koduje się przez Punycode do xn--ls8h.la. Nie polecam używania domen emoji do czegoś poważnego, ale to zabawny dowód, że system działa.

Jedna pułapka z Unicode w URL-ach: różne reprezentacje Unicode tego „samego" znaku. Na przykład é może być reprezentowane jako pojedynczy codepoint (U+00E9) lub jako e + łączący znak akcentu (U+0065 + U+0301). Te tworzą różne ciągi zakodowane procentowo! Standard URL WHATWG zaleca normalizację NFC, ale nie wszystkie systemy stosują ją konsekwentnie.

Podwójne kodowanie: Bug, który nawiedza twoje sny

Wspomniałem o podwójnym kodowaniu wcześniej, ale zasługuje na osobne głębokie omówienie, bo ten bug prawdopodobnie zmarnował więcej zbiorowych godzin programistów niż jakikolwiek inny problem związany z URL-ami. Sam przez to przechodziłem — wielokrotnie.

Oto podstawowy scenariusz:

plaintext

Co się stało? Gdy kodujesz hello%20world po raz drugi, znak % zostaje zakodowany do %25. Więc %20 staje się %2520. Serwer widzi literalny ciąg %20 zamiast spacji.

Brzmi to oczywiste, gdy tak to opisuję, ale w prawdziwych kodach źródłowych jest niesamowicie podstępne. Oto scenariusze, w których podwójne kodowanie cię ugryzie:

Łańcuchy proxy. Wysyłasz żądanie do serwera A, który przekazuje je do serwera B. Jeśli oba serwery kodują URL, bum — podwójne kodowanie. Bramy API jak Kong, AWS API Gateway czy reverse proxy nginx to częsti winowajcy.

Łańcuchy przekierowań. Użytkownik idzie na stronę A, zostaje przekierowany na stronę B z parametrem ?returnUrl=..., a strona B przekierowuje ponownie z URL-em powrotnym jako parametrem. Każde przekierowanie może ponownie zakodować URL. Po trzech przekierowaniach twój URL jest potrójnie zakodowany i kompletnie zniszczony.

"Pomocniki" frameworków. Niektóre frameworki webowe automatycznie kodują parametry URL. Jeśli ręcznie zakodujesz przed przekazaniem do frameworka, dostaniesz podwójne kodowanie. Widziałem to przy Spring Boot, middleware Express.js i Django URL reversing.

Jak wykryć podwójne kodowanie? Szukaj %25 w URL-u. To znak procentu, który został zakodowany, co zwykle oznacza, że coś zostało zakodowane dwukrotnie. Jeśli widzisz %2520, to podwójnie zakodowana spacja. Jeśli widzisz %253D, to podwójnie zakodowany znak =.

Jak to naprawić:

javascript

Najlepsza obrona to ustanowienie jasnych granic w kodzie: koduj na krawędziach (tuż przed wysłaniem żądania HTTP lub tuż przed skonstruowaniem URL-a do wyświetlenia), a wewnętrznie przekazuj wszędzie surowe, niezakodowane ciągi.

Limity długości URL-i i co z nimi zrobić

Oto coś, co cię zaskoczy: sama specyfikacja HTTP NIE definiuje maksymalnej długości URL-a. RFC 3986 mówi, że URL-e powinny mieć „nieograniczoną długość". Ale rzeczywistość się z tym nie zgadza.

Różne komponenty w łańcuchu mają swoje własne limity, a najkrótszy wygrywa:

KomponentMaksymalna długość URL
Internet Explorer (RIP)2 083 znaki
Chrome, Firefox, Safari~65 000+ znaków
Apache (domyślnie)8 190 znaków
Nginx (domyślnie)8 192 znaki
IIS (domyślnie)16 384 znaki
AWS ALB8 192 znaki
Cloudflare32 768 znaków

Ten stary limit 2083 znaków z IE kiedyś rządził wszystkim. Chociaż IE jest już w zasadzie martwy, niektórzy programiści i narzędzia wciąż traktują go jak ewangelię. Ale w praktyce większość nowoczesnych stosów poradzi sobie z dużo dłuższymi URL-ami.

Mimo to, to że MOŻESZ zrobić URL na 65 000 znaków, nie znaczy, że POWINIENEŚ. Oto kilka prawdziwych powodów, by trzymać URL-e krótkie:

  • Logi serwera. Wiele systemów logowania obcina długie URL-e, co zamienia debugowanie w koszmar.
  • Kopiuj-wklej. Użytkownicy kopiują i udostępniają URL-e. Super-długie URL-e psują się w mailach, wiadomościach czatu i dokumentach.
  • SEO. Wyszukiwarki generalnie zalecają trzymanie URL-i poniżej 2000 znaków.
  • Cache. Niektóre limity kluczy cache CDN i proxy opierają się na URL-ach. Dłuższe URL-e = więcej trafień w pustą.

Co więc zrobić, gdy twój URL robi się za długi? Cieszę się, że pytasz:

1. Użyj POST zamiast GET. Jeśli wysyłasz dużo danych, umieść je w ciele żądania. Ciało żądania nie ma praktycznego limitu rozmiaru. To najczęstsze rozwiązanie dla złożonych formularzy wyszukiwania z wieloma filtrami.

2. Użyj skracaczy URL-i lub identyfikatorów referencyjnych. Wygeneruj krótki token, który mapuje się na pełny zestaw parametrów przechowywanych po stronie serwera. Więc zamiast /search?filter1=abc&filter2=def&filter3=... dostajesz /search/saved/abc123.

3. Kompresuj swoje parametry. Niektóre aplikacje kodują w base64 skompresowany blob JSON i umieszczają go w URL-u. Nieładne, ale działa. Zobaczysz to w narzędziach jak URL-e dashboardów Grafana.

Kodowanie formularzy: application/x-www-form-urlencoded

Porozmawiajmy o słoniu w pokoju, który sprawia, że kodowanie URL jest jeszcze bardziej mylące: kodowanie formularzy HTML. Gdy przesyłasz formularz HTML z method="POST", przeglądarka koduje dane formularza w formacie zwanym application/x-www-form-urlencoded. I ten format jest PRAWIE taki sam jak standardowe kodowanie procentowe, ale z jedną kluczową różnicą, która doprowadza wszystkich do szału.

Spacje stają się + zamiast %20.

To tyle. To główna różnica. Ale, o rany, ile to powoduje zamieszania.

plaintext

Dlaczego ta różnica istnieje? Oczywiście powody historyczne. Specyfikacja kodowania formularzy HTML jest starsza od specyfikacji kodowania URL, a używała + dla spacji, bo... no cóż, komuś w latach 90. wydawało się, że ładniej wygląda. I teraz utknęliśmy z tym na zawsze.

Gdy przesyłasz formularz HTML, przeglądarka wysyła nagłówek Content-Type: application/x-www-form-urlencoded, a serwer wie, że + należy interpretować jako spacje. Ale jeśli budujesz URL-e ręcznie w JavaScript i używasz + dla spacji w części ścieżki, serwer zobaczy dosłowne znaki plus. Wesołe czasy.

Gdzie to ma znaczenie w praktyce:

javascript

A potem jest multipart/form-data, co jest zupełnie inną bestią. Gdy twój formularz zawiera przesyłanie plików (), przeglądarka przełącza się na kodowanie multipart/form-data, które używa granic do oddzielania pól zamiast znaków &. Nie używa w ogóle kodowania URL — każda część ma własne nagłówki i ciało. Jeśli kiedykolwiek próbowałeś ręcznie parsować żądanie multipart, znasz ten ból.

Praktyczna rada: używaj URLSearchParams dla ciągów zapytań i danych formularzy. Używaj FormData do przesyłania plików. Nie próbuj kodować tych rzeczy ręcznie, chyba że naprawdę, naprawdę musisz.

Wypróbuj sam

Pracujesz z URL-ami, które wyglądają źle? Wklej je do naszego Dekodera URL, aby zobaczyć prawdziwe znaki. Musisz zakodować wartość przed umieszczeniem jej w URL-u? Koder URL załatwi to natychmiast. A do rozbijania złożonych URL-i na komponenty użyj Parsera URL, aby wyraźnie zobaczyć każdą część.