[Перевод] Мой URL — это не ваш URL
Когда давным-давно в 1996 году я приступил к работе над программой httpget, предшественницей проекта Curl, я написал свой первый синтаксический анализатор URL. Как раз тогда этот универсальный адрес получил название URL: Uniform Resource Locator (единый указатель ресурсов). Его спецификация была опубликована IETF в 1994 году. Аббревиатура «URL» была затем использована как источник вдохновения для названия инструмента и проекта Curl.
Термин «URL» был позднее изменён; его стали называть URI (Uniform Resource Identifier — единый идентификатор ресурсов), согласно спецификации, опубликованной в 2005 году, однако основное сохранилось: синтаксис для строки, задающей онлайн-ресурс и указывающей протокол для получения этого ресурса. Мы требуем, чтобы curl принимал указатели URL, как определено данной спецификацией RFC 3986. Ниже я расскажу, почему на самом деле это не совсем так.
Был ещё родственный RFC, описывающий IRI: Internationalized Resource Identifier (международный идентификатор ресурсов). IRI, по существу, то же самое, что URI, но IRI позволяют использовать символы, не входящие в ASCII.
Консорциум WHATWG позднее создал свою собственную спецификацию URL, в основном, сведя вместе форматы и идеи от URI и IRI с сильным упором на браузеры (что неудивительно). Одна из объявленных ими целей — «Модернизировать RFC 3986 и RFC 3987 в соответствии с современными реализациями и постепенно вывести их из употребления». Они хотят вернуться к использованию термина «URL», справедливо заявляя, что термины URI и IRI просто запутывают ситуацию, и что люди так и не поняли их (или часто даже не знают, что эти термины существуют).
Спецификация WHATWG написана в духе старой доброй мантры браузеров: быть как можно более либеральными с пользователями, всегда пытаться угадать, что они имеют в виду, и выворачиваться наизнанку, пытаясь сделать это. Хотя при этом мы все знаем сейчас, что закон Постеля — не самый лучший подход к делу. На деле это значит, что спецификация позволяет использовать в URL слишком много слэшей, пробелы и символы, не входящие в ASCII.
С моей точки зрения, такую спецификацию также очень трудно читать и соблюдать, поскольку она не очень подробно описывает синтаксис или формат, но при этом навязывает обязательный алгоритм парсинга. Чтобы проверить моё утверждение: посмотрите, что это спецификация говорит о концевой точке после имени хоста в URL.
Вдобавок ко всем этим стандартам и спецификациям, в интерфейсе всех браузеров есть адресная строка (которую часто называют и по-другому), которая позволяет пользователям вводить какие угодно забавные строки и преобразовывает их в URL. Если ввести »http://localhost/%41
» в адресную строку, то участок с процентом будет преобразован в «A» (поскольку 41 в шестнадцатеричном исчислении является заглавной буквой A в ASCII), но если ввести »http://localhost/A A
», то фактически в исходящий HTTP-запрос GET будет отправлено »/A%20A
» (с пробелом в URL-кодировке). Я говорю об этом, так как люди часто думают, что всё, что можно ввести в эту строку — и есть URL.
Указанное выше — в основном моё (искаженное) представление, с какими спецификациями и стандартами нам пока приходится работать. Теперь давайте добавим реальности и посмотрим, какие проблемы мы получаем, когда мой URL — это не ваш URL.
Так что же такое URL?
Или более конкретно — как мы пишем их? Какой синтаксис используем?
Думаю, одна из самых больших ошибок в спецификации WHATWG (и в ней причина, почему я выступаю против этой спецификации в её текущей форме с твёрдым убеждением, что они неправы) состоит в том, что они полагают, будто только им позволено работать с URL и давать им определение; они ограничивают свое представление об URL исключительно браузерами, HTML и адресными строками. Конечно, WHATWG создан большими компаниями, представляющими браузеры, которые использует почти каждый, а в этих браузерах широко работают указатели URL, но сами URL — явление значительно большее.
Представление об URL, существующее у WHATWG, не слишком широко принимается за пределами браузеров.
Двоеточие-слэш-слэш
Если спросить пользователей — обычных людей без какого-либо особого знания протоколов или сети — о том, что такое URL, то что они ответят? Последовательность »://» (двоеточие-слэш-слэш) была бы в начале списка ответов; несколько лет назад, когда браузеры показывали URL более полно, это было бы еще заметнее. Увидев эту последовательность, мы сразу понимаем, что перед нами именно URL.
Однако давайте отойдём от пользователей и оглядимся — в мире существуют почтовые клиенты, эмуляторы терминалов, текстовые редакторы, Perl-скрипты и многое-многое другое, что способно распознавать URL и работать с ними. Например, открыть URL в браузере, превратить в активную ссылку в сгенерированном HTML и так далее. Огромное количество названных скриптов и программ будет использовать именно последовательность «двоеточие-слэш-слэш» как главный признак.
Спецификация WHATWG говорит, что должен быть как минимум один слэш и что парсер при этом обязан принимать какое угодно количество слэшей. Это значит, что »http:/example.com
» и »http:///////////////example.com
» — полностью подходящие варианты. RFC 3986 и многие другие с этим не согласны. Ну, действительно, большинство из людей, с которыми я спорил последние несколько дней, даже те, кто работает в вебе, говорит, думает и убеждено, что URL имеет два слэша. Просто посмотрите внимательнее на скриншот результата поиска картинок в Гугл по запросу «URL» выше в этой статье.
Мы просто знаем, что у URL есть два слэша (хотя, да, URL типа file: обычно имеют три слэша, но давайте пока проигнорируем это). Не один. Не три. Два. Но WHATWG с этим не согласен.
«Есть хоть одна настоящая причина принимать более двух слэшей для не-файловых URL?» (спрашиваю я раздраженно у членов WHATWG)
«Факт в том, что это делают все браузеры.»
Спецификация говорит это, потому что браузеры реализовали её именно так.
Никакое лучшее объяснение не было дано даже после того, как я указал, что это утверждение неправильное и далеко не все браузеры делают так. Возможно, эта ветка обсуждения покажется вам весьма познавательной.
В проекте Curl мы как раз недавно начали обсуждать, как обращаться с указателями URL, имеющими число слэшей, отличное от двух, потому что, оказывается, уже есть серверы, передающие обратно такие URL в заголовке «Location:», и некоторые браузеры без возражений принимают их. Curl — нет, так же как и большинство из множества других библиотек и инструментов командной строки. Кого нам поддержать?
Пробелы
Символ пробела (код 32 в ASCII, шестнадцатеричный код 0×20) не может быть частью URL. Если требуется отправить его, то использовать URL-кодировку, как это делают с любым другим недопустимым символом, который надо сделать частью URL. URL-кодировка — это байтовое значение в шестнадцатеричном исчислении со знаком процента перед ним. Таким образом,»%20» означает пробел. Это также означает, что синтаксический анализатор, например, сканирующий текст на предмет указателей URL, узнаёт, что достиг конца URL, когда он обнаруживает недопустимый символ. Например, пробел.
Браузеры обычно преобразовывают все %20 в своих адресных строках в символ пробела, чтобы ссылки выглядели прилично. При копировании адреса в буфер и вставке его в текстовый редактор мы видим пробелы как %20, что и требуется.
Я не уверен, в этом ли причина, но браузеры также принимают пробелы как часть URL, получая, например, переадресацию в HTTP-ответе. Такие URL передаются от сервера к клиенту в заголовке «Location:». Браузеры без проблем допускают пробелы в них URL, кодируя их в виде %20 и отправляя следующий запрос. Это заставляет curl принимать пробелы в перенаправляемых «URL».
Не-ASCII
Поддержка в URL языков, включающих символы, не входящие в ASCII, конечно, важно, особенно для незападных сообществ, и я согласен, что спецификация IRI никогда не была достаточно хороша. Я лично далёко не эксперт в интернационализации, поэтому я руководствуюсь тем, что слышал от других. Но, конечно, пользователи нелатинских алфавитов и систем печати должны иметь возможность записывать свои «интернет-адреса» в ресурсы и использовать их как ссылки.
В идеальном случае у нас была бы интернационализированная версия для показа пользователю, и версия в кодировке ASCII для внутреннего использования в сетевых запросах.
Для международных доменных имён имя преобразуется в кодировку punycode так, чтобы оно могло быть прочитано обычными серерами DNS, которые ничего не знают об именах в кодировке, отличной от ASCII. Идентификаторы URI не имеют IDN-имён; IRI и URL по версии WHATWG — имеют. Сurl поддерживает IDN-имена хостов.
WHATWG заявляет, что URL могут использовать UTF-8, тогда как URI — только ASCII. Curl не воспринимает не-ASCII-символы в части адреса, задающей путь, но кодирует их процентом в исходящих запросах; это порождает «интересные» побочные эффекты, когда не-ASCII-символы представлены в коде, отличном от UTF-8, что является, например, стандартным для Windows.
Подобно тому, что я написал выше, это приводит к серверам, отправляющим назад не-ASCII-коды в HTTP-заголовках, которые браузеры охотно принимают, и не-браузерам тоже приходится работать с ними.
Стандарта URL не существует
Я не пытался представить полный список проблем или несоответствий — здесь просто некоторая подборка трудностей, с которыми я недавно столкнулся. «URL», выданный в одном месте, конечно, совсем необязательно будет принят или понят в другом месте как «URL».
В наши дни даже curl уже не следует строго ни одной опубликованной спецификации — мы медленно деградируем в угоду «веб-совместимости».
Единый стандарт URL отсутствует, и какая-либо работа в этом направлении не ведётся. Я не могу считать, что WHATWG прилагает настоящие усилия к этому, поскольку она пишет спецификацию закрытой группой без серьёзных попыток привлечь более широкое сообщество.