Мой URL — это не ваш URL

Original author: Daniel Stenberg
  • Translation


Когда давным-давно в 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, шестнадцатеричный код 0x20) не может быть частью 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 прилагает настоящие усилия к этому, поскольку она пишет спецификацию закрытой группой без серьёзных попыток привлечь более широкое сообщество.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 32

    +11
    Зацепило выражение мы медленно деградируем в угоду “веб-совместимости”.
    Это не только к Curl и URl/URL применимо, но и ко всем web-стандартам. Консорциум, с самого начала своей работы величайшие вещи сформулировал, грамотно, с глубоким инженерным осознанием проблемы. А кучка комерсов и толпа недоучившихся «уэб-мастеров» свели всё это к какому-то зоопарку велосипедов.
    И даже не это страшно, а то что консорциум, если судить по последним тенденциям всё больше идёт на поводу у комерсов и содержащих их среднестатистических обывателей, и всё меньше мыслит в инженерной плоскости.

    Боюсь, что это в будущем может доставить немало проблем в стеке веб-технологий и затормозить прогресс.
      0
      В оригинале было «Not even curl follows any published spec very closely these days, as we’re slowly digressing for the sake of “web compatibility”.» Т.е. «мы постепенно отступаем (от спецификации) в угоду веб-совместимости». Про деградацию — фантазия переводчика.
        0
        Вот как.
        Одно другому не мешает. Если некоторое изделие на выходе с производственной линии не соответствует требованиям — это ли не свидетельство брака и деградации производственных процессов?
      +11
      Мне кажется, одна из основных причин тут — та самая демократия. Самый лучший дизайн что я видел в сфере IT — это Python и nginx и они оба имеют своих диктаторов. Я считаю что в сфере проектирования крайне важно найти человека с хорошим дизайнерским чутьём, за которым всегда будет последнее слово; возможно, это несколько замедлит развитие, но конечный продукт будет целостен и логичен. Как антипример можно привести, допустим, Javascript или PHP, где никого подобного не было; в результате получились плохо спроектированные языки, которые до сих пор борются сами с собой.
        +1
        Не хотите немного холиваров на тему JS, уважаемый?)
          +4
          Всегда к вашим услугам )
          +1
          PHP — согласен, местами ужасно спроектированный язык. Но с JS то что не так? Если кто-то не умеет в асинхронность — то это не проблемы JS :)
            +3
            Асинхронность-то ладно, с async/await жить прекрасно.
            Самые основные, коренные ошибки в JS — это undefined и «всё — словарь». Это те вещи, которые невозможно исправить, родовые травмы, ошибки в самой концепции. Из-за них весь язык перекошен похуже квазимодо и вынужден подпирать себя костылями для сохранения дееспособности.
            Чрезвычайно слабая типизация — это косяк чуть меньший, но тоже достаточно эпичный. Впрочем, с ней можно практически не сталкиваться, даже если третье равно не ставить ;)
            Про всякие мелочи я говорить не буду, хотя должен заметить что ситуация с this меня весьма печалит.
              +2
              Из новейшего — деструктуризация.
              // в импортах
              import { fooLong as foo } from 'Foo';
              // в коде:
              let { fooLong : foo } = fooObject;
              


              Я, конечно, понимаю, что с деструктуризацией через двоеточие налажали и захотели исправить в импортах (т.к. она менее понятна чем as и блокирует возможность добавления типичной для ES типизации (как в ES4, FlowType, TypeScript)):

              let { foo: string } = fooObject; // fail
              


              И приходится писать какой-то такой ужас:

              let { foo } : { foo: string }  = fooObject;
              


              Ну или отказываться от деструктуризации:
              let foo: string  = fooObject.foo;
              
            –1

            Согласен, разве что чутье может и подводить. Например, сломанная совместимость между питонами 2 и 3.

              +3
              Вы выставляете сломанную совместимость как недостаток, но это было исправление фундаментального недочёта в архитектуре. Основные проблемы там были со строками, а это застарелая болячка — ну не было ещё юникода когда питон писали. Для такого жёсткого решения нужен был как раз тот самый диктатор — самостоятельно сообщество на него никогда бы не решилось. Диктатор, к счастью, был, так что недостаток устранили, пусть на переход и потребовалось чуть ли не десять лет.
              • UFO just landed and posted this here
                0
                Не понятно только как это применять именно к веб-стандартам, где нет единой команды разрабатывающей браузеры и веб-приложения. Веб-разработчики допускают ошибки (а ошибок, например, в разметке реально сложно избежать полностью, ведь контент может быть сторонний, полученный от пользователей или сторонних источников). Разработчики браузеров, в свою очередь, стремятся сделать так, чтобы сайты с ошибками отображались правильно, так как это дает конкурентное преимущество. Разработчики стандартов никак не могут им приказать стандарт соблюдать. Так что приходится писать стандарты по реальной жизни, а не наоборот. Примерно как в случае естественных языков.
                  0

                  Невалидный контент полученный из сторонних источников — это не ошибки вёрстки, а уязвимости архитектуры.

                –1
                Стандарт по кодировке символов нужен, а работа не ведётся. На западе на это видимо всем как-то пофиг, у них во всех языках латиница. Но когда натыкаюсь на домены типа «рф», да ещё и с русскими названиями статей, и хочу вставить прямую ссылку на статьи с этого сайта, получается две вещи. Либо ссылка выходит такая длинная, что не всегда умещается в сообщении, либо она копируется прям кириллицей, и браузер не знает, что делать с такой ссылью (например, Opera). Тем временем китайцев и прочих азиатов огромная доля в мире, о них никто не думает. А с другой стороны как бы да — английский — это международный язык. И раз уж делаете сайты на обозрение всему миру, то извольте выражаться на латинице :(
                  +2
                  >либо она копируется прям кириллицей, и браузер не знает, что делать с такой ссылью (например, Opera).

                  Чего? Наверное вам стоит обновится, Уже Opera 7.0 вышедшая в 13 лет назад знала кириллические имена (ещё RFC на них не был окончательно утверждён).
                    0
                    Опера (или возможно какие-то другие браузеры вроде Хром) не умеет переходить по ссылке, в которой написано название кириллицей, типа такого:

                    [ url=«http://сайт.рф» ] Сайт [ /url ]

                    Не открывается по клику. А если вставить в адресную строку, откроется. Некоторые люди не умеют копировать текст ссылки, да и неудобно это. Получается так, ясное дело, на всяких форумах, когда пользователи сами не знают, что делают. Это скорее проблема конкретных браузеров. Но был бы стандарт, не было бы такой проблемы.
                      0
                      Да ладно? Сейчас проверим:
                      соснули.рф
                      Работает!

                      Вот что плохо, хром копирует из адресной строки в Punycode. Я думал от такого «xn--h1affdobp.xn--p1ai» уже давно все избавились.
                        0
                        Причём как новая так и старая опера 12.16 кроме корректного перехода и копирует правильно, без перевода в Punycode.
                        Так что нужно больше информации в каких конкретно случаях не получается перейти.
                          0
                          И правда, работает. Значит, это либо я что-то напутал, либо полгода или год назад была такая проблема.

                          Punycode — да. А когда ещё и страницы на русском, вообще получаются страшные шифры со знаками процента, как какие-то руны из договора о кредите.

                          Я, когда с этим столкнулся, в первую очередь подумал: «Ну, зачем было вообще кириллические домены вводить». Мне показалось, что это сделано для галочки, из каких-то политически-агитационных соображений. Не хочу в эту тему скатываться, но кто-то явно себе галочку на этом пункте поставил.
                    0
                    Причем здесь английский и латиница? Есть отличный алфавит для URI — это ASCII. И больше ничего там не нужно. Я честно говоря не владею вообще ни одним иностранным. Ну то-есть английским в мере достаточной для чтения мануалов, но не более. И тем не менее я не вижу ни одной причины для локализации URI. Я даже не представляю каким они могут быть. Если у вас пользователи не знакомые с URI и ASCII вынуждены работать с ним — у вас криво спроектированное приложение. Это не должно становится проблемой URI.
                      0
                      А человекочитаемость не причина для локализации урлов?
                        0
                        Нет, не причина. По двум причинами:
                        1 Человеку не надо их читать.
                        2 Когда человеку надо их читать более короткий алфавит предпочтительнее. Если вам нужно ЧПУ — используйте в них цифры. Арабские. Их читать и передавать легко. А вот попробуйте задиктовать по телефону «человекочитаемый» УРЛ: /раздел/вшбци/напровление переподготовки персанала/. Хорошо подумайте сколько где пробелов и не забудьте про опечатки ;)
                          0
                          Если человеку не нужно читать урлы, то можно использовать ip-адреса и uuid-идентификаторы ресурсов, машинам плевать, так даже удобнее. Прелесть ЧПУ в том, что можно получить информацию о ресурсе еще до обращения к нему.
                            0
                            Многим людям все равно приходится читать УРЛ, но эти освоятся с ASCII, точнее уже освоились.
                              +3
                              В том-то и дело, что url изначально предназначен для чтения человеком, иначе зачем столько мороки с DNS, покупкой доменов, а также с «виртуальными структурами папок» во всяких cms, где по сути всё открывается через index.php в корне сайта. Чтобы пользователь знал, что по адресу «http://biblioteka.ru/articles/ribalka/spinningi.html» лежит статья по спиннингам из раздела «рыбалка». Хотя такого html нет, и папок таких нет, а есть просто внешний вид ссылки, созданный исходя из классификации статей.
                    0
                    «https://ru.wikipedia.org/wiki/Ы», скопированная из адресной строки в блокнот, превращается в «https://ru.wikipedia.org/wiki/%D0%AB», причём в табличке кодов буквы Ы на этой странице википедии, среди (Юникод, ISO 8859-5, KOI 8, Windows 1251) никаких D0 и AB нет. Как так?
                      0

                      Это utf8 представление символа u+042b. См., например, http://unicode-table.com/ru/042B/

                        0
                        Спасибо, т.е. то что в табличке википедии подписано как «Юникод» на самом деле двухбайтный utf-16 (Ы — 0x042B), а переменнобайтного utf-8 (длина которого, в частности, для кириллицы также равна двум байтам, Ы — 0xD0AB) в этой табличке просто нету.
                          0

                          Там написан codepoint. Младшие codepoint'ы представимы в виде двух байт ucs2 или utf16 (отличается от ucs2 наличием суррогатов) as is.

                            0
                            Причём codepoint, в общем случае, совпадает с utf-32 (codepoint = u+042B, utf-32 = 0x0000042B), а не с uft-16.
                            utf-16 не 2-байтный получается, а тоже переменнобайтный (2 или 4) из-за этих суррогатных пар.
                            utf-32 хоть и постоянной длины (4-байтный), но символов не 2³² из-за общего ограничения кол-ва символов юникода, появившееся из-за uft-16, созданного из-за необходимости совместимости с юникодом первой версии, в котором было 2¹⁶ символов. А общее современное кол-во символов 2¹⁶ − 2*2¹⁰ + 2*2¹⁰*2¹⁰.
                            Надеюсь ещё одного расширения юникода, с обратной совместимостью, не будет, и 2¹⁶ − 2*2¹⁰ + 2*2¹⁰*2¹⁰ хватит всем :)
                            В общем, сложнее чем я раньше думал.

                            P.S. https://en.wikipedia.org/wiki/Ы табличка полнее, есть utf-8, вопроса бы не возникло :):
                              0

                              Unicode сложен ибо языки сложны и полны ужасов. Нормализация (nfc/nfd и nfkc/nfkd), локалезависимы to_lower/to_upper, collation, экзотика типа bocu-1 вместо utf-8. Грабли разложены плотным слоем на всём пути.

                    Only users with full accounts can post comments. Log in, please.