Получение данных с сервера
Другой очень распространённой задачей в современных веб-сайтах и приложениях является получение отдельных элементов данных с сервера для обновления разделов веб-страницы без необходимости загрузки всей новой страницы. Эта, казалось бы, небольшая деталь оказала огромное влияние на производительность и поведение сайтов, поэтому в этой статье мы объясним концепцию и рассмотрим технологии, которые делают это возможным, например XMLHttpRequest и API Fetch.
| Необходимые условия: | Основы JavaScript (см. первые шаги, структурные элементы, объекты JavaScript), основы клиентских API |
|---|---|
| Задача: | Узнать, как извлекать данные с сервера и использовать их для обновления содержимого веб-страницы. |
В чем проблема?
Первоначальная загрузка страницы в Интернете была простой — вы отправляли запрос на сервер web-сайта, и если всё работает, как и должно, то вся необходимая информация о странице будет загружена и отображена на вашем компьютере.
Проблема с этой моделью заключается в том, что всякий раз, когда вы хотите обновить любую часть страницы, например, чтобы отобразить новый набор продуктов или загрузить новую страницу, вам нужно снова загрузить всю страницу. Это очень расточительно и приводит к плохому пользовательскому опыту, особенно по мере того, как страницы становятся все более сложными.
Появление Ajax
Это привело к созданию технологий, позволяющих веб-страницам запрашивать небольшие фрагменты данных (например, HTML, XML, JSON или обычный текст) и отображать их только при необходимости, помогая решать проблему, описанную выше.
Это достигается с помощью таких API, как XMLHttpRequest или — более новой — Fetch API. Эти технологии позволяют веб-страницам напрямую обрабатывать запросы HTTP для определённых ресурсов, доступных на сервере, и форматировать результирующие данные по мере необходимости перед их отображением.
Примечание: Вначале эта общая техника была известна как Асинхронный JavaScript и XML (Ajax), поскольку она, как правило, использовала XMLHttpRequest для запроса данных XML. В наши дни это обычно не так (вы, скорее всего, будете использовать XMLHttpRequest или Fetch для запроса JSON), но результат все тот же, и термин «Ajax» по-прежнему часто используется для описания этой техники.

Модель Ajax предполагает использование веб-API в качестве прокси для более разумного запроса данных, а не просто для того, чтобы браузер перезагружал всю страницу. Давайте подумаем о значении этого:
- Перейдите на один из ваших любимых сайтов, богатых информацией, таких как Amazon, YouTube, CNN и т.д., и загрузите его.
- Теперь найдите что-нибудь, например, новый продукт. Основной контент изменится, но большая часть информации, подобной заголовку, нижнему колонтитулу, навигационному меню и т. д., останется неизменной.
Это действительно хорошо, потому что:
- Обновления страницы намного быстрее, и вам не нужно ждать перезагрузки страницы, а это означает, что сайт работает быстрее и воспринимается более отзывчивым.
- Меньше данных загружается при каждом обновлении, что означает меньшее потребление пропускной способности. Это не может быть такой большой проблемой на рабочем столе в широкополосном подключении, но это серьёзная проблема на мобильных устройствах и в развивающихся странах, которые не имеют повсеместного быстрого интернет-сервиса.
Чтобы ускорить работу, некоторые сайты также сохраняют необходимые файлы и данные на компьютере пользователя при первом обращении к сайту, а это означает, что при последующих посещениях они используют локальные версии вместо загрузки свежих копий, как при первой загрузке страницы. Содержимое загружается с сервера только при его обновлении.

Основной запрос Ajax
Давайте посмотрим, как обрабатывается такой запрос, используя как XMLHttpRequest , так и Fetch. В этих примерах мы будем запрашивать данные из нескольких текстовых файлов и использовать их для заполнения области содержимого.
Этот набор файлов будет действовать как наша поддельная база данных; в реальном приложении мы с большей вероятностью будем использовать серверный язык, такой как PHP, Python или Node, чтобы запрашивать наши данные из базы данных. Здесь, однако, мы хотим сохранить его простым и сосредоточиться на стороне клиента.
XMLHttpRequest
XMLHttpRequest (который часто сокращается до XHR) является довольно старой технологией сейчас — он был изобретён Microsoft в конце 1990-х годов и уже довольно долго стандартизирован в браузерах.
- Чтобы начать этот пример, создайте локальную копию ajax-start.html и четырёх текстовых файлов — verse1.txt, verse2.txt, verse3.txt и verse4.txt — в новом каталоге на вашем компьютере. В этом примере мы загрузим другое стихотворение (который вы вполне можете распознать) через XHR, когда он будет выбран в выпадающем меню.
- Внутри элемента <script> добавьте следующий код. В нем хранится ссылка на элементы <select> и <pre> в переменных и определяется onchange обработчика событий, так что, когда значение select изменяется, его значение передаётся вызываемой функции updateDisplay() в качестве параметра.
Обслуживание вашего примера с сервера
Некоторые браузеры (включая Chrome) не будут запускать запросы XHR, если вы просто запускаете пример из локального файла. Это связано с ограничениями безопасности (для получения дополнительной информации о безопасности в Интернете, ознакомьтесь с Website security).
Чтобы обойти это, нам нужно протестировать пример, запустив его через локальный веб-сервер. Чтобы узнать, как это сделать, прочитайте Как настроить локальный тестовый сервер?
Fetch
API-интерфейс Fetch — это, в основном, современная замена XHR — недавно он был представлен в браузерах для упрощения асинхронных HTTP-запросов в JavaScript, как для разработчиков, так и для других API, которые строятся поверх Fetch.
Давайте преобразуем последний пример, чтобы использовать Fetch!
- Сделайте копию своего предыдущего готового каталога примеров. (Если вы не работали над предыдущим упражнением, создайте новый каталог и внутри него создайте копии xhr-basic.html и четырёх текстовых файлов — verse1.txt, verse2.txt, verse3.txt и verse4.txt.)
- Внутри функции updateDisplay() найдите код XHR:
Итак, что происходит в коде Fetch?
Прежде всего, мы вызываем метод fetch() , передавая ему URL-адрес ресурса, который мы хотим получить. Это современный эквивалент request.open() в XHR, плюс вам не нужен эквивалент .send() .
После этого вы можете увидеть метод .then() , прикреплённый в конец fetch() — этот метод является частью Promises — современная функция JavaScript для выполнения асинхронных операций. fetch() возвращает промис, который разрешает ответ, отправленный обратно с сервера, — мы используем .then() для запуска некоторого последующего кода после того, как промис будет разрешено, что является функцией, которую мы определили внутри неё. Это эквивалент обработчика события onload в XHR-версии.
Эта функция автоматически передаёт ответ от сервера в качестве параметра, когда обещает fetch() . Внутри функции мы берём ответ и запускаем его метод text() (en-US), который в основном возвращает ответ как необработанный текст. Это эквивалент request.responseType = ‘text’ в версии XHR.
Вы увидите, что text() также возвращает промис, поэтому мы привязываем к нему другой .then() , внутри которого мы определяем функцию для получения необработанного текста, который выполняет text() .
Внутри функции внутреннего промиса мы делаем то же самое, что и в версии XHR, — устанавливаем текстовое содержимое <pre> в текстовое значение.
Помимо промисов
Промисы немного запутывают первый раз, когда вы их встречаете, но не беспокойтесь об этом слишком долго. Через некоторое время вы привыкнете к ним, особенно, когда вы узнаете больше о современных JavaScript-API. Большинство из них в большей степени основаны на промисах.
Давайте посмотрим на структуру промисов сверху, чтобы увидеть, можем ли мы ещё немного понять это:
В первой строке говорится: «Получить ресурс, расположенный по адресу url» (fetch(url) ) и «затем запустить указанную функцию, когда промис будет разрешено» ( .then(function() < . >) ). «Resolve» означает «завершить выполнение указанной операции в какой-то момент в будущем». Указанная операция в этом случае заключается в извлечении ресурса с указанного URL (с использованием HTTP-запроса) и возврата ответа для нас, чтобы что-то сделать.
Фактически, функция, переданная в then() , представляет собой кусок кода, который не запускается немедленно — вместо этого он будет работать в какой-то момент в будущем, когда ответ будет возвращён. Обратите внимание, что вы также можете сохранить своё промис в переменной и цепочку .then() вместо этого. Ниже код будет делать то же самое:
Поскольку метод fetch() возвращает промис, который разрешает HTTP-ответ, любая функция, которую вы определяете внутри .then() , прикованная к концу, будет автоматически передаваться как параметр. Вы можете вызвать параметр, который вам нравится — приведённый ниже пример будет работать:
Но имеет смысл называть параметр тем, что описывает его содержимое!
Теперь давайте сосредоточимся только на функции:
Объект ответа имеет метод text() (en-US), который берёт необработанные данные, содержащиеся в теле ответа, и превращает его в обычный текст, который является форматом, который мы хотим в нем А также возвращает промис (который разрешает полученную текстовую строку), поэтому здесь мы используем другой .then() , внутри которого мы определяем другую функцию, которая диктует что мы хотим сделать с этой текстовой строкой. Мы просто устанавливаем свойство textContent элемента <pre> нашего стихотворения равным текстовой строке, так что это получается довольно просто.
Также стоит отметить, что вы можете напрямую связывать несколько блоков промисов ( .then() , но есть и другие типы) на конце друг друга, передавая результат каждого блока следующему блоку по мере продвижения по цепочке , Это делает промисы очень мощными.
Следующий блок делает то же самое, что и наш оригинальный пример, но написан в другом стиле:
Многие разработчики любят этот стиль больше, поскольку он более плоский и, возможно, легче читать для более длинных цепочек промисов — каждое последующее промис приходит после предыдущего, а не внутри предыдущего (что может стать громоздким). Единственное отличие состоит в том, что мы должны были включить оператор return перед response.text() , чтобы заставить его передать результат в следующую ссылку в цепочке.
Какой механизм следует использовать?
Это действительно зависит от того, над каким проектом вы работаете. XHR существует уже давно и имеет отличную кросс-браузерную поддержку. Fetch and Promises, с другой стороны, являются более поздним дополнением к веб-платформе, хотя они хорошо поддерживаются в браузере, за исключением Internet Explorer и Safari (которые на момент написания Fetch были доступны в своём предварительный просмотр технологии).
Если вам необходимо поддерживать старые браузеры, тогда может быть предпочтительным решение XHR. Если, однако, вы работаете над более прогрессивным проектом и не так обеспокоены старыми браузерами, то Fetch может быть хорошим выбором.
Вам действительно нужно учиться — Fetch станет более популярным, так как Internet Explorer отказывается от использования (IE больше не разрабатывается, в пользу нового браузера Microsoft Edge), но вам может понадобиться XHR ещё некоторое время.
Более сложный пример
Чтобы завершить статью, мы рассмотрим несколько более сложный пример, который показывает более интересные применения Fetch. Мы создали образец сайта под названием The Can Store — это вымышленный супермаркет, который продаёт только консервы. Вы можете найти этот пример в прямом эфире на GitHub и посмотреть исходный код.

По умолчанию на сайте отображаются все продукты, но вы можете использовать элементы управления формы в столбце слева, чтобы отфильтровать их по категориям, поисковому запросу или и тому и другому.
Существует довольно много сложного кода, который включает фильтрацию продуктов по категориям и поисковым запросам, манипулирование строками, чтобы данные отображались правильно в пользовательском интерфейсе и т.д. Мы не будем обсуждать все это в статье, но вы можете найти обширные комментарии в коде (см. can-script.js).
Однако мы объясним код Fetch.
Первый блок, который использует Fetch, можно найти в начале JavaScript:
Это похоже на то, что мы видели раньше, за исключением того, что второй промис находится в условном выражении. В этом случае мы проверяем, был ли возвращённый ответ успешным — свойство response.ok (en-US) содержит логическое значение, которое true , если ответ был в порядке (например, 200 meaning «OK») или false , если он не увенчался успехом.
Если ответ был успешным, мы выполняем второй промис — на этот раз мы используем json() (en-US), а не text() (en-US), так как мы хотим вернуть наш ответ как структурированные данные JSON, а не обычный текст.
Если ответ не увенчался успехом, мы выводим сообщение об ошибке в консоль, в котором сообщается о сбое сетевого запроса, который сообщает о статусе сети и описательном сообщении ответа (содержащемся в response.status (en-US) и response.statusText (en-US), соответственно). Конечно, полный веб-сайт будет обрабатывать эту ошибку более грациозно, отображая сообщение на экране пользователя и, возможно, предлагая варианты для исправления ситуации.
Вы можете проверить сам случай отказа:
- Создание локальной копии файлов примеров (загрузка и распаковка the can-store ZIP file)
- Запустите код через веб-сервер (как описано выше, в Serving your example from a server)
- Измените путь к извлечённому файлу, например, «product.json» (т.е. убедитесь, что он написан неправильно)
- Теперь загрузите индексный файл в свой браузер (например, через localhost:8000 ) и посмотрите в консоли разработчика браузера. Вы увидите сообщение в строке «Запрос сети для продуктов.json не удалось с ответом 404: Файл не найден»
Второй блок Fetch можно найти внутри функции fetchBlob() :
Это работает во многом так же, как и предыдущий, за исключением того, что вместо использования json() (en-US) мы используем blob() (en-US) — в этом случае мы хотим вернуть наш ответ в виде файла изображения, а формат данных, который мы используем для этого — Blob — этот термин является аббревиатурой от« Binary Large Object »и может в основном использоваться для представляют собой большие файловые объекты, такие как изображения или видеофайлы.
После того как мы успешно получили наш blob, мы создаём URL-адрес объекта, используя createObjectURL() . Это возвращает временный внутренний URL-адрес, указывающий на объект, указанный в браузере. Они не очень читаемы, но вы можете видеть, как выглядит, открывая приложение Can Store, Ctrl-/щёлкнуть правой кнопкой мыши по изображению и выбрать опцию «Просмотр изображения» (которая может немного отличаться в зависимости от того, какой браузер вы ). URL-адрес объекта будет отображаться внутри адресной строки и должен выглядеть примерно так:
Вызов: XHR версия the Can Store
Мы хотели бы, чтобы вы решили преобразовать версию приложения Fetch для использования XHR в качестве полезной части практики. Возьмите копию ZIP файла и попробуйте изменить JavaScript, если это необходимо.
Некоторые полезные советы:
- Вы можете найти полезный справочный материал XMLHttpRequest .
- Вам в основном нужно использовать тот же шаблон, что и раньше, в примере XHR-basic.html.
- Однако вам нужно будет добавить обработку ошибок, которые мы показали вам в версии Fetch Can Store:
- Ответ найден в request.response после того, как событие load запущено, а не в промисе then() .
- О наилучшем эквиваленте Fetch’s response.ok в XHR следует проверить, является ли request.status равным 200 или если request.readyState равно 4.
- Свойства для получения статуса и сообщения состояния одинаковы, но они находятся на объекте request (XHR), а не в объекте response .
Примечание: Если у вас есть проблемы с этим, не стесняйтесь сравнить свой код с готовой версией на GitHub (см. исходник здесь, а также см. это в действии).
Резюме
Это завершает нашу статью по извлечению данных с сервера. К этому моменту вы должны иметь представление о том, как начать работать как с XHR, так и с Fetch.
Смотрите также
Однако в этой статье обсуждается много разных тем, которые только поцарапали поверхность. Для получения более подробной информации по этим темам, попробуйте следующие статьи:
Fetch: запросы на другие сайты
Если мы сделаем запрос fetch на другой веб-сайт, он, вероятно, завершится неудачей.
Например, давайте попробуем запросить http://example.com :
Вызов fetch не удался, как и ожидалось.
Ключевым понятием здесь является источник (origin) – комбинация домен/порт/протокол.
Запросы на другой источник – отправленные на другой домен (или даже поддомен), или протокол, или порт – требуют специальных заголовков от удалённой стороны.
Эта политика называется «CORS»: Cross-Origin Resource Sharing («совместное использование ресурсов между разными источниками»).
Зачем нужен CORS? Экскурс в историю
CORS существует для защиты интернета от злых хакеров.
Серьёзно. Давайте сделаем краткое историческое отступление.
Многие годы скрипт с одного сайта не мог получить доступ к содержимому другого сайта.
Это простое, но могучее правило было основой интернет-безопасности. Например, хакерский скрипт с сайта hacker.com не мог получить доступ к почтовому ящику пользователя на сайте gmail.com . И люди чувствовали себя спокойно.
В то время в JavaScript не было методов для сетевых запросов. Это был «игрушечный» язык для украшения веб-страниц.
Но веб-разработчики жаждали большей власти. Чтобы обойти этот запрет и всё же получать данные с других сайтов, были придуманы разные хитрости.
Использование форм
Одним из способов общения с другим сервером была отправка туда формы <form> . Люди отправляли её в <iframe> , чтобы оставаться на текущей странице, вот так:
Таким способом было возможно сделать GET/POST запрос к другому сайту даже без сетевых методов, так как формы можно отправлять куда угодно. Но так как запрещено получать доступ к содержимому <iframe> с другого сайта, прочитать ответ было невозможно.
Если быть точным, были трюки и для этого, требующие специального кода на странице и в ифрейме, так что общение с ифреймом было технически возможно. Сейчас мы не будем вдаваться в подробности, пусть эти динозавры покоятся с миром.
Использование скриптов
Ещё один трюк заключался в использовании тега script . У него может быть любой src , с любым доменом, например <script src="http://another.com/…"> . Это даёт возможность загрузить и выполнить скрипт откуда угодно.
Если сайт, например another.com , хотел предоставить данные для такого доступа, он предоставлял так называемый «протокол JSONP» (JSON with Padding)".
Вот как он работал.
Например, нам на нашем сайте нужны данные с сайта http://another.com , скажем, погода:
Сначала, заранее, объявляем глобальную функцию для обработки данных, например gotWeather .
Затем создаём тег <script> с src="http://another.com/weather.json?callback=gotWeather" , при этом имя нашей функции – в URL-параметре callback .
Удалённый сервер с another.com должен в ответ сгенерировать скрипт, который вызывает gotWeather(. ) с данными, которые хочет передать.
Когда этот скрипт загрузится и выполнится, наша функция gotWeather получает данные.
Это работает и не нарушает безопасность, потому что обе стороны согласились передавать данные таким образом. А когда обе стороны согласны, то это определённо не хак. Всё ещё существуют сервисы, которые предоставляют такой доступ, так как это работает даже для очень старых браузеров.
Спустя некоторое время в браузерном JavaScript появились методы для сетевых запросов.
Вначале запросы на другой источник были запрещены. Но в результате долгих дискуссий было решено разрешить их делать, но для использования новых возможностей требовать разрешение сервера, выраженное в специальных заголовках.
Простые запросы
Есть два вида запросов на другой источник:
- Простые.
- Все остальные.
Простые запросы будут попроще, поэтому давайте начнём с них.
Простой запрос – это запрос, удовлетворяющий следующим условиям:
-
: GET, POST или HEAD – разрешены только:
- Accept ,
- Accept-Language ,
- Content-Language ,
- Content-Type со значением application/x-www-form-urlencoded , multipart/form-data или text/plain .
- Он гарантирует, что к запросу на другой источник добавляется правильный заголовок Origin .
- Он проверяет наличие разрешающего заголовка Access-Control-Allow-Origin в ответе и, если всё хорошо, то JavaScript получает доступ к ответу сервера, в противном случае – доступ запрещается с ошибкой.
- Cache-Control
- Content-Language
- Content-Length
- Content-Type
- Expires
- Last-Modified
- Pragma
- Origin содержит именно источник (домен/протокол/порт), без пути.
- Access-Control-Request-Method содержит HTTP-метод «непростого» запроса.
- Access-Control-Request-Headers предоставляет разделённый запятыми список его «непростых» HTTP-заголовков.
- Access-Control-Allow-Origin должен содержать разрешённый источник.
- Access-Control-Allow-Methods должен содержать разрешённые методы.
- Access-Control-Allow-Headers должен содержать список разрешённых заголовков.
- Кроме того, заголовок Access-Control-Max-Age может указывать количество секунд, на которое нужно кешировать разрешения. Так что браузеру не придётся посылать предзапрос для последующих запросов, удовлетворяющих данным разрешениям.
- Метод PATCH
- Content-Type не один из: application/x-www-form-urlencoded , multipart/form-data , text/plain .
- Содержит «непростой» заголовок API-Key .
- Метод: OPTIONS .
- Путь – точно такой же, как в основном запросе: /service.json .
- Особые заголовки:
- Origin – источник.
- Access-Control-Request-Method – запрашиваемый метод.
- Access-Control-Request-Headers – разделённый запятыми список «непростых» заголовков запроса.
Шаг 2 (ответ сервера на предзапрос)
Сервер должен ответить со статусом 200 и заголовками:
- Access-Control-Allow-Methods: PATCH
- Access-Control-Allow-Headers: Content-Type,API-Key .
Это разрешит будущую коммуникацию, в противном случае возникает ошибка.
Если сервер ожидает в будущем другие методы и заголовки, то он может в ответе перечислить их все сразу, разрешить заранее, например:
Теперь, когда браузер видит, что PATCH есть в Access-Control-Allow-Methods , а Content-Type,API-Key – в списке Access-Control-Allow-Headers , он посылает наш основной запрос.
Кроме того, ответ на предзапрос кешируется на время, указанное в заголовке Access-Control-Max-Age (86400 секунд, один день), так что последующие запросы не вызовут предзапрос. Они будут отосланы сразу при условии, что соответствуют закешированным разрешениям.
Шаг 3 (основной запрос)
Если предзапрос успешен, браузер делает основной запрос. Алгоритм здесь такой же, что и для простых запросов.
Основной запрос имеет заголовок Origin (потому что он идёт на другой источник):
Шаг 4 (основной ответ)
Сервер не должен забывать о добавлении Access-Control-Allow-Origin к ответу на основной запрос. Успешный предзапрос не освобождает от этого:
После этого JavaScript может прочитать ответ сервера.
Предзапрос осуществляется «за кулисами», невидимо для JavaScript.
JavaScript получает только ответ на основной запрос или ошибку, если со стороны сервера нет разрешения.
Авторизационные данные
Запрос на другой источник по умолчанию не содержит авторизационных данных (credentials), под которыми здесь понимаются куки и заголовки HTTP-аутентификации.
Это нетипично для HTTP-запросов. Обычно запрос к http://site.com сопровождается всеми куки с этого домена. Но запросы на другой источник, сделанные методами JavaScript – исключение.
Например, fetch(‘http://another.com’) не посылает никаких куки, даже тех (!), которые принадлежат домену another.com .
Потому что запрос с авторизационными данными даёт намного больше возможностей, чем без них. Если он разрешён, то это позволяет JavaScript действовать от имени пользователя и получать информацию, используя его авторизационные данные.
Действительно ли сервер настолько доверяет скрипту? Тогда он должен явно разрешить такие запросы при помощи дополнительного заголовка.
Чтобы включить отправку авторизационных данных в fetch , нам нужно добавить опцию credentials: "include" , вот так:
Теперь fetch пошлёт куки с домена another.com вместе с нашим запросом на этот сайт.
Если сервер согласен принять запрос с авторизационными данными, он должен добавить заголовок Access-Control-Allow-Credentials: true к ответу, в дополнение к Access-Control-Allow-Origin .
Пожалуйста, обратите внимание: в Access-Control-Allow-Origin запрещено использовать звёздочку * для запросов с авторизационными данными. Там должен быть именно источник, как показано выше. Это дополнительная мера безопасности, чтобы гарантировать, что сервер действительно знает, кому он доверяет делать такие запросы.
Итого
С точки зрения браузера запросы к другому источнику бывают двух видов: «простые» и все остальные.
Простые запросы должны удовлетворять следующим условиям:
- Метод: GET, POST или HEAD.
- Заголовки – мы можем установить только:
- Accept
- Accept-Language
- Content-Language
- Content-Type со значением application/x-www-form-urlencoded , multipart/form-data или text/plain .
Основное их отличие заключается в том, что простые запросы с давних времён выполнялись с использованием тегов <form> или <script> , в то время как непростые долгое время были невозможны для браузеров.
Практическая разница состоит в том, что простые запросы отправляются сразу с заголовком Origin , а для других браузер делает предварительный запрос, спрашивая разрешения.
Для простых запросов:
- → Браузер посылает заголовок Origin с источником.
- ← Для запросов без авторизационных данных (не отправляются по умолчанию) сервер должен установить:
- Access-Control-Allow-Origin в * или то же значение, что и Origin
- Access-Control-Allow-Origin в то же значение, что и Origin
- Access-Control-Allow-Credentials в true
Дополнительно, чтобы разрешить JavaScript доступ к любым заголовкам ответа, кроме Cache-Control , Content-Language , Content-Type , Expires , Last-Modified или Pragma , сервер должен перечислить разрешённые в заголовке Access-Control-Expose-Headers .
Для непростых запросов перед основным запросом отправляется предзапрос:
- → Браузер посылает запрос OPTIONS на тот же адрес с заголовками:
- Access-Control-Request-Method – содержит запрашиваемый метод,
- Access-Control-Request-Headers – перечисляет непростые запрашиваемые заголовки.
- Access-Control-Allow-Methods со списком разрешённых методов,
- Access-Control-Allow-Headers со списком разрешённых заголовков,
- Access-Control-Max-Age с количеством секунд для кеширования разрешений
Задачи
Почему нам нужен Origin?
Как вы, вероятно, знаете, существует HTTP-заголовок Referer , который обычно содержит адрес страницы, инициировавшей сетевой запрос.
Например, при запросе (fetch) http://google.com с http://javascript.info/some/url заголовки выглядят так:
Как вы можете видеть, присутствуют и Referer , и Origin .
- Почему нужен Origin , если Referer содержит даже больше информации?
- Возможно ли отсутствие Referer или Origin , или это неправильно?
Нам нужен Origin , потому что иногда Referer отсутствует. Например, когда мы запрашиваем через fetch HTTP-страницу с HTTPS (менее безопасный доступ с более безопасного), то Referer нет.
Content Security Policy (политика безопасности содержимого) может запретить отправление Referer .
Как мы увидим позже, у fetch есть опции, которые предотвращают отправку Referer и даже позволяют изменять его (в пределах того же сайта).
Согласно спецификации, Referer является необязательным HTTP-заголовком.
Именно потому что Referer ненадёжен, был изобретён Origin . Браузер гарантирует наличие правильного Origin при запросах на другой источник.
Как правильно парсить другой сайт на клиенте JavaScript/jQuery [РЕШЕНО]
Дай ка угадаю. Ты front-end разработчик который крестится на back-end потому что не хочешь усложнять себе бородатую жызнь. Однако сегодня пред тобою стало задание: получить блок кода со стороннего ресурса. И API не решает вопроса ♂️
Именно это мы сегодня и разложым по ̶к̶о̶с̶т̶о̶ч̶к̶а̶м̶ строчкам кодаРассказ пойдет о разработанном мною сервисе eProves.com Который проверяет музыку с ютуба на наличие авторских прав (АП) или же YouTube лицензии. Для дальнейшего использования в монтаже. Чтобы быть уверенным что Ютуб не снимет монетизацию, либо же еще хуже — заблокирует само видео.
Задача здесь состоит в том, что бы пользователь отправлял нам ссылку на ютуб видео песни. После чего мы получаем код страницы и копаемся там в поисках нужного блока информации. Вроде и не тяжело
Теперь давайте составим ИКР (Идеальное Конечное Решение)
Пользователь отправляет нам ссылку на песню с YouTube
Мы получаем запрос и вытаскиваем с него код страницы
После чего проверяем на наличие авторских прав
Отдаем положительный либо отрицательный ответНачало вечеринки:
На самом деле НЕЛЬЗЯ отправить запрос на получение кода страницы со стороннего ресурса, без его ведомости. Так как прилетит ̶п̶р̶е̶д̶у̶п̶р̶е̶д̶и̶т̶е̶л̶ь̶н̶ы̶й̶ ̶в̶ы̶с̶т̶р̶е̶л̶ сообщение о попытке кроссбраузерного получения данных: ‘Access-Control-Allow-Origin’. Хана короче
Проблема первая: решение через API & oEmbed
Самым простым вариантом было бы получить желаемый код через oEmbed, который YouTube благополучно поддержывает.Но жизнь не любит халявщиков. Поэтому идем дальше
Следующий вариант который сработает (наверное) будет использование YouTube API v3. Он то уж точно имеет искомую информацию (ну увидим).
Шаг 1: идем на Developers.Google.Com чтобы получить уникальный API ключ. Операция получения ключа достаточно проста. Там же есть и вся документация по параметрам. Бла-бла-бла гуглить всеравно прийдется
Шаг 2: используем ключ для получения API по URL искомого видео.YouTube предоставляет все данные о видео, каннале, тегах, комментариях, в том числе лицензию самого видео [Youtube / Creative Commons], но нет лицензии песни. Лул)
Шаг 3: приунываем, очищаем память и начинаем сначала
Веселье в полном разгаре:
Как только я понял, что на клиенте эта задача непосильна — сразу же перешел на PHP. Потому что он самый популярный в предоставлении хостинга и предельно бюджетный по сравнению с Node.js или Python.
Ну да прийдется разбиратся в back-end. И да, согласен. Заголовок статьи кликбэйтовый, однако это для вашей же сохранности времени. Потому что я потратил несколько световых дней для поиска оптимального решения
Ушел день на установку WAMP-сервера и быстрый экскурс в синтаксис PHP, а так же подключение библиотеки «Simple Html Dom Php» которая упрощает парсинг страниц. И тут попал на вторую проблему. Которая заключается в том, что Ютуб шифрует свои данные в скриптах Тоесть, когда мы откроем изначальный код страцины, то увидим там скриптовую-абракадабру. А поскольку PHP — серверный язык. Он не видит исполнения .js скриптов, которые нужны для полной загрузки и отработки страницы.
Детальнее на StackOverFlow: Парсить данные с Ютуба YouTube и Как парсить динамические страницы PHP после чего я обязан был искать новые решения. Так и попал на Python.
Курс психотерапии:
Сносим к демонам WAMP-сервер. Забиваем на Пых и качаем Python с официального сайта.
Проходит день на изучение синтаксиса Питона. Так же включаем внутреннего Сократа для поисков решения на просторах паукнета. И о Боже!! Как же нам повезло. Находим статью с разбором примера: Как парсить данные с Ютуба на Питоне
Повторяем тютелька-в-тютельку. И…
…вам лучше этого не слышать.Поскольку искомая лицензия песни находится в скрытом блоке и подгружается после нажатия на “Подробнее”. То и в коде страницы его нет. И получить мы его не можем
Для этого подключаем Selenium, который нужен для управления браузером. А именно — имитации нажатия на блок “Подробнее”
Ищем по запросу: “парсинг сайтов на Selenium” и попадаем на божественный разбор парсинга от DoCode (это не реклама, но хотелось бы)
И оказывается, что за все эти дни изучения могущественного. И получение знаний Великих. Нам нужно было всего 30 стр кода
Мдааа. Карл бы одобрил ♂️Исследование различных веб-технологий, математических алгоритмов и проектирование веб-приложений.
Рубрики
- ▼ Уроки: JS & Imacros (17)
Комменты
- Сергей Фортовый к записи Интересные конструкции в Javascript
- фждлатыфж к записи [Урок 1] . Возможности iMacros. Что мы будем использовать из iMacros, а что эффективнее делать на Javascript?
- Сергей к записи [Урок 11] . Формат JSON, как хранилище любых данных в iMacros.
- Сергей к записи [Урок 11] . Формат JSON, как хранилище любых данных в iMacros.
- Тру-ля-ля к записи [Урок 9] . Чтение и запись txt-файлов в iMacros. Разбираем говнокод и смотрим «Best Practice».
[Урок 3] . Получение любой информации со страницы при помощи Javascript
Каждый уважающий себя бот должен уметь извлекать любую информацию с сайта, например ссылки, заголовки, любой текст, номера телефонов, адреса электронной почты, картинки. В этом уроке мы рассмотрим несколько методов сбора данных на странице при помощи iMacros и Javascript.
Исходный код страницы
Для начала, я хочу очень кратко рассказать про то, из чего состоит веб-страница. Вы можете открыть любой сайт, например мой блог и нажать Ctrl+U (или правой кнопкой на странице и выбрать «Исходный код»). Вы увидите код, это обычный текст в формате HTML, который браузер преобразует в веб-страницу. Ключевые слова, заключенные в «<>» — называются тегами, свойства внутри них — атрибутами и значениями атрибута. Все это нам пригодится, чтобы отыскивать необходимые теги с нужной информацией.
Пример:

Что же делает браузер с этим кодом? Браузер, руководствуясь этим кодом, располагает информацию на странице в указанном порядке, подгружает таблицы стилей css, которые добавляют оформление элементам, подгружает и вставляет картинки, скачивает и выполняет различные скрипты. Правила, по которым браузер это делает — называются спецификацией, которая необходима, чтобы все браузеры отрисовывали страницы и обрабатывали скрипты одинаково.
DOM (Document Object Model) – объектная модель страницы
Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) – объектная модель, используемая для XML/HTML-документов. Согласно DOM-модели, документ является иерархией, деревом. Каждый HTML-тег образует узел дерева с типом «элемент». Вложенные в него теги становятся дочерними узлами. Для представления текста создаются узлы с типом «текст».
DOM – это представление страницы в виде дерева объектов, доступное для чтения и изменения через JavaScript.
При чтении неверного HTML браузер автоматически корректирует его для показа и при построении DOM. В частности, всегда будет верхний тег <html> . Даже если в тексте нет – в DOM он будет, браузер создаст его самостоятельно. То же самое касается и тега <body> . Например, если файл состоит из одного слова «Test» , то браузер автоматически обернёт его в <html> и <body> .
При генерации DOM браузер самостоятельно обрабатывает ошибки в документе, закрывает теги и делает все, чтобы корректно отобразить документ.
Методы извлечения информации из тегов средствами iMacros
У iMacros есть возможности для извлечения текста и даже html-кода из тегов. Для начала я рекомендую вам установить расширение FireBug, если вы этого еще не сделали. Открывается он по нажатию клавиши F12, либо вы можете нажать в любом месте страницы правой кнопкой и тыкнуть «Инспектировать элемент при помощи Firebug».

Чтобы потренироваться и как следует разобраться, я создал для вас специальную страничку
http://polygon.nagibaka.ru/lessons/lesson3/index.htmДавайте приступим, я рекомендую вам сразу открыть эту ссылку в Firefox. Поскольку вам предстоит писать много кода, рекомендую первым делом поставить себе какой-нибудь редактор, например весьма распространенный Notepad++ или же мой любимый и в сто раз более удобный Sublime Text 3, я писал о плагинах для него и режиме Vim для быстрого и мощного редактирования.
Давайте нажмем на «Запись» в iMacros, тыкнем по первому заголовку и остановим запись.
Мы получим вот такой скрипт(Current.iim):
Давайте переделаем его в JS.
Создайте новый файл, например test.js. Допустим, нам необходимо получить содержимое тега «<H1>» . Переделаем макрос на js и извлечем из него содержимое тега «<H1>» .Результат(test.js):
Обратите внимание — я заменил «Тестовая<SP>страница» на «*» — поскольку текст заголовка может быть любым, а звездочка нам говорит, что текст может быть произвольным
TYPE=H1 — указывает что мы ищем тег « <H1> «, можно указать любой другой
POS=1 — указывает, что мы выбираем первый попавшийся тег <H1> , ведь на странице их может оказаться несколько
ATTR=TXT:* — указывает значение атрибута, в данном случае служебный атрибут, который ищет совпадение в тексте внутри тега
EXTRACT=TXT — указывает, что мы извлекаем текст, также можно указать «EXTRACT=HTM» , чтобы извлечь текст вместе со всеми тегамиМетоды извлечения информации из тегов средствами Javascript
Теперь давайте перепишем код полностью на JS без использования iim-команд. Еще, вместо iimDisplay мы будем использовать window.console.log(‘any text and vars’); Данные, которые мы будем выводить этой командой — будут отображаться в консоли firebug. Такой вариант удобнее по многим причинам, например, можно выводить js-объекты и удобно просматривать их содержимое. А самое главное, при помощи iimDisplay единовременно можно смотреть только последнее выведенное сообщение, а c этой командой мы увидим полный лог. Небольшое примечание, в некоторых старых версиях FF(например 27), window.console.log не всегда работало, и обратите внимание, нужно писать именно window.console.log , несмотря на то, что при создании обычных сайтов, чаще пишут просто console.log , весь наш код выполняется в контексте расширения iMacros, поэтому необходимо добавлять window .
Код на JS(test.js):
Здесь мы использовали поиск элемента по тегу. На самом деле, метод querySelector принимает в качестве параметра любое выражение в формате css-правила. Еще важно знать, что querySelector возвращает только первый элемент из DOM-дерева, который совпал. Чтобы получить все элементы, подходящие под указанное правило, используйте querySelectorAll .
Приведу несколько примеров:
Вы можете получать список всех элементов, подходящих под заданное правила, используйте для этого querySelectorAll() — он всегда возвращает коллекцию элементов либо null.
Давайте выведем все ссылки в консоль.
Тут мы видим набор элементов, а если быть точнее, то коллекцию узлов DOM-дерева. Квадратные скобки могут натолкнуть на мысль о том, что это обычный массив, но это не совсем так. Мы не сможем для такой коллекции использовать стандартные методы массива, такие, как .map(), .filter() и крайне удобный .forEach() . Поэтому для перебора и обработки элементов такой коллекции мы будем использовать классический цикл for .
В коллекции 6 элементов, вы можете нажать на любой и тогда откроется инспектор DOM-дерева и вы увидите элемент, на который нажали.

Хоть это и неполноценный массив, но нам все равно доступно свойство length . Давайте посчитаем количество ссылок в коллекции и выведем для каждой ссылки заголовок и адрес, куда она ссылается. В JS есть несколько вариантов реализации циклов, мы пока используем самый простой for .
Выводим все ссылки на странице и информацию о них:
Этот код покажет в консоли количество найденных элементов и выведет информацию о каждой ссылке на странице. Это базовый пример, позже вы научитесь создавать, изменять, фильтровать массивы полученной информации, сохранять в каком угодно виде и разумеется считывать из файлов и использовать данные в своих программах.
Помимо получения значений аттрибутов при помощи метода getAttribute() , текста внутри тега при помощи innerHTML, мы можем делать разные другие вещи. Чтобы посмотреть доступные методы, вы можете поискать в документации по JS, или воспользоваться автокомплитом из консоли, попробуйте вручную ввести window.document.querySelectorAll(«a»)[0]. или window.document.querySelector(«a»). , что будет равнозначно, и после ввода точки появится список поддерживаемых методов и свойств, более подробно о каждом свойстве или методе вы легко найдете в гугле. Подобным образом вы сможете исследовать методы и свойства любого объекта.
И еще, вы можете указывать несколько селекторов через запятую, например window.document.querySelectorAll(«a, .styled»)
Есть и другие методы для получения нужных нам тегов и извлечения из них различных свойств и атрибутов, но они не особо актуальны, потому что querySelectorAll — это метод, который поддерживают все современные браузеры, обладает куда большим функционалом и у нас нет необходимости использовать устаревшие методы, вроде window.document.getElementById() , window.document.getElementsByClassName() , window.document.getElementsByTagName() и прочие.
Также, для мощного поиска по тексту веб-страницы, можно использовать регулярные выражения, инструмент невиданной силы и мощи, который заслуживает отдельной книги страниц на 600, о нем я расскажу в следующих уроках.
Любой другой запрос считается «непростым». Например, запрос с методом PUT или с HTTP-заголовком API-Key не соответствует условиям.
Принципиальное отличие между ними состоит в том, что «простой запрос» может быть сделан через <form> или <script> , без каких-то специальных методов.
Таким образом, даже очень старый сервер должен быть способен принять простой запрос.
В противоположность этому, запросы с нестандартными заголовками или, например, методом DELETE нельзя создать таким способом. Долгое время JavaScript не мог делать такие запросы. Поэтому старый сервер может предположить, что такие запросы поступают от привилегированного источника, «просто потому, что веб-страница неспособна их посылать».
Когда мы пытаемся сделать непростой запрос, браузер посылает специальный предварительный запрос («предзапрос», по англ. «preflight»), который спрашивает у сервера – согласен ли он принять такой непростой запрос или нет?
И, если сервер явно не даёт согласие в заголовках, непростой запрос не посылается.
Далее мы разберём конкретные детали.
CORS для простых запросов
При запросе на другой источник браузер всегда ставит «от себя» заголовок Origin .
Например, если мы запрашиваем https://anywhere.com/request со страницы https://javascript.info/page , заголовки будут такими:
Как вы можете видеть, заголовок Origin содержит именно источник (домен/протокол/порт), без пути.
Сервер может проверить Origin и, если он согласен принять такой запрос, добавить особый заголовок Access-Control-Allow-Origin к ответу. Этот заголовок должен содержать разрешённый источник (в нашем случае https://javascript.info ) или звёздочку * . Тогда ответ успешен, в противном случае возникает ошибка.
Здесь браузер играет роль доверенного посредника:
Вот пример ответа сервера, который разрешает доступ:
Заголовки ответа
По умолчанию при запросе к другому источнику JavaScript может получить доступ только к так называемым «простым» заголовкам ответа:
При доступе к любому другому заголовку ответа будет ошибка.
Чтобы разрешить JavaScript доступ к любому другому заголовку ответа, сервер должен указать заголовок Access-Control-Expose-Headers . Он содержит список, через запятую, заголовков, которые не являются простыми, но доступ к которым разрешён.
При таком заголовке Access-Control-Expose-Headers , скрипту разрешено получить заголовки Content-Encoding и API-Key ответа.
«Непростые» запросы
Мы можем использовать любой HTTP-метод: не только GET/POST , но и PATCH , DELETE и другие.
Некоторое время назад никто не мог даже предположить, что веб-страница способна делать такие запросы. Так что могут существовать веб-сервисы, которые рассматривают нестандартный метод как сигнал: «Это не браузер». Они могут учитывать это при проверке прав доступа.
Поэтому, чтобы избежать недопониманий, браузер не делает «непростые» запросы (которые нельзя было сделать в прошлом) сразу. Перед этим он посылает предварительный запрос, спрашивая разрешения.
Предварительный запрос использует метод OPTIONS , у него нет тела, но есть три заголовка:
Если сервер согласен принимать такие запросы, то он должен ответить без тела, со статусом 200 и с заголовками:
Давайте пошагово посмотрим, как это работает, на примере PATCH запроса (этот метод часто используется для обновления данных) на другой источник:
Этот запрос не является простым по трём причинам (достаточно одной):
Шаг 1 (предзапрос)
Перед тем, как послать такой запрос, браузер самостоятельно генерирует и посылает предзапрос, который выглядит следующим образом: