URL-кодування замінює символ знаком відсотка і двома шістнадцятковими цифрами: пробіл стає %20, амперсанд стає %26, знак питання стає %3F. Мета - не дати символу прочитатися як синтаксис URL, коли ви мали на увазі дані. Більше нічого.
Здається складнішим, бо майже кожне питання про це - насправді питання про область застосування. Які символи, у якій частині URL, екрановані яким шаром? Помилившись з областю застосування, отримуєте одну з двох класичних відмов: трекінговий параметр, що мовчки обрізається, або адресу призначення, яка приходить як https%3A%2F%2Fexample.com і віддає 404. Цей матеріал охоплює два набори символів, що визначають відповідь, місця, де правила змінюються, і як перевірити, що посилання насправді несе. Про ширшу картину того, що редирект робить з усім цим, читайте у статті типи редиректів.
Два набори, що визначають усе
RFC 3986, розділ 2.3 визначає незарезервований набір, який ніколи не потребує кодування: літери, цифри та рівно чотири знаки пунктуації - дефіс, крапка, підкреслення, тильда. Якщо ваше значення містить лише їх, робити нічого не потрібно.
Усе інше потрапляє в один із двох кошиків. Зарезервовані символи несуть структурне значення: : / ? # [ ] @ розділяють частини URL, а ! $ & ' ( ) * + , ; = розділяють елементи всередині цих частин. Розділ 2.2 перелічує їх. Вони легальні як синтаксис і мають бути закодовані, коли з'являються як дані. Решта - це все, що поза ASCII: воно кодується байт за байтом після перетворення в UTF-8, тому одна літера з діакритикою зазвичай коштує шість символів, а не три.
Звідси єдине правило, яке варто запам'ятати: кодуйте символ, коли він є даними і інакше прочитався б як синтаксис. Амперсанд між двома параметрами - це синтаксис. Амперсанд усередині назви кампанії - це дані, і якщо залишити його без змін, список параметрів обірветься саме там.
Кодуйте значення, а не URL
Це найчастіша помилка, яку я бачу, і вона завжди однакової форми. У когось є URL, він знає, що його потрібно закодувати, тож вставляє все це в кодувальник цілком і отримує:
https%3A%2F%2Fexample.com%2Fspring%3Futm_campaign%3Dspring%20sale
Цей рядок - не URL. Це текст у формі URL, який може бути лише значенням усередині іншого URL - саме туди він і має потрапити, коли ви передаєте адресу призначення через редиректор, і саме туди він не повинен потрапляти, коли ви намагаєтеся його відкрити.
Правильний підхід - кодувати кожне значення окремо:
https://example.com/spring?utm_campaign=spring%20sale&utm_source=flyer
Схема, хост, роздільники шляху та ? і & залишаються синтаксисом. Змінилося лише значення. У кожній мові є дві функції для цього розрізнення, і вибір неправильної - інша половина проблеми: сторінка MDN про encodeURIComponent прямо каже, що encodeURI свідомо залишає зарезервовані символи без змін, бо очікує цілий URI, тоді як encodeURIComponent екранує їх, бо очікує лише фрагмент. Для значень потрібен encodeURIComponent. У Python це urllib.parse.quote, у Go - url.QueryEscape, у PHP - rawurlencode.
Пробіл - це %20, окрім тих місць, де це плюс
Обидва варіанти правильні, тільки в різних місцях, і це найбільш заплутана річ у цій темі.
У шляху чи в звичайному URI пробіл - це %20. У рядку запиту, побудованому так, як його будує HTML-форма, пробіл - це +, бо саме це визначає серіалізація application/x-www-form-urlencoded у стандарті URL WHATWG. Обидві форми будь-який серверний парсер запиту, який ви скоріш за все зустрінете, прочитає як пробіл.
Пастка - у зворотному напрямку. Якщо знак плюс - це дані (номер телефону, пошуковий запит, кампанія з назвою spring+summer), його потрібно писати як %2B. Залишений без змін у рядку запиту, він стає пробілом, і ви проведете цілий день, розмірковуючи, чому номер у вашій CRM втратив код країни.
| Символ | Закодовано | Чому це важливо |
|---|---|---|
| space | %20 або + | + лише всередині рядка запиту, %20 - всюди |
& | %26 | Без кодування список параметрів обривається саме там |
? | %3F | Без кодування все, що після нього, стає запитом |
# | %23 | Без кодування решта взагалі не доходить до сервера |
+ | %2B | Без кодування в запиті він приходить як пробіл |
% | %25 | Без кодування наступні два символи з'їдаються |
Рядок з # заслуговує на окрему примітку, бо саме він породжує найбільш заплутаний баг-репорт. Фрагмент ніколи не надсилається на сервер. Поставте незакодований # у цілі редиректу, і сервер побачить обрізаний URL, тоді як адресний рядок браузера й далі виглядатиме правильно, тож людина, що про це повідомляє, присягається, що посилання в порядку.
Якщо ви будуєте URL кампаній вручну частіше, ніж зрідка, зупиніться: наш конструктор UTM кодує кожне значення в процесі введення, а правила іменування UTM розкриває, як обирати значення, що взагалі не потребують кодування. Скоротіть результат на власному домені, і закодований безлад перестане бути тим, на що комусь треба дивитися.
Подвійне кодування і як його розпізнати
Подвійне кодування - це те, що відбувається, коли значення проходить через два шари, кожен з яких виконує свою роботу. Знак відсотка сам є символом, що потребує екранування, тож %20 стає %2520, а %2520 стає %252520.
Симптоми легко впізнати, щойно їх побачивши. Заголовок сторінки, що показує spring%20sale реальному відвідувачу. Параметр, що приходить в аналітику з видимими escape-послідовностями. Редирект, який працює на першому переході й відмовляє на другому. Причина майже завжди - викликана функція кодування, обгорнута навколо значення, яке вже прийшло закодованим, часто тому, що воно вийшло з бази даних, яка зберігала закодовану форму.
Виправлення - вирішити, який шар відповідає за кодування, і зробити всі інші такими, що не втручаються. Декодуйте один раз, коли читаєте значення, кодуйте один раз, коли записуєте його в URL, і ніколи не робіть обидві дії в одній функції.
Де це болить на практиці
Три місця, у порядку, в якому ви їх, скоріш за все, зустрінете.
Трекінгові параметри. Значення кампанії з незакодованим амперсандом обриває список параметрів, тож сесія потрапляє в аналітику як прямий трафік, і кампанія не отримує жодного зарахування. Нічого не викидає помилку. UTM-параметри не показуються в GA4 розкриває діагностику з боку звітності, а браузери вирізають UTM-параметри розкриває іншу причину, з якої параметр може зникнути між кліком і сторінкою.
Редиректи. Серверні правила перекодовують непослідовно, і те, чи виживе рядок запиту взагалі, залежить від використаної директиви. Редирект 301 у .htaccess містить повну таблицю для Apache; коротка версія - правило, що замінює рядок запиту, мовчки відкине ваш.
QR-коди. Кодування збільшує довжину payload, а довжина payload визначає, наскільки щільним буде надрукований код. Кожен пробіл коштує три символи замість одного, кожна літера з діакритикою - шість. Трекінговий URL із парою закодованих назв кампаній може підняти код на версію-другу вище, а це реальна різниця при розмірі візитівки - саме тому QR-код не сканується називає довжину payload серед чотирьох причин. Кодування короткого посилання замість повного URL - найдешевше з доступних виправлень.
Перевірте, що посилання насправді несе
Дві команди розв'язують майже будь-яку суперечку. Перша показує, що сервер отримує після редиректу:
curl -sI 'https://example.com/spring?utm_campaign=spring%20sale' | grep -i '^location'
Друга сама будує кодування, а не довіряє вашим пальцям, що корисно, коли значення містить кілька проблемних символів одразу:
curl -G --data-urlencode 'utm_campaign=spring & summer sale' \
--data-urlencode 'utm_source=flyer' \
-o /dev/null -w '%{url_effective}\n' https://example.com/spring
Читайте результат як дані, а не як оформлення. Якщо бачите %2520 - у вас проблема подвійного кодування, якщо бачите значення, що обривається раніше часу - у вас незакодований роздільник, а якщо бачите %3A%2F%2F на початку - ви закодували весь URL. Наш перевіряч посилань виконує половину роботи з редиректом у браузері, якщо вам не хочеться відкривати термінал.
Звичка, яку варто виробити, - один раз глянути на фінальний URL власними очима перед запуском кампанії. Баги кодування невидимі в браузері й очевидні в терміналі, і вони коштують вам атрибуції, а не безвідмовної роботи, тому й живуть так довго.
Читайте наріжну серію
Цей матеріал належить до кластера engineering. Для боку редиректів типи редиректів охоплює кожен код статусу і клієнтський метод, а як працюють скорочувачі URL охоплює те, що відбувається між кліком і сторінкою.
Пов'язане в блозі
Поширені запитання
Що таке URL-кодування?
Заміна символу знаком відсотка, за яким іде значення його байта в шістнадцятковій системі, щоб символ не сплутали із синтаксисом URL. Пробіл стає %20, амперсанд стає %26, знак питання стає %3F. Механізм визначений у RFC 3986 і також зветься процентним кодуванням.
Які символи потрібно кодувати в URL?
Усе, що виходить за межі незарезервованого набору, який RFC 3986 визначає як літери, цифри та чотири символи: дефіс, крапку, підкреслення й тильду. Усе інше - або зарезервована пунктуація, що несе структурне значення, або байт поза межами ASCII, і обидва випадки потребують кодування через відсоток, коли з'являються всередині значення, а не як синтаксис.
Кодувати весь URL чи лише його частини?
Лише частини. Пропустивши весь URL через кодувальник, ви перетворюєте https://example.com на https%3A%2F%2Fexample.com, а це вже взагалі не URL. Кодуйте кожне значення параметра запиту і кожен сегмент шляху окремо, а схему, хост і роздільники залишайте без змін.
Пробіл - це %20 чи знак плюс?
І те, і те, тільки в різних місцях. У шляху й у звичайному URI пробіл - це %20. У рядку запиту, побудованому так, як його будують HTML-форми, пробіл - це знак плюс, бо саме це визначає серіалізація application/x-www-form-urlencoded. Тому буквальний плюс усередині значення запиту потрібно писати як %2B, інакше його прочитають як пробіл.
Що таке подвійне кодування?
Кодування того, що вже було закодоване, тож %20 стає %2520, бо сам знак відсотка екранується до %25. Симптом - сторінка, що показує буквальний %20 у своєму тексті, або параметр, який приходить із видимими escape-послідовностями. Майже завжди причина в тому, що значення пройшло через два шари, і кожен із них услужливо його закодував.
Чому закодовані символи ускладнюють сканування QR-коду?
Бо кожен коштує три символи замість одного. Пробіл - це один символ сенсу і три символи payload, тож кілька таких символів можуть підняти код на версію-другу вище, а це означає більше модулів на тій самій площі друку. Кодування довгого трекінгового URL у QR - один із найшвидших способів отримати код, який сканується лише зблизька.
Спробуйте Elido
Вставте URL - отримайте коротке посилання
Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.
Безкоштовно, без реєстрації · 2 на день