URI и URL: разбор адреса и проверка на учебном наборе строится на пяти строках. RFC 3986 задаёт общий синтаксис идентификатора, WHATWG описывает web URL и его парсер. Ниже — эталон разбора, разрешение относительных ссылок от базы и отрицательный случай.
Две рамки: общий синтаксис RFC 3986 и web URL по WHATWG
RFC 3986 описывает общий синтаксис идентификатора. По грамматике этого документа URI-reference есть либо URI, либо relative-ref; URI начинается со схемы, за которой идёт двоеточие, а имя схемы начинается с буквы. Схема определяет способ идентификации ресурса в своём пространстве имён. Документ RFC 3986 прямо говорит, что URL — это подмножество URI, у которого есть средство locating: описание основного механизма доступа, например сетевого расположения. URN исторически обозначает идентификаторы схемы «urn», обязанные оставаться глобально уникальными и постоянными. При этом отдельная схема не обязана быть только именем или только локатором: экземпляры URI одной схемы могут иметь свойства и того, и другого. Будущим спецификациям RFC 3986 рекомендует использовать общий термин URI.
WHATWG описывает web URL отдельно. В целях стандарта сказано: он выравнивает RFC 3986 и RFC 3987 с современными реализациями и объявляет эти RFC устаревшими в процессе; пробелы, «нелегальные» кодовые точки, кодирование query, равенство и канонизация — понятия, которые не полностью совпадают или не полностью определены. Стандарт фиксирует термин URL и заявляет, что на практике для URI и IRI используется один алгоритм. Парсер URL принимает произвольную строку и возвращает либо URL, либо отказ, попутно записывая ошибки валидации. Этот парсер не доказывает соответствие грамматике RFC и не подаётся как её замена: рамки решают разные задачи и не выводятся одна из другой.
Пять строк учебного набора
Учебный набор задан заранее и берётся без изменений. Пять строк:
https://example.test/a?x=1#part
urn:isbn:9780131103627
/help?q=2
../img/a.png
1bad:thing
Первая строка — абсолютный адрес с http(s)-схемой, путём, query и fragment. Вторая — идентификатор схемы urn с номером ISBN. Третья начинается с одного слэша: это ссылка с абсолютным путём, привязанная к базе. Четвёртая начинается с двух точек: относительный путь, который поднимается на уровень выше базы. Пятая начинается с цифры и содержит двоеточие в первом сегменте — это отрицательный случай.
Набор держит три разных типа входа: абсолютные адреса, относительные ссылки и строку, которая не проходит грамматику. Такой состав позволяет проверить не только разбор, но и отказ. Строки короткие, их удобно вставлять в чек-лист и сверять результат построчно. Дальше разбираем каждую по компонентам и по правилам.
Матрица выбора рамки и условия пересмотра
Выбор инструмента зависит от задачи. Матрица ниже фиксирует условия и оставляет неизвестное явно отмеченным.
| Задача | Рамка | Что даёт | Ограничение |
|---|---|---|---|
| Строгая проверка общего синтаксиса | RFC 3986 | Грамматика URI-reference, URI, relative-ref, absolute-URI | Не описывает поведение web-парсера |
| Поведение web-парсера | WHATWG | Приём произвольной строки, URL или отказ, ошибки валидации | Не доказывает соответствие грамматике RFC |
| Разбор компонентов для http(s) | Выбранный пример | Восстановление scheme, host, path, query, fragment | Покрывает только разобранный класс адресов |
| Поведение конкретных реализаций за пределами этих рамок | — | Неизвестно | Требует отдельной проверки |
Рекомендация условна. Если нужна строгая проверка общего синтаксиса — берётся рамка RFC 3986. Если нужно поведение web-парсера — берётся рамка WHATWG. Если нужен только разбор компонентов для http(s) — достаточно выбранного примера. Неизвестным остаётся поведение конкретных реализаций за пределами этих рамок.
Условия пересмотра: смена требований к строгости и смена среды разбора. Когда проверка переходит из учебного разбора в конвейер, где строки приходят от пользователя, приоритет меняется на поведение парсера. Когда строка идёт в протокол с жёсткой грамматикой, приоритет у RFC. При каждом таком переходе матрицу пересматривают заново.
Эталон разбора первой строки по компонентам
Первая строка — https://example.test/a?x=1#part. По RFC 3986 это URI с допустимой схемой и fragment. Отдельный production absolute-URI из §4.3 fragment не допускает, поэтому первая строка ему не соответствует, оставаясь при этом URI. Это различие важно: absolute-URI — более узкое правило, применяемое там, где fragment запрещён, например при определении базового URI для последующего разрешения относительных ссылок.
Как http(s)-URL первая строка раскладывается так:
| Компонент | Значение |
|---|---|
| scheme | https |
| host | example.test |
| path | /a |
| query | x=1 |
| fragment | part |
Authority по документации MDN — секция после схемы и перед путём; она может содержать до трёх частей: user information, host и port. Path — секция после authority, данные обычно организованы иерархически. Query — секция после пути, неиерархические данные. Fragment — необязательная часть в конце, начинается с символа # и указывает на конкретную часть ресурса, например раздел документа или позицию в видео.
По RFC 3986 fragment не участвует в scheme-specific обработке: он отделяется от остального URI до dereference и обрабатывается агентом пользователя. Использование fragment не означает, что действие извлечения вообще произойдёт.
Разрешение относительных ссылок от базы
База задана: https://example.test/docs/start. Разрешаем две относительные ссылки.
Третья строка /help?q=2 — relative-ref, а именно absolute-path reference: она начинается с одного слэша. По RFC 3986 relative-ref состоит из relative-part, необязательных query и fragment. Варианты relative-part: // с authority и путём path-abempty, абсолютный путь, путь без схемы или пустой путь. Правило path-abempty допускает пустой путь и путь из сегментов, начинающихся со слэша. Ссылка с одним ведущим слэшем называется absolute-path reference. При разрешении от базы путь заменяется целиком, authority и схема берутся из базы:
/help?q=2 → https://example.test/help?q=2
Четвёртая строка ../img/a.png — relative-ref, relative-path reference: она не начинается со слэша. Сначала относительный путь присоединяется к каталогу базы: /docs/ и ../img/a.png дают /docs/../img/a.png. Затем алгоритм удаления точечных сегментов убирает .. вместе с предшествующим сегментом docs, и получается /img/a.png.
../img/a.png → https://example.test/img/a.png
Обе ссылки разрешены от одной базы, и результат проверяем построчно. Ссылка с абсолютным путём сохраняет хост базы, ссылка с относительным путём поднимается на уровень выше. Этот эталон годится как минимальный тест: если инструмент даёт другие строки, сначала проверьте операцию разрешения ссылки и использованную базу.
Отрицательный случай 1bad:thing
Пятая строка 1bad:thing не проходит правило схемы. По грамматике RFC 3986 имя схемы начинается с буквы: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ). Первый символ здесь — цифра, поэтому строка не является URI.
Второй барьер — правило первого сегмента. Строка не является и path-noscheme: по RFC 3986 сегмент пути, содержащий двоеточие, не может быть первым сегментом relative-path reference, поскольку его приняли бы за имя схемы. Такой сегмент требуется предварять точечным сегментом, например ./this:that, чтобы получить relative-path reference. Здесь двоеточие стоит в первом сегменте, а ведущей точки нет.
Строка 1bad:thing не соответствует ни URI со схемой, ни relative-ref в исходном виде. Если добавить ./, получится ./1bad:thing — relative-ref с путём, содержащим двоеточие. При начале с буквы строку с двоеточием сначала проверяют как URI со схемой; сама эта форма не означает отказ. Для интерпретации такого первого сегмента именно как относительного пути его предваряют ./. Отрицательный случай в наборе проверяет ожидаемый отказ вместе с успешными примерами.
Fragment и HTTP request target в форме origin-form
Fragment не попадает в HTTP request target в форме origin-form. Для первой строки в target входит /a?x=1, а часть #part в него не входит.
Причина в роли fragment. По RFC 3986 fragment отделяется от остального URI до dereference и обрабатывается агентом пользователя, независимо от схемы. Он не используется в scheme-specific обработке URI. Это отличает его от path и query, которые участвуют в формировании запроса.
Практический вывод для проверки: если инструмент показывает fragment внутри request target, он смешал клиентскую и серверную части адреса. Проверка формулируется так: взять первую строку, получить target и убедиться, что он равен /a?x=1. Ожидаемый результат — fragment отсутствует. Такая проверка ловит ошибку на стыке разбора и отправки запроса, где чаще всего и теряется граница между компонентами.
План проверки читателя
План отделён от чьего-либо выполненного опыта: это последовательность действий, которую читатель проходит сам.
- Взять пять строк набора без изменений и записать их в файл или таблицу.
- Для каждой строки определить, является ли она URI или relative-ref по правилу схемы.
- Для первой строки восстановить scheme, host, path, query и fragment и сверить с эталоном.
- Для третьей и четвёртой строк применить базу
https://example.test/docs/startи сверить результаты разрешения. - Для пятой строки зафиксировать причину отказа: правило схемы и правило первого сегмента.
- Для первой строки получить HTTP request target в форме origin-form и убедиться, что fragment в него не входит.
- Заполнить матрицу выбора под свою задачу и отметить неизвестное.
Проверка считается пройденной, когда компоненты первой строки восстановлены, обе относительные ссылки разрешены верно и fragment в target не допущен. Расхождение на любом шаге указывает на конкретный слой: разбор компонентов, разрешение ссылок или формирование запроса.
Частые вопросы
Чем URI отличается от URL?
По RFC 3986 URL — подмножество URI, у которого есть средство locating: описание основного механизма доступа, например сетевого расположения. URI — более общий термин, охватывающий и имена, и локаторы. RFC 3986 рекомендует будущим спецификациям использовать общий термин URI. WHATWG, в свою очередь, фиксирует термин URL и заявляет, что на практике для URI и IRI используется один алгоритм.
Что такое urn:isbn:9780131103627 в наборе?
Это абсолютный URI с допустимой схемой urn. Схема начинается с буквы, поэтому правило схемы пройдено. Host у этой строки отсутствует: наличие host не утверждается для любой схемы, а требование host относится к выбранным http(s)-примерам. URN исторически обозначает идентификаторы, обязанные оставаться глобально уникальными и постоянными.
Почему 1bad:thing не проходит проверку?
Строка начинается с цифры, а имя схемы по грамматике RFC 3986 начинается с буквы. Второй барьер — правило первого сегмента: сегмент с двоеточием не может быть первым сегментом relative-path reference, поскольку его приняли бы за имя схемы. Такой сегмент требуется предварять точечным сегментом. Отказ приходит по правилу схемы.
Попадает ли fragment в HTTP-запрос?
Fragment не попадает в HTTP request target в форме origin-form. Для первой строки в target входит /a?x=1. По RFC 3986 fragment отделяется от остального URI до dereference и обрабатывается агентом пользователя, независимо от схемы, и не используется в scheme-specific обработке. Если инструмент показывает fragment в target, он смешал клиентскую и серверную части адреса.
Какую рамку выбрать для проверки адресов?
Если нужна строгая проверка общего синтаксиса — рамка RFC 3986. Если нужно поведение web-парсера — рамка WHATWG. Если нужен только разбор компонентов для http(s) — достаточно выбранного примера. Неизвестным остаётся поведение конкретных реализаций за пределами этих рамок. Рекомендация пересматривается при смене требований к строгости или к среде разбора.
