1 of 39

Лекция 8 �AJAX, фоновые задачи

intervolga.ru/school/

Никита Владимирович Калинин,

ИНТЕРВОЛГА

2 of 39

Устройство ИНТЕРНЕТА,

HTTP, HTML, верстка

Веб-сервер�Лекции 1 и 2�ЛР 1

Работа с данными�PHP, MySQL, веб-формы, фильтрация данных�Лекции 3 и 4�ЛР 2

Работа с формами, куками, сессиями.

Авторизация, регистрация, проверка доступа�Лекции 5 и 6�ЛР 3

Обработка текста на PHP.

Организация длительных операций и точек доступа для AJAX�Лекции 7 и 8�ЛР 4

3 of 39

  • Переход по ссылке в браузере приводит к GET-запросу к серверу.
  • Отправка формы (обычно) приводит к POST-запросу к серверу.

GET/POST URL

HTML

Программа на сервере (сайт):

HTTP

Веб-часть

Браузер

4 of 39

  • Переход по ссылке в браузере приводит к GET-запросу к серверу.
  • Отправка формы (обычно) приводит к POST-запросу к серверу.

  • На странице сайта может быть JS-код, который сам отправляет HTTP-запросы без перезагрузки страницы.

GET/POST URL

HTML

GET/POST URL

HTML

Программа на сервере (сайт):

HTTP

Веб-часть

Браузер

AJAX

5 of 39

  • Переход по ссылке в браузере приводит к GET-запросу к серверу.
  • Отправка формы (обычно) приводит к POST-запросу к серверу.

  • На странице сайта может быть JS-код, который сам отправляет HTTP-запросы без перезагрузки страницы.

  • На сайте может запускаться код без запроса из браузера.

GET/POST URL

HTML

GET/POST URL

HTML

Фоновые задачи

Программа на сервере (сайт):

HTTP

Веб-часть

Браузер

AJAX

6 of 39

Технология AJAX

7 of 39

HTTP-запросы из JavaScript

  • AJAX (Asynchronous JavaScript and XML) — технология, которая позволяет отправлять HTTP-запросы из браузера с помощью кода на JavaScript без перезагрузки страницы.
  • Название немного устарело: сегодня чаще всего пересылают данные в формате JSON, а не XML.

8 of 39

HTTP-запросы из JavaScript

  • AJAX (Asynchronous JavaScript and XML) — технология, которая позволяет отправлять HTTP-запросы из браузера с помощью кода на JavaScript без перезагрузки страницы.
  • Название немного устарело: сегодня чаще всего пересылают данные в формате JSON, а не XML.�
  • XHR (XML HTTP Request) — конкретная реализация AJAX, класс в JavaScript, с помощью которого отправляют запросы.
  • Этот класс низкоуровневый, напрямую его редко используют, но аббревиатура встречается, надо помнить.

9 of 39

Синхронное выполнение кода

function a() {

b()

c()

}

t

a()

b()

c()

10 of 39

Синхронное выполнение кода

Асинхронное выполнение кода

function a() {

b()

c()

}

function a() {

b()

c()

}

t

a()

b()

c()

t

a()

b()

c()

Как возвращать значение?

11 of 39

Асинхронный JavaScript: функции обратного вызова

  • В JavaScript есть анонимные функции, их можно передавать как аргументы при вызове других функций.�
  • Идея №1: если функция асинхронная, она должна принимать callback, который будет вызван, когда функция завершит выполнение.�
  • Код превращается в «лесенку» — трудно читать и сопровождать.
  • Это явление назвали «callback hell».

function a(callback) {

b(resB => {

c(resC => {

let resA = resB + resC

callback(resA)

})

})

}

// Вызов асинхронной функции

a(res => {

console.log(res)

})

function (arg) {

...

}

arg => {

...

}

==

12 of 39

Асинхронный JavaScript: Promise

  • Идея №2: все асинхронные функции возвращают объект Promise. Он может быть в одном из состояний:
    • pending (ожидает),
    • fulfilled (выполнен успешно),
    • rejected (выполнен с ошибкой).�
  • У Promise есть метод then, в который можно передать callback, который будет вызван, когда Promise будет успешно выполнен.
  • then возвращает новый Promise, у которого тоже есть then — callback-и можно собрать в цепочку.

function a() {

let resB

return b().then(res => {

resB = res

return c()

}).then(resC => {

return resB + resC

})

}

// Вызов асинхронной функции

a().then(res => {

console.log(res)

})

13 of 39

Асинхронный JavaScript: async/await

  • Идея №3: сопрограммы + поддержка Promise на уровне языка JavaScript.�
  • Асинхронная функция (async) всегда возвращает Promise, это не нужно указывать явно.
  • Оператор await приостанавливает вызывающую асинхронную функцию до тех пор, пока не выполнится Promise вызванной асинхронной функции.�
  • Проблема «red-blue functions»: синхронные функции не совсем могут вызывать асинхронные.

async function a() {

let resB = await b()

let resC = await c()

return resB + resC

}

// Вызов асинхронной функции

let res = await a()

// Вызов a() из синхронной функции

a().then(res => {

console.log(res)

})

14 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

15 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

Язык не позволяет выполнять несколько задач одновременно

16 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

Язык не позволяет выполнять несколько задач одновременно

Кооперативная многозадачность (в один момент времени выполняется только одна задача)

17 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

Язык не позволяет выполнять несколько задач одновременно

Кооперативная многозадачность (в один момент времени выполняется только одна задача)

Вытесняющая многозадачность и/или параллелизм, ресурсоемкий, сложный подход

18 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

Язык не позволяет выполнять несколько задач одновременно

Кооперативная многозадачность (в один момент времени выполняется только одна задача)

Вытесняющая многозадачность и/или параллелизм, ресурсоемкий, сложный подход

Вытесняющая многозадачность и/или параллелизм, эффективный, очень сложный подход

19 of 39

Разберемся в терминах: что такое concurrency?

Один поток

Многопоточность

Блокирующие вызовы

Неблокирующие вызовы

Язык не позволяет выполнять несколько задач одновременно

Кооперативная многозадачность (в один момент времени выполняется только одна задача)

Вытесняющая многозадачность и/или параллелизм, ресурсоемкий, сложный подход

Вытесняющая многозадачность и/или параллелизм, эффективный, очень сложный подход

PHP

JavaScript

Go

C#

Java

20 of 39

HTTP-запросы из JavaScript

  • Для отправки HTTP-запроса используется функция fetch.
  • Первый аргумент — URL.
  • Второй аргумент (опциональный) — параметры запроса: метод, заголовки, тело запроса и пр.�
  • fetch возвращает Promise, ожидаем его выполнения с помощью await.
  • Результат fetch — объект Response, в нем есть заголовки, но тело еще не прочитано
  • Методы ответа — json, text, blob — читают тело ответа до конца

let resp = await fetch('/some/url/')

let body = await resp.json()

console.log(body)

GET

let resp = await fetch('/some/url/', {

method: 'POST',

headers: {

'Content-Type': 'application/json',

},

})

let body = await resp.json()

console.log(body)

POST с заголовками

21 of 39

Пример: форма входа на AJAX

<form id="signIn">

<div class="errors"></div>

Логин:

<input name="login">

Пароль:

<input name="passwd" type="password">

<button>Войти</button>

</form>

<script>

</script>

let form = document.querySelector('#signIn')

form.onsubmit = e => {

// Обработка...

// Чтобы браузер не сделал обычную

// отправку формы.

return false

}

22 of 39

Пример: форма входа на AJAX

<form id="signIn">

<div class="errors"></div>

Логин:

<input name="login">

Пароль:

<input name="passwd" type="password">

<button>Войти</button>

</form>

<script>

</script>

let form = document.querySelector('#signIn')

form.onsubmit = async e => {

let resp = await fetch('/api/login/', {

method: 'POST',

body: form,

})

if (resp.status == 200) {

window.location.href = '/personal/'

return false

}

let err = await resp.text()

let errDiv = form.querySelector('.errors')

errDiv.textContent = err

return false

}

Чего не хватает?

23 of 39

Пример: форма входа на AJAX

  • В ответе на AJAX-запрос может быть не только HTML:
    • JSON
    • XML
    • что угодно�
  • В некоторых проектах сервер отдает только JSON, а генерацией HTML занимается JS в браузере.�
  • Можно ли на сервере определить, AJAX-запрос или «обычный»?

<?php

$root = $_SERVER['DOCUMENT_ROOT'];

require_once "{$root}/core/index.php";

$login = $_POST['login'];

$passwd = $_POST['passwd'];

$err = UserLogic::signIn($login, $passwd);

if ($err) {

// Статус ответа: 403 Bad Request.

http_response_code(403);

echo $err;

die;

}

// Статус ответа: 204 No Content

// (у ответа нет тела).

http_response_code(204);

24 of 39

Фоновые задачи

25 of 39

Фоновые задачи

  • Код фоновых задач выполняется независимо от запросов, которые отправляют пользователи сайта. То есть для запуска не нужен HTTP-запрос.�
  • Чаще всего фоновые задачи запускаются по расписанию.

Примеры фоновых задач:

  • отправка почтовых уведомлений, например напоминаний;
  • удаление временных файлов;
  • ротация логов;
  • очистка кеша;
  • импорт данных;
  • создание резервной копии сайта;
  • другие длительные задачи (например, 1 час).

26 of 39

Консольные PHP-скрипты

  • PHP позволяет разрабатывать не только веб-страницы, код которых запускает браузер при входящем HTTP-запросе.
  • PHP позволяет писать программы, которые можно запускать из командной строки.
  • Во время работы такого скрипта не доступны $_GET, $_POST, сессии, отсутствует даже $_SERVER['DOCUMENT_ROOT'].
  • Но можно, например, подключиться к БД сайта и делать запросы.

#!/usr/bin/env php

<?php

echo "Hello\n\n";

user@host:~$ php -f script.php

Hello

user@host:~$ ./script.php

Hello

27 of 39

Запуск консольных скриптов

  • Администратор сайта может зайти на сервер и выполнить скрипт.�Примеры:
    • импорт большого объема данных;
    • массовые изменения в БД (например, при установке новой версии программы надо изменить структуру БД);
    • другие служебные операции.�
  • Программа-планировщик может запустить скрипт по расписанию — то есть запустить фоновую задачу. Примеры:
    • обновление курсов валют;
    • отправка почтовых уведомлений;
    • прочие…

28 of 39

Планировщик cron

  • Серверная ОС — чаще всего Linux.�
  • В Linux чаще всего используют планировщик задач cron.�
  • cron умеет запускать любые консольные команды по расписанию, в том числе консольные скрипты PHP.�
  • При переносе сайта на другой хостинг/сервер нужно переносить конфигурацию cron.
  • На классических хостингах cron настраивают через панель управления хостинга.
  • На отдельном сервере конфигурация cron расположена в файле /etc/crontab. Редактировать его — неправильно!
  • Вместо этого надо редактировать crontab пользователя, от имени которого работает веб-сервер (Apache или PHP FPM):

user@host:~$ crontab -e -u <польз.>

29 of 39

Конфигурация планировщика cron

* * * * * php -f script1.php

15 2 * * * php -f script2.php

*/10 * * * * php -f script3.php

0 0 1 * * php -f script4.php

0 6,15 * * sun php -f script5.php

Минуты

Часы

Дни

Месяцы

Дни недели

Команда

Пустая строка в конце обязательна!

Иначе последняя команда не будет запускаться.

30 of 39

Собственный планировщик «на хитах»

  • В очень редких случаях нет возможности использовать системный планировщик (cron).�
  • Можно симулировать работу cron самостоятельно.
  • Посетители сайта отправляют HTTP-запросы на сервер.�
  • Во время обработки запроса можно также выполнять задачи по расписанию.�
  • Пока на сайте есть посетители, задачи будут выполняться.�
  • Цена такого решения — обработка запроса выполняется дольше.

31 of 39

Собственный планировщик «на хитах»

Завести таблицу (аналог конфигурации cron), в которой хранить:

  1. ID задачи
  2. дату и время последнего запуска
  3. интервал запуска
  • На каждой странице подключить код, который:
    • находит по таблице задачу, которую пора запустить;
    • запускает каждую задачу;
    • после завершения задачи задает новый lastRun.
  • Таким образом можно запустить несколько задач подряд — сколько?�
  • Что может пойти не так?

id

lastRun

interval

...

task1

01.01.2022 17:41:19

60

task2

01.01.1970 00:00:00

300

...

32 of 39

Особенности планировщика «на хитах»

  • Каждая задача должна выполняться достаточно быстро, чтобы человек (посетитель) этого не замечал.
  • Нельзя выполнять много задач на одном HTTP-запросе.
  • Требуется синхронизация: два параллельных HTTP-запроса не должны запустить одну и ту же задачу дважды.
  • Скрипты, обрабатывающие AJAX-запросы, должны работать как можно быстрее, поэтому на них запускать фоновые задачи не нужно:
    • перед подключением ядра объявить константу «не выполнять фоновые задачи»
    • в коде обработки фоновых задач ничего не запускать, если константа задана

33 of 39

Длительные задачи

34 of 39

Выполнение длительных задач

Примеры задач:

  • Импорт большого объема данных (миллионы строк)�
  • Создание резервной копии�
  • Массовые изменения в данных�
  • Ресурсоемкие вычисления

Два способа выполнения длительных задач:

  1. в фоне — планировщик запускает задачу�
  2. НЕ в фоне — браузер отправляет запросы, чтобы задача продолжала выполняться

35 of 39

НЕ фоновое выполнение длительных задач

  • Задача выполняется долго (несколько минут или часов).�
  • Длительность выполнения скрипта по HTTP-запросу ограничена.�Сколько по-умолчанию?�
  • Можно «нарезать» задачу на части. Например, импорт:
    • 1 запрос: загрузить файл
    • 2 запрос: импортировать 1000 строк
    • 3 запрос: импортировать след. 1000
    • и т. д.

<?php

const path = 'upload/big.csv';

const max = 1000;

$pos = (int) ($_GET['pos'] ?? 0);

$f = fopen(path, 'r');

fseek($pos);

$cnt = 0;

while ($cnt < max && $row = fgetcsv($f)) {

// Обработать строку $row...

$cnt++;

}

$pos = ftell($f);

$finished = feof($f);

fclose($f);

if (!$finished) {

header("Location: import.php?pos={$pos}");

}

echo 'Готово.';

36 of 39

НЕ фоновое выполнение длительных задач

Ограничения:

  • Процесс остановится, если:
    • компьютер отключится от сети
    • компьютер «заснет» или отключится
    • …�
  • Обычные пользователи не понимают разницы между «в фоне» и «не в фоне». Они просто нажимают кнопку. А если задача длительная — могут уйти пить кофе или даже закрыть ноутбук и уехать.

Совет:

«Не в фоне» можно выполнять только задачи, которые будут запускать программисты или администраторы — технически грамотные лица.

37 of 39

Фоновое выполнение длительных задач

Если задачу нужно запускать через равные периоды времени:

  • Просто настроить планировщик (cron)

Если задачу надо запускать по требованию:

  • Записать в таблицу (очередь задач) что нужно запустить и входные данные�
  • Написать скрипт, который проверяет очередь и выполняет одну задачу�
  • Настроить планировщик на запуск этого скрипта каждую минуту�
  • Код задачи должен записывать прогресс в БД, для пользователя сделать интерфейс, который выводит прогресс задачи

38 of 39

Очередь задач

  • Не обязательно программировать очередь задач самостоятельно (в виде таблицы).�
  • Можно использовать брокер сообщений. Самый популярный — RabbitMQ.�
  • Брокер применяют с целью:
    • создания шины сообщений для интеграции множества программ в единую систему
    • создания распределенных очередей

С точки зрения брокера есть два действующих лица:

  • Producer (поставщик) — создает новые сообщения, которые брокер добавляет в очередь.�
  • Consumer (потребитель) — получает сообщения из очереди по одному и обрабатывает.

Сообщение может описывать задачу, которую нужно выполнить.

39 of 39

Характеристики брокеров сообщений

  • Пропускная способность — кол-во сообщений в секунду.
  • Длительность хранения сообщений — в ОЗУ или на диске.
  • Гарантии доставки — 0..N или 1..N, транзакции.
  • Гарантия сохранения порядка сообщений (не обязательно).
  • Возможность масштабирования — создание реплик.
  • Возможность маршрутизации сообщений.