DevToys Pro

бесплатные веб-инструменты для разработчиков

Блог
Оцените нас:
Попробуйте расширение для браузера:
← Назад к блогу

Крайние случаи URL-кодирования: пробел, плюс и процент

14 мин чтения

Рано или поздно каждый разработчик выпускает фичу, которая таинственным образом ломается, как только кто-то вставляет в URL значение с пробелами, плюсами или не-ASCII символами. Корень проблемы почти всегда один и тот же: тонкие различия в том, как URL-кодирование работает в браузере, на бэкенде и в промежуточных прокси.

В этой статье мы разберём реальные сценарии отладки вокруг percent encoding, + против %20, а также различия между RFC 3986 и application/x-www-form-urlencoded. Мы также посмотрим, как использовать URL encoder/decoder, чтобы проверять query-строки ещё до того, как они попадут на бэкенд.

Два мира: RFC 3986 против application/x-www-form-urlencoded

Когда разработчики говорят «URL-кодирование», они часто смешивают два немного разных набора правил:

  • RFC 3986 URL encoding — описывает, как символы percent-кодируются в URI и URL.
  • application/x-www-form-urlencoded — описывает, как браузеры кодируют поля HTML-форм в query-строку или тело запроса.

Хитрость в том, что обе схемы используют percent encoding, но формат form-encoded по‑особому обращается с пробелом.

КонтекстСимвол пробелаПример
RFC 3986 (общий URL)%20q=hello%20world
application/x-www-form-urlencoded+ (плюс)q=hello+world

В form-encoded данных + означает пробел, а не буквальный плюс. В обычном RFC 3986 URL + — просто ещё один символ, и если вам нужен именно плюс, его нужно percent-кодировать как %2B.

История отладки: «C++» превращается в «C   »

Представьте, что вы отлаживаете поиск по языкам программирования. Пользователь жалуется, что поиск по C++ возвращает результаты для C.

Фронтенд, на первый взгляд, шлёт вполне нормальный запрос:

GET /search?query=C++ HTTP/1.1
Host: example.test

Но если браузер или клиент не кодирует плюсы корректно, реальная строка запроса может оказаться такой:

GET /search?query=C%2B%2B  # Правильно (плюсы закодированы)
GET /search?query=C++    # Двусмысленно (может быть интерпретировано как "C  ")

На сервере, если фреймворк трактует query как application/x-www-form-urlencoded, он декодирует + в пробел. Результат — "C " вместо "C++".

Безопасное правило: кодируйте буквальный плюс как %2B. Если сомневаетесь, пропустите значение через URL encoder и проверьте результирующую строку.

Резервированные, нерезервированные и специальные символы

RFC 3986 делит символы на три крупные группы:

  • Нерезервированные: безопасны без кодирования — буквы, цифры, -, ., _, ~
  • Резервированные: имеют структурное значение в URL — :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =
  • Всё остальное: должно быть percent-кодировано (включая пробелы, не-ASCII, управляющие символы)

В query-строке резервированные символы часто используются как разделители:

  • & разделяет пары «ключ-значение»
  • = разделяет имя и значение
  • ? начинает query-часть

Если вам нужны эти символы внутри значения, их нужно кодировать. Например, параметр, содержащий JSON, должен кодировать ", { и }, а также любые &, чтобы не сломать структуру URL.

Как работает percent encoding

Percent encoding заменяет байт в URL на %, за которым следуют две шестнадцатеричные цифры в верхнем регистре, представляющие значение этого байта.

" "   (пробел)   -> %20
"+"   (плюс)    -> %2B
"%"   (процент) -> %25
"?"   (вопрос)  -> %3F
"&"   (амперсанд)-> %26

Для не-ASCII символов в UTF-8 каждый байт кодированной последовательности кодируется отдельно:

"привет" (UTF-8 байты):

пр -> D0 BF
ив -> D0 B8 D0 B2
ет -> D0 B5 D1 82

Закодированное URL-значение:
%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82

URL encoder/decoder особенно полезен в таких случаях: вставьте Unicode-строку, проверьте percent-кодированный вывод и убедитесь, что ваш бэкенд декодирует её обратно без порчи байтов.

Query string encoding на практике

Допустим, вы хотите отправить несколько фильтров в одном параметре query-строки в виде JSON. Сам JSON выглядит так:

{
  "status": ["open", "closed"],
  "assignee": "alice@example.com"
}

Если наивно вставить его в URL без кодирования, получится некорректная query-строка:

/issues?filter={"status":["open","closed"],"assignee":"alice@example.com"}

Символы вроде ", {, }, а также @ и & конфликтуют с правилами парсинга URL.

Безопасный вариант — percent-кодировать JSON-значение:

/issues?filter=%7B%22status%22%3A%5B%22open%22%2C%22closed%22%5D%2C%22assignee%22%3A%22alice%40example.com%22%7D

Здесь помогает понятный workflow для query-строк: собираете JSON, пропускаете его через URL encoder и только потом добавляете в URL.

Типичные ошибки URL-кодирования

Ошибка 1: двойное кодирование

Двойное кодирование возникает, когда значение percent-кодируется больше одного раза. Например, пробел превращается в %20, а затем символ % сам кодируется в %25, и получается %2520.

// Первое кодирование:
"hello world" -> "hello%20world"

// Повторное кодирование:
"hello%20world" -> "hello%2520world"

На сервере один проход декодирования даёт hello%20world, а не hello world. В итоге вы сохраняете буквально %20 в базу или сравниваете неправильную строку при поиске.

Для отладки декодируйте значение один раз через URL decoder. Если вы всё ещё видите percent-последовательности, скорее всего, строка была закодирована несколько раз.

Ошибка 2: отсутствие кодирования значений в query-строке

Конкатенация строк при построении URL — классический источник ошибок:

// Хрупко: без кодирования
const url = "/search?query=" + query;

// Надёжнее: кодируем значение
const url = "/search?query=" + encodeURIComponent(query);

Если забыть закодировать значения, символы вроде & и = ломают структуру query-строки и «обрезают» параметры. Всегда пропускайте сырое вводимое пользователем значение через URL encoder, прежде чем вставлять его в query.

Ошибка 3: кодирование всего URL вместо отдельных значений

Другая распространённая ошибка — вызывать URL encoder на всей строке URL, включая разделители и слэши:

// Неправильно: кодируются "/", "?", "&" и "="
encodeURIComponent("/search?query=hello world");

// Правильно: кодируем только динамическое значение
"/search?query=" + encodeURIComponent("hello world");

Если перекодировать всю строку, сервер больше не видит нормальный URL с путём и query-частью. Многие фреймворки будут трактовать его как путь с буквальными %3F и %3D, и вы потеряете все query-параметры.

Использование URL-инструмента в стиле DevToys в рабочем процессе

Отдельный URL encoder/decoder крайне полезен при отладке web-пейлоадов. С DevToys Pro URL Encoder/Decoder вы можете:

  • Вставить сырую query-строку и сразу увидеть декодированные ключи и значения
  • Кодировать произвольный текст (включая пробелы, плюсы и Unicode) в безопасное значение для URL
  • Проверять, не была ли строка закодирована дважды, декодируя её шаг за шагом
  • Сравнивать, как разные входные данные меняют закодированный вывод в крайних случаях
  • Экспериментировать с query string encoding до того, как добавить его в production-код

Лучшие практики URL-кодирования

1. Кодируйте значения, а не весь URL

Применяйте URL-кодирование только к динамическим частям URL (query-параметры, сегменты пути), а не ко всей строке. Так разделители ?, &, = и / останутся нетронутыми, и парсер URL сможет корректно отработать.

2. Осторожно обращайтесь с «+»

  • В form-encoded данных + означает пробел.
  • Чтобы передать буквальный + в значении, кодируйте его как %2B.
  • Если вы видите пробелы вместо плюсов, проверьте, как сервер декодирует application/x-www-form-urlencoded.

3. Используйте полноценный encoder

Не пишите самодельные замены вида replace(" ", "%20"). Используйте полноценный URL encoder в своём языке и сверяйтесь с онлайн URL encoder/decoder, чтобы убедиться, что браузер, сервер и инструменты одинаково понимают семантику.

4. Логируйте и сырые, и декодированные значения

При отладке проблем в продакшене логируйте как сырую строку запроса, так и уже декодированные параметры, которые видит фреймворк. Разница между ними часто выдаёт двойное кодирование, отсутствие кодирования или неправильную обработку спецсимволов.

Выводы

URL-кодирование кажется простым, пока в игру не вступают пробелы, плюсы и не-ASCII символы. Понимание различий между RFC 3986 и application/x-www-form-urlencoded, аккуратная работа с + и единообразное percent-кодирование специальных символов позволяют избежать тонких багов в query-строках.

Сочетание корректного query string encoding в коде и надёжного URL encoder/decoder в вашем наборе инструментов даёт уверенность, что то, что пользователь видит в адресной строке, — ровно то же самое, что ваш бэкенд получает и декодирует.