Когда веб-серверы отправляют страницу, почему они не отправляют все необходимые CSS, JS и изображения без запроса?


45

Когда веб-страница содержит один CSS-файл и изображение, почему браузеры и серверы тратят время на этот традиционный трудоемкий маршрут:

  1. браузер отправляет начальный запрос GET для веб-страницы и ожидает ответа сервера.
  2. Браузер отправляет еще один запрос GET для файла CSS и ждет ответа сервера.
  3. браузер отправляет еще один запрос GET для файла изображения и ожидает ответа сервера.

Когда вместо этого они могли бы использовать этот короткий, прямой, экономящий время маршрут?

  1. Браузер отправляет запрос GET для веб-страницы.
  2. Веб-сервер отвечает ( index.html, затем style.css и image.jpg )

2
Любой запрос не может быть сделан, пока веб-страница не будет извлечена, конечно. После этого запросы выполняются по порядку при чтении HTML. Но это не значит, что за один раз делается только один запрос. На самом деле делается несколько запросов, но иногда между запросами существуют зависимости, и некоторые из них должны быть разрешены до того, как страница будет правильно нарисована. Браузеры иногда делают паузу, когда запрос удовлетворяется, прежде чем появляются для обработки других ответов, создавая впечатление, что каждый запрос обрабатывается по одному за раз. Реальность в большей степени связана с браузером, поскольку они, как правило, требуют больших ресурсов.
closetnoc

20
Я удивлен, что никто не упомянул кеширование. Если у меня уже есть этот файл, он мне не нужен.
Кори Огберн

2
Этот список может быть сотни вещей в длину. Хотя это короче, чем на самом деле отправка файлов, это все еще довольно далеко от оптимального решения.
Кори Огберн

1
На самом деле, я никогда не посещал веб-страницу с более чем 100 уникальными ресурсами ..
Ахмед

2
@AhmedElsoobky: браузер не знает, какие ресурсы можно отправить в виде заголовка cached-resources без предварительного извлечения самой страницы. Это также может быть кошмаром в отношении конфиденциальности и безопасности, если при извлечении страницы сервер сообщает, что у меня есть другая страница в кэше, которая, возможно, контролируется другой организацией, чем исходная страница (веб-сайт с несколькими арендаторами).
Ли Райан

Ответы:


63

Краткий ответ: «Потому что HTTP не был разработан для этого».

Тим Бернерс-Ли не разработал эффективный и расширяемый сетевой протокол. Его единственная цель - простота. (Профессор моего сетевого класса в колледже сказал, что он должен был оставить работу профессионалам.) Проблема, которую вы изложили, является лишь одной из многих проблем с протоколом HTTP. В своем первоначальном виде:

  • Не было версии протокола, только запрос ресурса
  • Заголовков не было
  • Каждый запрос требует нового соединения TCP
  • Не было сжатия

Протокол был позже пересмотрен, чтобы решить многие из этих проблем:

  • Запросы были версионными, теперь запросы выглядят как GET /foo.html HTTP/1.1
  • Добавлены заголовки для метаинформации с запросом и ответом
  • Соединения были разрешены для повторного использования с Connection: keep-alive
  • Были введены фрагментированные ответы, позволяющие повторно использовать соединения, даже если размер документа не известен заранее.
  • Gzip сжатие было добавлено

На этом этапе HTTP был принят, насколько это возможно, без нарушения обратной совместимости.

Вы не первый человек, который предлагает, чтобы страница и все ее ресурсы были переданы клиенту. На самом деле, Google разработал протокол, который может сделать так называемый SPDY .

Сегодня и Chrome, и Firefox могут использовать SPDY вместо HTTP для серверов, которые его поддерживают. С веб-сайта SPDY его основные функции по сравнению с HTTP:

  • SPDY позволяет клиенту и серверу сжимать заголовки запросов и ответов, что сокращает использование полосы пропускания, когда подобные заголовки (например, файлы cookie) отправляются многократно для нескольких запросов.
  • SPDY разрешает множественные одновременные мультиплексированные запросы по одному соединению, экономя при двусторонних соединениях между клиентом и сервером и предотвращая блокировку запросов с более высоким приоритетом для ресурсов с низким приоритетом.
  • SPDY позволяет серверу активно передавать клиенту ресурсы, которые, как он знает, потребуется клиенту (например, файлы JavaScript и CSS), не дожидаясь, пока клиент запросит их, позволяя серверу эффективно использовать неиспользованную полосу пропускания.

Если вы хотите обслуживать свой сайт с SPDY для браузеров, которые его поддерживают, вы можете это сделать. Например, в Apache есть mod_spdy .

SPDY стала основой для HTTP версии 2 с технологией push-сервера.


2
Черт хороший и осознанный ответ! Веб-браузеры носят последовательный характер, и запросы могут быть сделаны довольно быстро. Один взгляд на файл журнала показывает, что запросы на ресурсы выполняются довольно быстро после анализа HTML. Что есть, то есть. Неплохая система, но не настолько эффективная, как могла бы быть.
closetnoc

6
Для справки, SPDY - это не Святой Грааль. Он делает некоторые вещи хорошо, но создает другие проблемы. Вот одна статья, содержащая некоторые пункты, говорящие против SPDY.
Jost

3
Я настоятельно рекомендую всем, кто заинтересован в этом, прочитать критику в ссылке @Jost. Это дает вам подсказку о сложности, связанной с выяснением того, как сделать очень часто реализуемую вещь не просто постепенно, а гораздо лучше, чтобы все начали ее использовать . Легко представить себе улучшение, которое делает вещи несколько лучше для относительно большого подмножества вариантов использования. Улучшить ситуацию таким образом, чтобы все начали использовать ваш новый протокол, потому что он настолько лучше, что его стоит заплатить за изменение - это совсем другой вопрос, и его нелегко сделать.
MST

11
он должен был оставить работу профессионалам : если бы он сделал это, им потребовалось бы шесть лет, чтобы разработать стандарт, который устарел бы в день его появления, и вскоре появилось бы дюжина конкурирующих стандартов. Кроме того, нужны ли профессионалам разрешение от кого-то? Почему они не сделали это сами?
Шантну Тивари

2
Откровенно говоря, тогда не было квалифицированных специалистов. Никто не знает, как создать всемирную паутину, потому что никто никогда не создавал ее. Тим не придумал концепцию гипермедиа, у него был опыт работы с различными локальными гипермедиа системами за десять лет до того, как он написал предложение об «управлении информацией» для решения проблемы «потери информации» в CERN.
Ли Райан

14

Ваш веб-браузер не знает о дополнительных ресурсах, пока не загрузит веб-страницу (HTML) с сервера, которая содержит ссылки на эти ресурсы.

Вы можете спросить, почему сервер просто не анализирует собственный HTML и не отправляет все дополнительные ресурсы в веб-браузер во время первоначального запроса веб-страницы? Это потому, что ресурсы могут быть распределены по нескольким серверам, и веб-браузеру могут не понадобиться все эти ресурсы, поскольку некоторые из них уже кэшированы или могут не поддерживать их.

Веб-браузер поддерживает кэш ресурсов, поэтому ему не нужно загружать одни и те же ресурсы снова и снова с серверов, на которых они размещены. При навигации по разным страницам на веб-сайте, которые используют одну и ту же библиотеку jQuery, вам не нужно загружать эту библиотеку каждый раз, только в первый раз.

Поэтому, когда веб-браузер получает веб-страницу с сервера, он проверяет, какие связанные ресурсы у него уже нет в кэше, а затем выполняет дополнительные HTTP-запросы для этих ресурсов. Довольно простой, очень гибкий и расширяемый.

Веб-браузер обычно может выполнять два HTTP-запроса параллельно. Это мало чем отличается от AJAX - они оба являются асинхронными методами загрузки веб-страниц - асинхронная загрузка файлов и асинхронная загрузка контента. С помощью keep-alive мы можем сделать несколько запросов, используя одно соединение, а с помощью конвейерной обработки мы можем сделать несколько запросов, не дожидаясь ответов. Оба эти метода очень быстрые, потому что большая часть накладных расходов обычно связана с открытием / закрытием соединений TCP:

поддержания активности

конвейерная

Немного истории веб ...

Веб-страницы начинались как обычное текстовое электронное письмо, и вокруг этой идеи создавались компьютерные системы, образуя несколько бесплатную коммуникационную платформу; в то время веб-серверы были проприетарными. Позже, в «спецификацию электронной почты» было добавлено больше слоев в форме дополнительных типов MIME, таких как изображения, стили, сценарии и т. Д. В конце концов, MIME расшифровывается как Многоцелевое расширение Internet Mail . Рано или поздно у нас появились мультимедийные сообщения электронной почты, стандартизированные веб-серверы и веб-страницы.

HTTP требует, чтобы данные передавались в контексте почтовых сообщений, хотя чаще всего это не электронные письма.

По мере развития подобной технологии она должна позволять разработчикам постепенно внедрять новые функции, не нарушая существующее программное обеспечение. Например, когда в спецификацию добавляется новый тип MIME, скажем, JPEG, веб-серверам и веб-браузерам потребуется некоторое время для реализации этого. Вы не просто внезапно вводите JPEG в спецификацию и начинаете отправлять его во все веб-браузеры, вы позволяете веб-браузеру запрашивать ресурсы, которые он поддерживает, что радует всех и продвигает технологию. Нужно ли программе чтения с экрана все файлы JPEG на веб-странице? Возможно нет. Нужно ли вам загружать кучу файлов Javascript, если ваше устройство не поддерживает Javascript? Возможно нет. Нужно ли Googlebot загружать все ваши файлы Javascript, чтобы правильно проиндексировать ваш сайт? Нет.

Источник: я разработал основанный на событиях веб-сервер, такой как Node.js. Это называется Rapid Server .

Рекомендации:

Дальнейшее чтение:


Ну, на самом деле, мы можем позаботиться обо всех этих побочных проблемах (таких как: кэш, заголовок Content-Type .. и т. Д.), Есть обходные пути для решения этих проблем. И, как я предложил в комментариях к посту выше, мы можем использовать что-то вроде этого заголовка> Cached-Resources: image.jpg; style.css; решить проблему с кешированием .. (Если у вас есть время, посмотрите на комментарии выше ...)
Ахмед

Да, эта идея приходила мне в голову раньше, но для HTTP это просто слишком много, и это не решает тот факт, что ресурсы могут быть распределены по нескольким серверам. Более того, я не думаю, что предложенный вами метод экономии времени действительно сэкономит время, потому что данные будут отправляться в виде потока независимо от того, как вы на это смотрите, а с поддержкой активности 100 одновременных HTTP-запросов по существу становятся 1 запросом. Технология и возможности, которые вы предлагаете, похоже, уже существуют. См. En.wikipedia.org/wiki/HTTP_persistent_connection
perry

@perry: Что бы вы подумали об идее альтернативы https://для отправки больших общедоступных файлов, которые должны быть аутентифицированы, но не должны быть конфиденциальными: включите в URL хэш определенных частей заголовка легитимного ответа, который, в свою очередь, может включать подпись или хэш полезных данных, и браузеры проверяют полученные данные по заголовку? Такой дизайн не только сохранит некоторые шаги рукопожатия SSL, но, что более важно, позволит кэшировать прокси. Получите URL через ссылку SSL, и данные могут быть переданы откуда угодно.
суперкат

11

Потому что они не знают, что это за ресурсы. Ресурсы, необходимые для веб-страницы, закодированы в HTML. Пользовательский агент может запросить y только после того, как парсер определит, что это за активы.

Кроме того, как только эти активы известны, они должны обслуживаться индивидуально, чтобы можно было обслуживать надлежащие заголовки (то есть тип контента), чтобы пользовательский агент знал, как с ним обращаться.


2
Особенно, если вы используете что-то вроде require.js. Браузер запрашивает только то, что ему нужно. Представьте, что вам нужно загрузить все сразу ...
Аран Малхолланд,

2
Это правильный ответ, который, по-видимому, отсутствует у большинства комментаторов - для того, чтобы сервер проактивно отправлял ресурсы, ему нужно знать, что они из себя представляют, что означает, что серверу придется анализировать HTML.

1
Но возникает вопрос, почему веб- сервер не отправляет ресурсы, а не почему клиент не может запросить их одновременно. Очень легко представить мир, в котором на серверах есть пакет связанных ресурсов, которые отправляются вместе, и для его создания не требуется анализ HTML.
Дэвид Мейстер

@DavidMeister Поскольку сервер не всегда знает, чего хочет клиент - веб-сканер для поисковой системы может не заботиться о CSS / JS, и помимо документа есть много других ресурсов, связанных с этим, нет необходимости отправлять весь RSS кормить вниз в пакет для веб - браузера (большую часть контента, вероятно , в уже в HTML), в то время как для чтения каналов может просто разобрать <head>элемент ищет RSS альтернативных ссылок , чтобы найти только что - клиент может отправить список что его интересует, но тогда ему нужно знать, что доступно, и мы вернемся к началу
Zhaph - Ben Duguid

@ Zhaph-BenDuguid Я говорю об альтернативном мире, чтобы подчеркнуть, что ответ имеет такое же отношение к тому, как работает протокол, как и все остальное. Кроме того, сервер может быстрее отправлять все данные, даже если это не нужно. Вы по сути компенсируете проблемы задержки с использованием пропускной способности
Дэвид Майстер

8

Потому что, в вашем примере, веб-сервер будет всегда отправлять CSS и изображения независимо от того, есть ли их у клиента, что приводит к значительному снижению пропускной способности (и, следовательно, замедлению соединения , а не ускорению за счет уменьшения задержки, что, по-видимому, было вашим намерением). Обратите внимание, что файлы CSS, JavaScript и изображения обычно отправляются с очень большим временем истечения именно по этой причине (например, когда вам нужно изменить их, вы просто изменяете имя файла, чтобы принудительно создать новую копию, которая снова будет кэшироваться в течение длительного времени).

Теперь вы можете попытаться обойти эту потерю пропускной способности, сказав: « ОК, но клиент может указать, что у него уже есть некоторые из этих ресурсов, поэтому сервер не будет отправлять его снова ». Что-то вроде:

GET /index.html HTTP/1.1
Host: www.example.com
If-None-Match: "686897696a7c876b7e"
Connection: Keep-Alive

GET /style.css HTTP/1.1
Host: www.example.com
If-None-Match: "70b26618ce2c246c71"

GET /image.png HTTP/1.1
Host: www.example.com
If-None-Match: "16d5b7c2e50e571a46"

И затем получают только те файлы, которые не изменились, и отправляются через одно TCP-соединение (с использованием HTTP-конвейеризации через постоянное соединение). И угадайте, что? Это то, как это уже работает (вы можете также использовать If-Modified-Since вместо If-None-Match ).


Но если вы действительно хотите уменьшить задержку, тратя много трафика (как в исходном запросе), вы можете сделать это сегодня, используя стандартный HTTP / 1.1 при разработке вашего сайта. Причина, по которой большинство людей этого не делают, заключается в том, что они не думают, что это того стоит.

Чтобы сделать это, вам не нужно иметь CSS или в JavaScript в отдельном файле, вы можете включить их в основной HTML файл с помощью <style>и <script>тегов (вы , вероятно , даже не нужно делать это вручную, ваш шаблон двигатель , вероятно , может сделать это автоматически) , Вы даже можете включить изображения в файл HTML, используя URI данных , например так:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />

Конечно, кодирование base64 немного увеличивает использование полосы пропускания, но если вы не заботитесь о потраченной пропускной способности, это не должно быть проблемой.

Теперь, если вы действительно заботитесь, вы можете даже сделать свои веб-скрипты достаточно умными, чтобы получить лучшее из обоих миров: по первому запросу (у пользователя нет cookie), отправляйте все (CSS, JavaScript, изображения), встроенные только в один HTML файл, как описано выше, добавьте ссылку rel = "prefetch" теги для внешних копий файлов и добавьте cookie. Если пользователь уже имеет печенье (например, он посетил ранее), а затем отправить его просто обычный HTML с <img src="example.jpg">, и <link rel="stylesheet" type="text/css" href="style.css">т.д.

Поэтому при первом посещении браузер запрашивает только один HTML-файл и получает и показывает все. Затем он будет (при простое) предварительно загружать указанные внешние CSS, JS, изображения. При следующем посещении пользователя браузер будет запрашивать и получать только измененные ресурсы (возможно, просто новый HTML).

Дополнительные данные изображений CSS + JS + будут отправлены только дважды, даже если вы кликнули сотни раз на веб-сайте. Намного лучше, чем сотни раз, как предложено вами. И он никогда (ни в первый раз, ни в следующий раз) не будет использовать более одного увеличения времени ожидания.

Теперь, если это звучит как слишком много работы, и вы не хотите использовать другой протокол, такой как SPDY , уже есть модули, такие как mod_pagespeed для Apache, которые могут автоматически выполнять часть этой работы за вас (объединяя несколько файлов CSS / JS). в один, автоматически вставляя небольшой CSS и минимизируя их, создавайте небольшие встроенные изображения-заполнители, ожидая загрузки оригиналов, ленивой загрузки изображений и т. д.), не требуя изменения одной строки веб-страницы.


3
Я думаю , что это правильный ответ.
el.pescado

7

HTTP2 основан на SPDY и делает именно то, что вы предлагаете:

На высоком уровне HTTP / 2:

  • является двоичным, а не текстовым
  • полностью мультиплексирован, а не упорядочен и блокируется
  • поэтому можно использовать одно соединение для параллелизма
  • использует сжатие заголовка, чтобы уменьшить накладные расходы
  • позволяет серверам активно «проталкивать» ответы в клиентские кеши

Больше доступно на HTTP 2 Faq


3

Потому что это не предполагает, что эти вещи действительно необходимы .

Протокол не определяет никакой специальной обработки для какого-либо конкретного типа файла или пользовательского агента. Он не знает разницы между, скажем, файлом HTML и изображением PNG. Чтобы выполнить то, что вы просите, веб-сервер должен будет определить тип файла, разобрать его, чтобы выяснить, на какие другие файлы он ссылается, а затем определить, какие другие файлы действительно необходимы, учитывая, что вы собираетесь делать файл . Есть три большие проблемы с этим.

Первая проблема заключается в том, что не существует стандартного надежного способа определения типов файлов на стороне сервера . HTTP управляется через механизм Content-Type, но это не помогает серверу, который должен сам разобраться с этим (частично, чтобы он знал, что поместить в Content-Type). Расширения имен файлов широко поддерживаются, но хрупки и легко одурачены, иногда в злонамеренных целях. Метаданные файловой системы менее хрупки, но большинство систем не очень хорошо их поддерживают, поэтому серверы даже не беспокоятся. Отслеживание содержимого (как fileпытаются сделать некоторые браузеры и команда Unix ) может быть надежным, если вы хотите сделать его дорогим, но надежное отслеживание слишком дорого, чтобы быть практичным на стороне сервера, а дешевое отслеживание недостаточно надежно.

Вторая проблема заключается в том, что анализ файла является дорогостоящим, вычислительно говоря . Это несколько связано с первым, в том смысле, что вам нужно было бы проанализировать файл множеством различных потенциальных способов, если вы хотите надежно прослушивать содержимое, но это также применяется после того, как вы определили тип файла, потому что вам нужно выяснить, что ссылки. Это не так плохо, когда вы делаете только несколько файлов одновременно, как это делает браузер, но веб-сервер должен обрабатывать сотни или тысячи запросов одновременно. Это суммирует, и если оно заходит слишком далеко, оно может на самом деле замедлить работу больше, чем несколько запросов. Если вы когда-либо посещали ссылку из Slashdot или аналогичных сайтов, и обнаружили, что сервер мучительно медленен из-за высокой загрузки, вы увидели этот принцип в действии.

Третья проблема заключается в том, что у сервера нет возможности узнать, что вы собираетесь делать с файлом . Браузеру могут понадобиться ссылки на файлы в HTML, но это может не произойти, в зависимости от точного контекста, в котором выполняется файл. Это было бы достаточно сложно, но в Интернете есть нечто большее, чем просто браузеры: между пауками, агрегаторами каналов и скребками страниц, существует множество видов пользовательских агентов, которым не нужны файлы, на которые ссылаются в HTML : они заботиться только о самом HTML. Отправка этих других файлов таким пользовательским агентам приведет только к потере пропускной способности.

Суть в том, что выяснение этих зависимостей на стороне сервера - больше проблем, чем оно того стоит . Вместо этого они позволяют клиенту понять, что ему нужно.


Если мы собираемся разработать новый протокол или исправить уже существующий, мы можем так или иначе позаботиться обо всех этих проблемах! И веб-сервер будет анализировать файлы только один раз, а затем он может классифицировать их в зависимости от определенных правил, чтобы он мог определять приоритетность файлов для отправки в первую очередь ... и т. Д., И веб-сервер не должен знать, что я намерен делать с этими файлами, он просто должен знать, что отправлять, когда делать и в зависимости от того, какие правила .. (веб-боты и пауки не проблема, у них будет другое поведение - у них есть уникальные заголовки user-agent- ..)
Ахмед

@AhmedElsobky: То, о чем вы говорите, больше похоже на конкретную реализацию, чем на сетевой протокол. Но он действительно должен знать, что вы собираетесь делать с файлами, прежде чем он сможет определить, что отправлять: в противном случае он неизбежно отправит файлы, которые не нужны многим пользователям. Вы не можете доверять строкам User-Agent, поэтому вы не можете использовать их для определения намерений пользователя.
Ложная
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.