Arvaan — sinulla on ollut bugi, jossa välilyöntejä tai erikoismerkkejä sisältävä URL rikkoi jotain. Ehkä &-merkki hakukyselyssä aiheutti outoa käytöstä. Tai näit %20:n URL:ssa ja ihmettelit mitä se tarkoittaa. Selvitetään tämä kaikki.

Miksi URL:t tarvitsevat koodausta

URL:t voivat sisältää vain rajallisen joukon merkkejä. RFC 3986 -spesifikaatio määrittelee "varaamattomat" merkit, jotka ovat aina turvallisia: A-Z, a-z, 0-9, -, _, . ja ~. Kaikki muu — välilyönnit, &, =, ?, ei-ASCII-merkit kuten ñ tai 日本語 — täytyy prosenttikoodata.

Miten prosenttikoodaus toimii

Se on suoraviivaista: otetaan merkin UTF-8-tavuarvo ja esitetään jokainen tavu %-merkkinä ja kahtena heksadesimaalinumerona.

Esimerkkejä:

  • Välilyönti → %20
  • &%26
  • =%3D
  • é%C3%A9 (kaksi tavua UTF-8:ssa)
  • %E6%97%A5 (kolme tavua UTF-8:ssa)

Eli Hello World muuttuu URL-polussa muotoon Hello%20World.

Bugi numero 1: tuplakoodaus

Tämä on yleisin URL-koodausvirhe, jonka näen, ja se on salakavala. Koodaat merkkijonon, sitten välität sen funktiolle, joka koodaa sen uudelleen. Nyt %20:stä (jo koodattu välilyönti) tulee %2520, koska %-merkki itsessään koodataan.

Esimerkki ongelmasta:

javascript

Ratkaisu: koodaa arvot kerran, oikealla tasolla. Älä koodaa asioita, jotka on jo koodattu.

Plus-merkki vs. %20 — Kyllä, se on hämmentävää

URL-kyselymerkkijonoissa välilyönnit voivat olla joko + tai %20. +-käytäntö tulee HTML-lomakekoodauksesta (application/x-www-form-urlencoded). URL-poluissa vain %20 on kelvollinen.

Eli https://example.com/hello world → polku koodataan muotoon https://example.com/hello%20world

Mutta https://example.com/search?q=hello world → kysely voi olla ?q=hello+world tai ?q=hello%20world

encodeURI vs. encodeURIComponent

JavaScript antaa sinulle kaksi funktiota, ja väärän käyttäminen on klassinen virhe:

  • encodeURI() — Koodaa koko URL:n. Jättää :, /, ?, #, &, = ennalleen, koska ne ovat URL:n rakenteellisia osia.
  • encodeURIComponent() — Koodaa URL-komponentin (kuten kyselyparametrin arvon). Koodaa MYÖS :, /, ?, #, &, =, koska ne voivat olla osa dataa.

Nyrkkisääntö: käytä encodeURIComponent()-funktiota arvoille, älä koskaan kokonaisille URL:ille. MDN-dokumentaatio selittää eron kauniisti.

Muut ohjelmointikielet

  • Python: urllib.parse.quote() poluille, urllib.parse.urlencode() kyselymerkkijonoille — dokumentaatio täällä
  • Java: URLEncoder.encode() (käyttää + välilyönneille — varo!)
  • PHP: urlencode() kyselymerkkijonoille, rawurlencode() poluille

Pikavinkit

  • Koodaa aina parametrien arvot, älä kokonaisia URL:ja
  • Dekoodaa vastaanottopäässä vain kerran — älä välitä koodattuja arvoja useiden kerrosten läpi
  • Testaa reunatapauksilla: välilyönnit, &, =, #, ei-ASCII-merkit ja emojit

URL:ien turvallinen rakentaminen JavaScriptissä

Oikea tapa rakentaa URL:ja kyselyparametreilla on käyttää sisäänrakennettuja URL- ja URLSearchParams-rajapintoja. Ne hoitavat kaiken koodauksen puolestasi:

javascript

Huomaa miten URLSearchParams käyttää +-merkkiä välilyönneille (HTML-lomakekoodauksen käytäntö) ja koodaa &-merkin arvossa oikein, jottei sitä sekoiteta parametreja erottavaan &-merkkiin. Tämä on paljon turvallisempaa kuin merkkijonojen yhdistely.

Yleiset URL-koodauksen sudenkuopat

1. Koko URL:n koodaaminen pelkkien arvojen sijaan. Jos ajat encodeURIComponent()-funktion koko URL:lle, se koodaa ://-, /-, ?- ja &-merkit — tehden URL:sta täysin käyttökelvottoman. Koodaa vain yksittäisten parametrien arvot.

2. Hash-fragmenttien koodaamisen unohtaminen. #-merkki URL:ssa aloittaa fragmentti-tunnisteen. Jos datasi sisältää #-merkin etkä koodaa sitä, kaikki sen jälkeen katoaa palvelimen näkökulmasta. Palvelin ei koskaan näe fragmentti-tunnisteita — ne ovat vain asiakaspuolella.

3. Kansainvälisten verkkotunnusten (IDN) käsittelemättä jättäminen. Verkkotunnukset, joissa on ei-ASCII-merkkejä kuten example.日本, tarvitsevat erityiskäsittelyn nimeltä Punycode-koodaus. Tämä on erillinen prosenttikoodauksesta ja koskee vain URL:n isäntänimiosaa.

4. Välilyöntien epäjohdonmukainen koodaaminen. Jotkut järjestelmäsi osat saattavat koodata välilyönnit +-merkkinä ja toiset %20:nä. Tämä toimii yleensä hyvin dekoodauksessa, mutta voi aiheuttaa ongelmia URL:ien vertailussa ja välimuistissa. Valitse yksi käytäntö ja pysy siinä.

Prosenttikoodauksen pikaopas

MerkkiKoodattuMiksi se tarvitsee koodauksen
Välilyönti%20 tai +Ei sallittu URL:issa
&%26Erottaa kyselyparametrit
=%3DErottaa avaimen arvosta
?%3FAloittaa kyselymerkkijonon
#%23Aloittaa fragmentti-tunnisteen
%%25Itse pakomerkkki
/%2FPolun erotin
@%40Käytetään userinfo-osiossa

URL-koodaus eri konteksteissa

URL-koodaus ei koske vain selaimen URL:ja. Törmäät siihen näissä yleisissä tilanteissa:

  • API-pyynnöt: REST API -kutsujen kyselyparametrit tarvitsevat oikean koodauksen, varsinkin jos ne sisältävät käyttäjän syötettä
  • Uudelleenohjaus-URL:t: Kun välität paluu-URL:n parametrina (kuten ?redirect=https://...), koko uudelleenohjaus-URL täytyy koodata arvona
  • OAuth-kulut: OAuth-callback-URL:t ja state-parametrit ovat tunnetusti hankalia, koska ne sisältävät useita URL-koodauksen kerroksia
  • Syvälinkit: Mobiilien syvälinkit noudattavat samoja URL-koodaussääntöjä, mutta joillain alustoilla on lisävaatimuksia

URL-koodausongelmien debuggaus

Kun jokin menee pieleen URL-koodauksessa, tässä on debuggauslistani:

1. Tarkista tuplakoodaus — Etsi %25 URL:sta, mikä tarkoittaa, että % on koodattu kahdesti

2. Tarkista raaka pyyntö — Käytä selaimesi DevTools-työkalujen Network-välilehteä nähdäksesi tarkalleen mikä URL lähetettiin, ennen kuin selain näyttää dekoodatun version

3. Vertaa koodattua ja dekoodattua — Liitä ongelmallinen URL dekooderiin nähdäksesi mitä se oikeasti sisältää

4. Tarkista palvelinpuolen dekoodaus — Jotkut kehykset dekoodaavat URL-parametrit automaattisesti, ja sen tekeminen manuaalisesti sen päälle aiheuttaa ongelmia

URL:n anatomia

OK, otetaan askel taaksepäin. Ennen kuin menemme syvemmälle koodaukseen, sinun täytyy oikeasti ymmärtää mistä URL koostuu. Tiedän, tiedän — olet käyttänyt URL:ja koko urasi ajan. Mutta lyön vetoa, ettet tiedä kaikkia osia niiden virallisilla nimillä. Useimmat kehittäjät eivät tiedä, ja siitä koodauksen sekaannus alkaa.

RFC 3986:n mukaan URL:lla on tämä rakenne:

plaintext

Käydään jokainen osa läpi:

  • Scheme (https, ftp, mailto) — Protokolla. Ei tarvitse koodausta, se on aina ASCII-kirjaimia.
  • Authority — Tämä sisältää valinnaisen user:password@:n (jota sinun ei pitäisi käyttää vuonna 2026), hostin (verkkotunnus tai IP) ja valinnaisen porttinumeron.
  • Path (/search/results) — Hierarkkinen osa. Kauttaviivat / erottavat segmentit. Jokaisen segmentin sisällä sinun täytyy koodata erikoismerkit, mutta ET koodaa itse kauttaviivoja.
  • Query (?q=hello&lang=en) — Avain-arvo-parit ?:n jälkeen. & erottaa parit, = erottaa avaimet arvoista. Koodaat avaimet ja arvot, mutta et rakenteellisia &- ja =-merkkejä.
  • Fragment (#section-2) — Osa #:n jälkeen. Tämä on mielenkiintoinen — sitä ei koskaan lähetetä palvelimelle. Se on puhtaasti asiakaspuolella. Mutta sinun täytyy silti koodata erikoismerkit sen sisällä.

Tässä se mikä hämmentää ihmisiä: URL:n eri osilla on eri koodaussäännöt. / on täysin hyväksytty polussa (se on erotin!), mutta täytyy koodata %2F:ksi, jos se esiintyy kyselyparametrin arvon sisällä. @-merkki on OK authority-osiossa, mutta pitäisi koodata polussa. Siksi yleiskäyttöiset koodausfunktiot aiheuttavat niin paljon bugeja.

Ajattele asiaa näin: URL on lause, jolla on kielioppisäännöt. Erikoismerkit ovat välimerkkejä. Et koodaisi pilkkua, jota käytetään oikeasti pilkkuna — koodaat vain pilkun, joka on osa dataa ja saattaisi sekoittua välimerkkiin.

encodeURI vs. encodeURIComponent: JavaScriptin miinakenttä

Selvä, tämä aihe ajaa minut hulluksi, koska näen kehittäjien mokailvan tämän JATKUVASTI. JavaScript antaa sinulle kaksi koodausfunktiota, ja ne kuulostavat lähes identtisiltä, mutta väärän käyttäminen pilaa päiväsi.

Selvitetään asia:

encodeURI() on suunniteltu koodaamaan koko URL. Se koodaa vaaralliset merkit mutta jättää rakenteelliset rauhaan — asiat kuten :, /, ?, #, &, =, @. Koska jos koodaat koko URL:n, et selvästikään halua rikkoa URL:n rakennetta.

encodeURIComponent() on suunniteltu koodaamaan yksittäinen arvo, joka menee URL:n sisälle. Se koodaa KAIKEN paitsi kirjaimet, numerot ja - _ . ~. Tämä sisältää :, /, ?, #, &, = — koska kun nämä esiintyvät arvossa, ne ovat dataa, eivät rakennetta.

Tässä vertailu, joka tekee asian kristallinkirkkaaksi:

MerkkiencodeURI()encodeURIComponent()
:: (ennallaan)%3A
// (ennallaan)%2F
?? (ennallaan)%3F
## (ennallaan)%23
&& (ennallaan)%26
== (ennallaan)%3D
@@ (ennallaan)%40
Välilyönti%20%20
é%C3%A9%C3%A9

Nyt tulee pelottavaksi. Katso mitä tapahtuu kun käytät väärää:

javascript

Ja päinvastainen virhe on yhtä paha:

javascript

Kultainen sääntö: encodeURIComponent arvoille, encodeURI kokonaisille URL:ille. Tai vielä parempi, käytä vain URL-rajapintaa ja anna selaimen hoitaa se. Vakavasti, URL- ja URLSearchParams-luokat ovat olemassa syystä. Katso MDN encodeURIComponent -dokumentaatio ja MDN encodeURI -dokumentaatio täydelliset tiedot.

URL-koodaus eri ohjelmointikielissä

JavaScript ei ole ainoa kieli, jossa on hämmentäviä URL-koodausfunktioita. Jokaisella kielellä on omat kummallisuutensa, enkä vitsaile, jotkut ovat jopa hämmentävämpiä kuin JavaScriptin pari.

Python — Itse asiassa aika järkevä, kun löydät oikean moduulin:

python

Java — Tässä se menee oudoksi. URLEncoder suunniteltiin HTML-lomakekoodaukseen, ei yleiseen URL-koodaukseen. Joten välilyönnit muuttuvat +:ksi %20:n sijaan:

java

C# — .NET antaa sinulle oikeasti oikeat työkalut, mutta siellä on noin viisi eri metodia ja ne kaikki tekevät hieman eri asioita:

csharp

PHP — Voi PHP. Tietenkin sinulla on kaksi funktiota lähes identtisillä nimillä, jotka tekevät hieman eri asioita:

php

Go — Siisti ja järkevä, kuten Go tapaa olla:

go

Johtopäätös? Jokainen kieli käsittelee + vs. %20 -asian eri tavalla, ja jokaisella kielellä on ainakin yksi funktio, joka yllättää sinut. Tarkista aina käyttämäsi kielen dokumentaatio. Älä oleta, että se toimii samalla tavalla kuin JavaScriptissä.

Unicode URL:issa: Villi länsi

OK, tässä se menee TODELLA mielenkiintoiseksi. Mitä tapahtuu kun laitat ei-ASCII-merkkejä URL:iin? Kuten, mitä jos haluat URL:n japanilaisilla, arabialaisilla tai — en vitsaile — emojilla?

Vastaus sisältää kaksi täysin erilaista järjestelmää, ja niiden sekoittaminen on klassinen virhe.

Polku- ja kyselyosille: Käytät prosenttikoodausta. Ota merkin UTF-8-tavut ja koodaa jokainen. Eli café muuttuu muotoon caf%C3%A9. Selaimesi tekee tämän automaattisesti ja näyttää yleensä kauniin version osoitepalkissa, mutta konepellin alla se lähettää prosenttikoodatun version.

Verkkotunnuksille: Prosenttikoodausta EI käytetä. Sen sijaan on tämä villi järjestelmä nimeltä Punycode. Se muuntaa Unicode-verkkotunnukset ASCII-yhteensopiviksi merkkijonoiksi, jotka alkavat xn--:lla.

Katso tätä:

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

Miksi kaksi eri järjestelmää? Koska DNS (järjestelmä, joka muuntaa verkkotunnukset IP-osoitteiksi) rakennettiin 1980-luvulla ja tukee vain ASCII:ta. Joten heidän piti keksiä tapa pakata Unicode ASCII-merkkijonoihin — ja Punycode on se keksintö. URL:n polku- ja kyselyosat puolestaan käsitellään web-palvelimilla, jotka osaavat käsitellä prosenttikoodattuja tavuja.

Itse asiassa on kokonainen spesifikaatio Unicodelle URL:issa nimeltä IRI (Internationalized Resource Identifiers), määritelty RFC 3987:ssä. IRI on periaatteessa URL, joka sallii Unicode-merkit suoraan. Selaimet muuntavat IRI:t URI:ksi kulissien takana.

Ja kyllä, emoji-verkkotunnuksia on olemassa. 💩.la on oikea verkkotunnus (tai oli jossain vaiheessa). Se Punycode-koodataan muotoon xn--ls8h.la. En suosittele emoji-verkkotunnusten käyttöä mihinkään vakavaan, mutta se on hauska todiste siitä, että järjestelmä toimii.

Yksi kompastuskivi Unicoden kanssa URL:issa: saman merkin erilaiset Unicode-esitykset. Esimerkiksi é voidaan esittää yhtenä koodipisteenä (U+00E9) tai e:nä + yhdistävänä aksenttimerkkinä (U+0065 + U+0301). Nämä tuottavat erilaisia prosenttikoodattuja merkkijonoja! WHATWG URL -standardi suosittelee NFC-normalisointia, mutta kaikki järjestelmät eivät noudata tätä johdonmukaisesti.

Tuplakoodaus: Bugi joka kummittelee unissasi

Mainitsin tuplakoodauksen aiemmin, mutta se ansaitsee oman syväsukelluksensa, koska tämä bugi on todennäköisesti tuhlannut enemmän kollektiivisia kehittäjätunteja kuin mikään muu URL:iin liittyvä ongelma. Olen ollut siinä tilanteessa — useita kertoja.

Tässä perusskenaario:

plaintext

Mitä tapahtui? Kun koodaat hello%20world:n toisen kerran, %-merkki koodataan muotoon %25. Eli %20:stä tulee %2520. Palvelin näkee kirjaimellisen merkkijonon %20 välilyönnin sijaan.

Tämä kuulostaa ilmeiseltä kun kirjoitan sen näin, mutta oikeissa koodikannoissa se on uskomattoman salakavala. Tässä skenaariot, joissa tuplakoodaus iskee:

Proxy-ketjut. Lähetät pyynnön palvelimelle A, joka välittää sen palvelimelle B. Jos molemmat palvelimet koodaavat URL:n, bum — tuplakoodaus. API-yhdyskäytävät kuten Kong, AWS API Gateway tai nginx-käänteisproxit ovat yleisiä syyllisiä.

Uudelleenohjausketjut. Käyttäjä menee sivulle A, uudelleenohjataan sivulle B ?returnUrl=...-parametrilla, ja sivu B uudelleenohjaa taas paluuURL:lla parametrina. Jokainen uudelleenohjaus saattaa koodata URL:n uudelleen. Kolmen uudelleenohjauksen jälkeen URL:si on kolminkertaisesti koodattu ja täysin rikki.

Kehysten "apulaiset". Jotkut web-kehykset koodaavat URL-parametrit automaattisesti. Jos koodaat manuaalisesti ennen kehykselle välittämistä, saat tuplakoodauksen. Olen nähnyt tämän tapahtuvan Spring Bootissa, Express.js-välikerroksissa ja Djangon URL-käännöksissä.

Miten tunnistat tuplakoodauksen? Etsi %25 URL:sta. Se on prosenttimerkki, joka on koodattu, mikä yleensä tarkoittaa, että jokin on koodattu kahdesti. Jos näet %2520, se on tuplakoodattu välilyönti. Jos näet %253D, se on tuplakoodattu =-merkki.

Miten se korjataan:

javascript

Paras puolustus on selkeiden rajojen asettaminen koodissasi: koodaa reunoilla (juuri ennen HTTP-pyynnön lähettämistä tai juuri ennen URL:n rakentamista näytettäväksi), ja välitä raakoja, koodaamattomia merkkijonoja kaikkialla sisäisesti.

URL:n pituusrajoitukset ja mitä niille tehdään

Tässä jotain mikä yllättää sinut: HTTP-spesifikaatio itsessään EI määrittele URL:n maksimipituutta. RFC 3986 sanoo, että URL:ien pitäisi olla "rajoittamattoman pituisia". Mutta todellinen maailma on eri mieltä.

Ketjun eri komponenteilla on omat rajansa, ja lyhin voittaa:

KomponenttiURL:n maksimipituus
Internet Explorer (RIP)2 083 merkkiä
Chrome, Firefox, Safari~65 000+ merkkiä
Apache (oletus)8 190 merkkiä
Nginx (oletus)8 192 merkkiä
IIS (oletus)16 384 merkkiä
AWS ALB8 192 merkkiä
Cloudflare32 768 merkkiä

Se vanha 2 083 merkin raja IE:stä hallitsi aiemmin kaikkea. Vaikka IE on käytännössä kuollut nyt, jotkut kehittäjät ja työkalut kohtelevat sitä yhä pyhänä totuutena. Mutta käytännössä useimmat modernit stackit pystyvät käsittelemään paljon pidempiä URL:ja.

Silti, se että VOIT tehdä 65 000 merkin URL:n ei tarkoita, että PITÄISI. Tässä joitain todellisia syitä pitää URL:t lyhyinä:

  • Palvelinlokit. Monet lokijärjestelmät katkaisevat pitkät URL:t, mikä tekee debuggauksesta painajaista.
  • Kopioi ja liitä. Käyttäjät kopioivat ja jakavat URL:ja. Todella pitkät URL:t rikkoutuvat sähköposteissa, chat-viesteissä ja dokumenteissa.
  • SEO. Hakukoneet suosittelevat yleensä URL:ien pitämistä alle 2 000 merkin.
  • Välimuisti. Jotkut CDN- ja proxy-välimuistin avainrajoitukset perustuvat URL:iin. Pidemmät URL:t = enemmän välimuistihuteja.

Mitä siis tehdä kun URL:si kasvaa liian pitkäksi? Hyvä että kysyit:

1. Käytä POST:ia GET:in sijaan. Jos lähetät paljon dataa, laita se pyynnön runkoon. Pyynnön rungolla ei ole käytännön kokorajaa. Tämä on yleisin ratkaisu monimutkaisille hakulomakkeille, joissa on paljon suodattimia.

2. Käytä URL-lyhentäjiä tai viitetunnisteita. Luo lyhyt tunnus, joka viittaa palvelinpuolella tallennettuun täydelliseen parametrijoukkoon. Eli /search?filter1=abc&filter2=def&filter3=...:n sijaan saat /search/saved/abc123.

3. Pakkaa parametrisi. Jotkut sovellukset base64-koodaavat pakatun JSON-blobin ja laittavat sen URL:iin. Ei kaunista, mutta toimii. Näet tämän työkaluissa kuten Grafana-dashboardien URL:issa.

Lomakekoodaus: application/x-www-form-urlencoded

Puhutaan huoneen norsusta, joka tekee URL-koodauksesta vieläkin hämmentävämpää: HTML-lomakekoodaus. Kun lähetät HTML-lomakkeen method="POST":lla, selain koodaa lomakkeen datan käyttäen muotoa nimeltä application/x-www-form-urlencoded. Ja tämä muoto on MELKEIN sama kuin tavallinen prosenttikoodaus, mutta yhdellä keskeisellä erolla, joka ajaa kaikki hulluksi.

Välilyönnit muuttuvat +:ksi %20:n sijaan.

Siinä se. Se on pääero. Mutta voi pojat, kuinka paljon se aiheuttaa sekaannusta.

plaintext

Miksi tämä ero on olemassa? Historialliset syyt, tietenkin. HTML-lomakekoodauksen spesifikaatio on vanhempi kuin URL-koodauksen spesifikaatio, ja se käytti +:aa välilyönneille koska... no, joku ajatteli 90-luvulla, että se näyttää paremmalta. Ja nyt olemme jumissa sen kanssa ikuisesti.

Kun lähetät HTML-lomakkeen, selain lähettää Content-Type: application/x-www-form-urlencoded -otsakkeen, ja palvelin tietää tulkita +:n välilyönneiksi. Mutta jos rakennat URL:ja manuaalisesti JavaScriptissä ja käytät +:aa välilyönneille polku-osassa, palvelin näkee kirjaimellisia plus-merkkejä. Hauskoja aikoja.

Tässä sillä on väliä käytännössä:

javascript

Ja sitten on multipart/form-data, joka on täysin erilainen peto. Kun lomakkeessasi on tiedostojen lataus (), selain vaihtaa multipart/form-data-koodaukseen, joka käyttää rajoja kenttien erottamiseen &-merkkien sijaan. Se ei käytä URL-koodausta lainkaan — jokaisella osalla on omat otsakkeensa ja rungkonsa. Jos olet koskaan yrittänyt parseroida multipart-pyyntöä manuaalisesti, tiedät tuskan.

Käytännön neuvo: käytä URLSearchParams:ia kyselymerkkijonoille ja lomakedata. Käytä FormData:a tiedostojen lataukseen. Älä yritä koodata näitä asioita käsin, ellet oikeasti, oikeasti ole pakko.

Kokeile itse

Työskenteletkö URL:ien kanssa, jotka näyttävät vääriltä? Liitä ne URL-dekooderiin nähdäksesi todelliset merkit. Tarvitsetko koodata arvon ennen sen laittamista URL:iin? URL-koodaaja hoitaa sen hetkessä. Ja monimutkaisten URL:ien purkamiseen osiin käytä URL-jäsentäjää nähdäksesi jokaisen osan selkeästi.