Файл xhr что это
XMLHttpRequest
XMLHttpRequest – это встроенный в браузер объект, который даёт возможность делать HTTP-запросы к серверу без перезагрузки страницы.
Несмотря на наличие слова «XML» в названии, XMLHttpRequest может работать с любыми данными, а не только с XML. Мы можем загружать/скачивать файлы, отслеживать прогресс и многое другое.
В современной веб-разработке XMLHttpRequest используется по трём причинам:
Основы
XMLHttpRequest имеет два режима работы: синхронный и асинхронный.
Сначала рассмотрим асинхронный, так как в большинстве случаев используется именно он.
Чтобы сделать запрос, нам нужно выполнить три шага:
Этот метод устанавливает соединение и отсылает запрос к серверу. Необязательный параметр body содержит тело запроса.
Три наиболее используемых события:
Вот полный пример. Код ниже загружает /article/xmlhttprequest/example/load с сервера и сообщает о прогрессе:
После ответа сервера мы можем получить результат запроса в следующих свойствах xhr :
Мы можем также указать таймаут – промежуток времени, который мы готовы ждать ответ:
Тип ответа
К примеру, давайте получим ответ в формате JSON:
Состояния запроса
Список всех состояний, указанных в спецификации:
Изменения в состоянии объекта запроса генерируют событие readystatechange :
Вы можете наткнуться на обработчики события readystatechange в очень старом коде, так уж сложилось исторически, когда-то не было событий load и других. Сегодня из-за существования событий load/error/progress можно сказать, что событие readystatechange «морально устарело».
Отмена запроса
Если мы передумали делать запрос, можно отменить его вызовом xhr.abort() :
Синхронные запросы
Выглядит, может быть, и неплохо, но синхронные запросы используются редко, так как они блокируют выполнение JavaScript до тех пор, пока загрузка не завершена. В некоторых браузерах нельзя прокручивать страницу, пока идёт синхронный запрос. Ну а если же синхронный запрос по какой-то причине выполняется слишком долго, браузер предложит закрыть «зависшую» страницу.
Из-за всего этого синхронные запросы используют очень редко. Мы более не будем рассматривать их.
HTTP-заголовки
XMLHttpRequest умеет как указывать свои заголовки в запросе, так и читать присланные в ответ.
Для работы с HTTP-заголовками есть 3 метода:
XMLHttpRequest не разрешено изменять их ради безопасности пользователей и для обеспечения корректности HTTP-запроса.
Ещё одной особенностью XMLHttpRequest является то, что отменить setRequestHeader невозможно.
Если заголовок определён, то его нельзя снять. Повторные вызовы лишь добавляют информацию к заголовку, а не перезаписывают его.
Возвращает значение заголовка ответа name (кроме Set-Cookie и Set-Cookie2 ).
Заголовки возвращаются в виде единой строки, например:
Таким образом, если хочется получить объект с парами заголовок-значение, нам нужно задействовать немного JS.
Вот так (предполагается, что если два заголовка имеют одинаковое имя, то последний перезаписывает предыдущий):
POST, FormData
Чтобы сделать POST-запрос, мы можем использовать встроенный объект FormData.
Если нам больше нравится формат JSON, то используем JSON.stringify и отправляем данные как строку.
Прогресс отправки
Событие progress срабатывает только на стадии загрузки ответа с сервера.
Если мы отправляем что-то большое, то нас гораздо больше интересует прогресс отправки данных на сервер. Но xhr.onprogress тут не поможет.
Примеры обработчиков для этих событий:
Пример из реальной жизни: загрузка файла на сервер с индикацией прогресса:
Запросы на другой источник
XMLHttpRequest может осуществлять запросы на другие сайты, используя ту же политику CORS, что и fetch.
Детали по заголовкам, которые при этом необходимы, смотрите в главе fetch.
Итого
Типичный код GET-запроса с использованием XMLHttpRequest :
Событий на самом деле больше, в современной спецификации они все перечислены в том порядке, в каком генерируются во время запроса:
Наиболее часто используют события завершения загрузки ( load ), ошибки загрузки ( error ), или мы можем использовать единый обработчик loadend для всего и смотреть в свойствах объекта запроса xhr детали произошедшего.
Как спарсить любой сайт?
Меня зовут Даниил Охлопков, и я расскажу про свой подход к написанию скриптов, извлекающих данные из интернета: с чего начать, куда смотреть и что использовать.
Написав тонну парсеров, я придумал алгоритм действий, который не только минимизирует затраченное время на разработку, но и увеличивает их живучесть, робастность, масштабируемость.
Чтобы спарсить данные с вебсайта, пробуйте подходы именно в таком порядке:
Найдите официальное API,
Найдите XHR запросы в консоли разработчика вашего браузера,
Найдите сырые JSON в html странице,
Отрендерите код страницы через автоматизацию браузера,
Совет профессионалов: не начинайте с BS4/Scrapy
Крутые вебсайты с крутыми продактами делают тонну A/B тестов, чтобы повышать конверсии, вовлеченности и другие бизнес-метрики. Для нас это значит одно: элементы на вебстранице будут меняться и переставляться. В идеальном мире, наш написанный парсер не должен требовать доработки каждую неделю из-за изменений на сайте.
Приходим к выводу, что не надо извлекать данные из HTML тегов раньше времени: разметка страницы может сильно поменяться, а CSS-селекторы и XPath могут не помочь. Используйте другие методы, о которых ниже. ⬇️
Используйте официальный API
Поищите XHR запросы в консоли разработчика
Все современные вебсайты (но не в дарк вебе, лол) используют Javascript, чтобы догружать данные с бекенда. Это позволяет сайтам открываться плавно и скачивать контент постепенно после получения структуры страницы (HTML, скелетон страницы).
В итоге, даже не имея официального API, можно воспользоваться красивым и удобным закрытым API. ☺️
Даже если фронт поменяется полностью, этот API с большой вероятностью будет работать. Да, добавятся новые поля, да, возможно, некоторые данные уберут из выдачи. Но структура ответа останется, а значит, ваш парсер почти не изменится.
Алгорим действий такой:
Открывайте вебстраницу, которую хотите спарсить
Открывайте вкладку Network и кликайте на фильтр XHR запросов
Обновляйте страницу, чтобы в логах стали появляться запросы
Найдите запрос, который запрашивает данные, которые вам нужны
Копируйте запрос как cURL и переносите его в свой язык программирования для дальнейшей автоматизации.
Кнопка, которую я искал месяцы
Поищите JSON в HTML коде страницы
Как было удобно с XHR запросами, да? Ощущение, что ты используешь официальное API. 🤗 Приходит много данных, ты все сохраняешь в базу. Ты счастлив. Ты бог парсинга.
Но тут надо парсить другой сайт, а там нет нужных GET/POST запросов! Ну вот нет и все. И ты думаешь: неужели расчехлять XPath/CSS-selectors? 🙅♀️ Нет! 🙅♂️
Чтобы страница хорошо проиндексировалась поисковиками, необходимо, чтобы в HTML коде уже содержалась вся полезная информация: поисковики не рендерят Javascript, довольствуясь только HTML. А значит, где-то в коде должны быть все данные.
Современные SSR-движки (server-side-rendering) оставляют внизу страницы JSON со всеми данные, добавленный бекендом при генерации страницы. Стоп, это же и есть ответ API, который нам нужен! 😱😱😱
Вот несколько примеров, где такой клад может быть зарыт (не баньте, плиз):
Красивый JSON на главной странице Habr.com. Почти официальный API! Надеюсь, меня не забанят. И наш любимый (у парсеров) Linkedin!
Алгоритм действий такой:
В dev tools берете самый первый запрос, где браузер запрашивает HTML страницу (не код текущий уже отрендеренной страницы, а именно ответ GET запроса).
Внизу ищите длинную длинную строчку с данными.
Вырезаете JSON из HTML любыми костылямии (я использую html.find(«=<") ).
Отрендерите JS через Headless Browsers
Если коротко, то есть инструменты, которые позволяют управлять браузером: открывать страницы, вводить текст, скроллить, кликать. Конечно же, это все было сделано для того, чтобы автоматизировать тесты веб интерфейса. I’m something of a web QA myself.
После того, как вы открыли страницу, чуть подождали (пока JS сделает все свои 100500 запросов), можно смотреть на HTML страницу опять и поискать там тот заветный JSON со всеми данными.
Для масштабируемости и простоты, я советую использовать удалённые браузерные кластеры (remote Selenium grid).
Вот так я подключаюсь к Selenoid из своего кода: по факту нужно просто указать адрес запущенного Selenoid, но я еще зачем-то передаю кучу параметров бразеру, вдруг вы тоже захотите. На выходе этой функции у меня обычный Selenium driver, который я использую также, как если бы я запускал браузер локально (через файлик chromedriver).
Парсите HTML теги
Если случилось чудо и у сайта нет ни официального API, ни вкусных XHR запросов, ни жирного JSON внизу HTML, если рендеринг браузерами вам тоже не помог, то остается последний, самый нудный и неблагодарный метод. Да, это взять и начать парсить HTML разметку страницы. То есть, например, из Cool website достать ссылку. Это можно делать как простыми регулярными выражениями, так и через более умные инструменты (в питоне это BeautifulSoup4 и Scrapy) и фильтры (XPath, CSS-selectors).
Мой единственный совет: постараться минимизировать число фильтров и условий, чтобы меньше переобучаться на текущей структуре HTML страницы, которая может измениться в следующем A/B тесте.
Подписывайтесь на мой Телеграм канал, где я рассказываю свои истории из парсинга и сливаю датасеты.
Почему при загрузке файлов методом POST через XMLHttpRequest происходит задержка после xhr.upload.onload?
Собственно вот суть вопроса:
При загрузке файлов, на этапе xhr.readyState = 1 и после наступления события xhr.upload.onload происходит какая-то задержка, примерно равная времени загрузки файлов (xhr.upload.onprogress).
Файлы успешно загружены ( о чем говорит событие xhr.upload.onload ). И на этом этапе (xhr.readyState = 1) мы еще не ждем ответа от сервера, а только готовимся выполнить send(). Потом-то, когда «там» что-то прогружается, статусы 2,3,4 отрабатываются почти одновременно.
Что может происходить в этот момент?
Или просто сам API по дебильному реализован, и по факту 100% загрузка еще не означает 100% подготовку файлов к отправке (send())? Получается прогресс-бар загрузки дошел до 100% а на самом деле этап отправки еще наступит не скоро (xhr.readyState = 2).
Просто какой смысл тогда в этом xhr.upload.onprogress если он мне не дает информации о том, что текущий статус/этап вот вот будет выполнен (при приближении прогресс-бара к концу).
Потому что сначала реквест, и только потом респонс.
Запускаешь: xhr.send(data); и по очереди:
xhr.upload.onload // загрузка на сервер завершена
// потом сервер
xhr.onreadystatechange
Получается на этапе «Потом сервер» что-то еще происходит? И что происходит? Что мы ждем?
Если файлы загружены, то что происходит ровно столько же времени сколько мы загружали файлы? Сериализация/десериализация?
Просто после загрузки файлов и до этапа xhr.readyState === 2 происходит еще задержка равная по времени загрузки файлов.
Это вопросы к автору сервера.
С включенным троттлингом (режим «Fast 3G») загрузка 0.5 MB занимает 6 секунд, ответ сервера — 100 мс:
HTML5 Rocks
Переводы
Введение
Мало кто знает, что в последнюю версию XHR было добавлено много функций. В XMLHttpRequest Level 2 представлена масса новых возможностей, которые избавят нас от ненужных операций и таких понятий, как кросс-доменные запросы, события хода отправки файлов, а также поддержка загрузки и отправки двоичных данных. Благодаря этому технология AJAX работает в сочетании с новейшими API HTML5: API файловой системы, API веб-аудио и WebGL.
В этом руководстве описываются некоторые из новых возможностей XMLHttpRequest и в особенности те из них, которые необходимы для работы с файлами.
Извлечение данных
Загрузка файла в виде двоичного объекта с помощью XHR всегда была проблемой. С технической точки зрения это было даже невозможно. Один из известных способов заключается в переопределении mime-типа пользовательской кодировкой, как показано ниже.
Ранее содержимое картинки можно было извлечь таким способом:
Этот способ работает, однако элемент responseText вовсе не является большим двоичным объектом (элементом blob). Это двоичная строка, представляющая файл картинки. Мы заставляем сервер вернуть данные в необработанном виде. Хотя этот прием работает, я не рекомендую использовать его. При попытке принудительно перевести данные в нужный формат с помощью манипуляций с кодировкой и строками всегда возникают проблемы.
Указание формата ответа
Ответы в формате ArrayBuffer
Ответы в формате Blob
Объект Blob можно использовать по разному: например, сохранить его в индексированной базе данных, записать в файловую систему HTML5 или создать URL элемента Blob, как показано в этом примере.
Отправка данных
Отправка строковых данных: xhr.send(DOMString)
Отправка данных форм: xhr.send(FormData)
Многие из нас привыкли пользоваться плагинами jQuery и другими библиотеками для отправки форм AJAX. Вместо них можно использовать FormData – еще один новый тип данных в рамках технологии XHR2. Тип FormData очень удобен для динамического создания HTML-элементов
Отправка файла или объекта Blob: xhr.send(Blob)
В этом примере мы создаем новый текстовый файл с помощью API BlobBuilder и отправляем этот объект Blob на сервер. Этот код также запускает обработчик, который показывает нам ход отправки файла.
Отправка произвольного набора байтов: xhr.send(ArrayBuffer)
Обмен ресурсами с запросом происхождения (Cross Origin Resource Sharing, или CORS)
С помощью технологии CORS веб-приложения могут выполнять кросс-доменные AJAX-запросы к другим доменам. Сделать это очень просто: достаточно, чтобы сервер отправил необходимый заголовок ответа.
Включение CORS-запросов
Заголовок Access-Control-Allow-Origin можно добавить как для одного сайта, так и для всего домена. Чтобы разрешить отправку запросов из всех доменов, добавьте строку такого вида:
Фактически на всех страницах этого сайта (html5rocks.com) также используется технология CORS. Запустите инструменты разработчика, и в ответе вы увидите заголовок Access-Control-Allow-Origin :
Заголовок Access-Control-Allow-Origin на сайте html5rocks.com
Разрешить кросс-доменные запросы несложно, поэтому настоятельно рекомендуется включать CORS для общедоступных данных.
Создание кросс-доменного запроса
Практические примеры
Загрузка и сохранение файлов в файловой системе HTML5
Обратите внимание: для использования этого кода нужно ознакомиться с условиями поддержки браузеров и ограничениями на хранение в руководстве Знакомство с API файловой системы.
Отправка файла по частям
API файлов существенно облегчает отправку больших файлов. Методика такова: крупный файл разбивается на несколько мелких, которые затем отправляются с помощью XHR и собираются обратно на сервере. Примерно так же Gmail быстро отправляет большие прикрепленные файлы. Эта технология также позволяет обойти ограничение Google App Engine: 32 МБ на один HTTP-запрос.
Ниже приведен код для сборки файла на сервере.
Проверьте, как он работает.
Шифрование/дешифрование данных на стороне клиента в web-ориентированных системах
В наши дни всё больше программ переводятся в так называемый «web-ориентированный» вид, то есть используется принцип клиент-сервер, что позволяет хранить данные удалённо и получать к ним доступ через тонкий клиент (браузер).
Одновременно с удобством использования остро встаёт вопрос о защищённости этих данных. Конфиденциальная информация может стать доступна другим людям несколькими путями. Во-первых, к пользователю могут быть применены физические меры для выпытывания. Во-вторых, при передаче данные могут быть перехвачены различными снифферами. И, в-третьих, на сервер могут быть произведены хакерские атаки, что позволит злоумышленникам похитить информацию, либо недобросовестный администратор сервера воспользуется ею в личных целях.
Задача
Реализация
Тот факт, что обработка данных должна производиться исключительно на стороне клиента, ограничивал выбор средств для реализации. На начальной стадии разработки была опробована связка «Java-апплет – Java-сервлет», но через какое-то время пришлось искать другой способ, потому что были трудности в отладке и передаче данных между апплетом и сервлетом.
Я остановился на использовании возможностей HTML5 и JavaScript-объекта «XmlHttpRequest Level 2» в частности, потому что они позволили с меньшими усилиями реализовать необходимый функционал.
Работа с текстом
Работа с файлами
Работа с изображениями
Немного ключевого исходного кода для работы с файлами:
Исходный код для работы с изображениями:
Я не стал здесь приводить реализацию функций XOREncrypt, XORDecrypt и класса Base64, чтобы не загромождать и без того длинный листинг. Их можно посмотреть в прилагаемом архиве с исходным кодом.
Код Java-апплета для вывода диалога сохранения файла.