Как можно использовать многопоточность в приложениях php

POST запрос при помощи cURL

Теперь давайте отправим post запрос на адрес https://httpbin.org/anything

$url = 'https://httpbin.org/anything'; // url, на который отправляется запрос
$post_data = [ // поля нашего запроса
    'field1' => 'val_1',
    'field2' => 'val_2',
];

$headers = []; // заголовки запроса

$post_data = http_build_query($post_data);

$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true); // true - означает, что отправляется POST запрос

$result = curl_exec($curl);

Отлично, с GET и POST запросами в cURL мы немного освоились.
Теперь разберемся с заголовками, которые мы можем отсылать в запросе.

Заголовки устанавливаются при помощи опции CURLOPT_HTTPHEADER
Чтобы получше узнать, для чего нужна эта опция давайте попробуем отправить POST запрос в формате JSON

Синхронизация

В последнем разделе этой статьи мы рассмотрим синхронизацию в pthreads. Синхронизация — это метод, позволяющий контролировать доступ к общим ресурсам.

Для примера, давайте реализуем простейший счетчик:

$counter = new class extends Thread {
    public $i = 0;

    public function run()
    {
        for ($i = 0; $i < 10; ++$i) {
            ++$this->i;
        }
    }
};

$counter->start();

for ($i = 0; $i < 10; ++$i) {
    ++$counter->i;
}

$counter->join();

var_dump($counter->i); // выведет число от 10 до 20

Без использования синхронизации, вывод не детерминирован. Несколько потоков пишут в одну переменную без контролируемого доступа, что означает что обновления будут потеряны.

Давайте исправим это так, что мы получим правильный вывод , путем добавления синхронизации:

$counter = new class extends Thread {
    public $i = 0;

    public function run()
    {
        $this->synchronized(function () {
            for ($i = 0; $i < 10; ++$i) {
                ++$this->i;
            }
        });
    }
};

$counter->start();

$counter->synchronized(function ($counter) {
    for ($i = 0; $i < 10; ++$i) {
        ++$counter->i;
    }
}, $counter);

$counter->join();

var_dump($counter->i); // int(20)

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

Вот поочерёдный инкремент в двух синхронизированных циклах while:

$counter = new class extends Thread {
    public $cond = 1;

    public function run()
    {
        $this->synchronized(function () {
            for ($i = 0; $i < 10; ++$i) {
                var_dump($i);
                $this->notify();

                if ($this->cond === 1) {
                    $this->cond = 2;
                    $this->wait();
                }
            }
        });
    }
};

$counter->start();

$counter->synchronized(function ($counter) {
    if ($counter->cond !== 2) {
        $counter->wait(); // wait for the other to start first
    }

    for ($i = 10; $i < 20; ++$i) {
        var_dump($i);
        $counter->notify();

        if ($counter->cond === 2) {
            $counter->cond = 1;
            $counter->wait();
        }
    }
}, $counter);

$counter->join();

/* Вывод:
int(0)
int(10)
int(1)
int(11)
int(2)
int(12)
int(3)
int(13)
int(4)
int(14)
int(5)
int(15)
int(6)
int(16)
int(7)
int(17)
int(8)
int(18)
int(9)
int(19)
*/

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

Это важно, потому что уведомления могут поступать из других мест, кроме как при вызове. Таким образом, если вызовы метода не были заключены в условиях, мы будем выполнять ложные вызовы пробуждения, которые приведут к непредсказуемому поведению кода

Настройка PHP

В документации сказано, что PHP должен быть скомпилирован с опцией —enable-maintainer-zts. Я не пробовал сам компилировать, вместо этого нашел пакет для Debian, который и установил себе.

Таким образом у меня остался прежний PHP, который запускается из консоли обычным образом, с помощью команды . Соответственно, веб сервер использует его-же. И появился еще один PHP, который можно запускать из консоли через .

После этого можно ставить расширение pthreads.

Вот теперь все. Ну… почти все. Представьте, что вы написали мультипоточный код, а PHP на машине у коллеги не настроен соответствующим образом? Конфуз, не правда ли? Но выход есть.

cURL запросы с сохранением и загрузкой cookie из файла

cURL позволяет нам установить cookie при передачи запросов, а также автоматически принимать и устанавливать cookie, которые нам возвращает сервер, сохраняя их между запросами.

Давайте рассмотрим такой пример:

$cookiePath = __DIR__ . '/cookie.txt';

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "https://yandex.ru/");
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);

//CURLOPT_COOKIEJAR - файл, куда пишутся куки после закрытия коннекта, например после curl_close()
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiePath);

//CURLOPT_COOKIEFILE - файл, откуда читаются куки.
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiePath);

$result = curl_exec($ch);

// теперь в этой переменной будут содержаться cookie, которые установил сервер
$cookies = curl_getinfo($ch, CURLINFO_COOKIELIST);

curl_close($ch);

// выводим на экран содержимое файла cookie.txt, которое установил нам Yandex.
// можете изучить сколько всего он сохраняет на ваш компьютер при первом же заходе.
echo file_get_contents($cookiePath);

Теперь cookie у нас хранятся в файле cookie.txt в директории со скриптом (если вы ничего не меняли). Если мы совершаем повторные запросы, то cURL автоматически берет и отправляет cookie на сервер, как и обычный браузер. Таким образом мы можем авторизироваться на сайте и сохранить сеанс между запросами.

Когда не стоит использовать pthreads

Прежде чем мы начнём, я хотел бы уточнить, когда вы не должны (да и не можете) использовать расширение pthreads.

В pthreads v2, рекомендация была в том, что pthreads не должна использоваться в веб-серверной среде (т.е. в fcgi процессе). Что касается pthreads v3, эта рекомендация является программным ограничением, так что теперь вы просто не сможете использовать его в среде веб-сервера. Две известные причины:

  1. Это небезопасно использовать несколько потоков в такой среде (например, в связи с ошибками ввода-вывода, да и кучи других проблем).
  2. Это не очень хорошо масштабируется. Например, скажем, у вас есть PHP-скрипт, который создает новый поток, чтобы обработать какую-то задачу, и этот скрипт выполняется при каждом запросе. Это означает, что для каждого запроса, ваше приложение будет создавать новый поток (это модель потоков 1:1 – один поток на один запрос). Если ваше приложение обслуживает 1тыс. запросов в секунду, это означает создание 1тыс. нитей в секунду! Наличие множества потоков, запущенных на одной машине, быстро наводнит ее, и проблема будет лишь усугубляться, т. к. скорость выполнения запроса будет увеличиваться.

Вот почему многопоточность не является хорошим решением в такой среде

Если вы рассматриваете многопоточность как решение задач, блокирующих ввод-вывод (например, выполнение http-запросов), то позвольте мне обратить ваше внимание на асинхронное программирование, которое можно реализовать с помощью фреймворков, таких как Amp

После такого отступления, давайте сразу перейдём к делу!

Отправка файлов

Для того, чтобы отправить файлы на сервер, мы просто заполняем поля POST запроса, указывая там специальный класс CURLFile. На сервере вы можете получить отправленные файлы, при помощи суперглобального массива $_FILES.

$url = 'https://phpstack.ru/';

$postFields = [
    'photo1' => new \CURLFile( __DIR__ . '/img1.jpg' ),
    'photo2' => new \CURLFile( __DIR__ . '/img2.jpg' ),
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$html = curl_exec($ch);

curl_close($ch);

👨‍💻 Создаем запрос к OpenWeatherAPI с помощью curl

  • Предположим, что практическое занятие раздела выполнено, возвращаемся в Postman.
  • В любом запросе кликаем на кнопку под кнопкой
  • В диалоговом окне “Generate Code Snippets” выбираем cURL из выпадающего списка и нажимаем на кнопку

Код Postman для запроса прогноза погоды OpenWeatherMap выглядит в формате cURL следующим образом:

Postman добавил свою информацию о хедере (обозначено -Н) Тэги добавленного заголовка можно удалить. Также можно удалить знаки “», они добавлены для читаемости текста.

Кроме того, обратите внимание, что в Windows нужно изменить одинарные кавычки на двойные, потому что одинарные кавычки не поддерживаются в терминале Windows по умолчанию. Вот запрос curl с удаленными символами -H и обратной косой чертой, а одинарные кавычки преобразованы в двойные кавычки:

Вот запрос curl с удаленными символами -H и обратной косой чертой, а одинарные кавычки преобразованы в двойные кавычки:

  • Curl доступен на MacOS по умолчанию. Если на Windows curl еще не установлен, то инструкции по установке по , нужно выбрать одну из бесплатных версий с правами Администратора.
  • Открываем терминал
    • на OS Windows нажимаем и вводим команду , Правой кнопкой мыши вызываем меню и выбираем для вставки запроса.
    • на MacOS открываем iTerm или терминал, нажимая и вводим команду Вставляем запрос в командную строку и жмем кнопку .

Ответ от OpenWeatherMap на наш запрос будет выглядеть так:

Этот запрос минимизирован. Вы можете развернуть его, например на сайте JSON pretty print или, на MacOS с установленным Python добавив в конец cURL запроса, чтобы минимизировать JSON в ответе (Для подробностей можно посмотреть ветку на Stack Overflow по этой теме).

Самостоятельно сделаем curl запрос на 5-дневный прогноз, сохраненный в Postman. И третий API запрос OpenWeatherMap? сохраненный в Postman тоже выполняем в curl

Использование wget Linux

Команда wget linux, обычно поставляется по умолчанию в большинстве дистрибутивов, но если нет, ее можно очень просто установить. Например установка с помощью yum будет выглядеть следующим образом:

А в дистрибутивах основанных на Debian:

Теперь перейдем непосредственно к примерам:

1. Загрузка файла

Команда wget linux скачает один файл и сохранит его в текущей директории. Во время загрузки мы увидим прогресс, размер файла, дату его последнего изменения, а также скорость загрузки:

Опция -О позволяет задать имя сохраняемому файлу, например, скачать файл wget с именем wget.zip:

Вы можете скачать несколько файлов одной командой даже по разным протоколам, просто указав их URL:

4. Взять URL из файла

Вы можете сохранить несколько URL в файл, а затем загрузить их все, передав файл опции -i. Например создадим файл tmp.txt, со ссылками для загрузки wget, а затем скачаем его:

5. Продолжить загрузку

Утилита wget linux рассчитана на работу в медленных и нестабильных сетях. Поэтому если вы загружали большой файл, и во время загрузки было потеряно соединение, то вы можете скачать файл wget с помощью опции -c.

6. Загрузка файлов в фоне

Опция -b заставляет программу работать в фоновом режиме, весь вывод будет записан в лог файл, для настройки лог файла используются специальные ключи wget:

7. Ограничение скорости загрузки

Команда wget linux позволяет не только продолжать загрузку файлов, но и ограничивать скорость загрузки. Для этого есть опция —limit-rate. Например ограничим скорость до 100 килобит:

Здесь доступны, как и в других подобных командах индексы для указания скорости — k — килобит, m — мегабит, g — гигабит, и так далее.

8. Подключение по логину и паролю

Некоторые ресурсы требуют аутентификации, для загрузки их файлов. С помощью опций —http-user=username, –http-password=password и —ftp-user=username, —ftp-password=password вы можете задать имя пользователя и пароль для HTTP или FTP ресурсов.

Или:

9. Загрузить и выполнить

Вы, наверное, уже видели такие команды. wget позволяет сразу же выполнять скачанные скрипты:

Если опции -O не передать аргументов, то скачанный файл будет выведен в стандартный вывод, затем мы его можем перенаправить с интерпретатор bash, как показано выше.

По умолчанию wget сохраняет файл в текущую папку, но это поведение очень легко изменить с помощью опции -P:

11. Передать информацию о браузере

Некоторые сайты фильтруют ботов, но мы можем передать фальшивую информацию о нашем браузере (user-agent) и страницу с которой мы пришли (http-referer).

12. Количество попыток загрузки

По умолчанию wget пытается повторить загрузку 20 раз, перед тем как завершить работу с ошибкой. Количество раз можно изменить с помощью опции —tries:

13. Квота загрузки

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

Здесь работают те же индексы для указания размера — k, m, g, и т д.

14. Скачать сайт

Wget позволяет не только скачивать одиночные файлы, но и целые сайты, чтобы вы могли их потом просматривать в офлайне. Использование wget, чтобы скачать сайт в linux выглядит вот так:

Создайте поток, реализуя работающий интерфейс

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

Шаг 1

В качестве первого шага вам необходимо реализовать метод run(), предоставляемый интерфейсом Runnable. Этот метод обеспечивает точку входа для потока, и вы поместите всю свою бизнес-логику в этот метод. Ниже приведен простой синтаксис метода run().

public void run( )

Шаг 2

На втором этапе вы создадите экземпляр объекта Thread, используя следующий конструктор.

Thread(Runnable threadObj, String threadName);

Где – это экземпляр класса, который реализует интерфейс Runnable, а threadName – это имя, данное новому потоку.

Шаг 3

Как только объект Thread создан, вы можете запустить его, вызвав метод start(), который выполняет вызов метода run(). Ниже приведен простой синтаксис метода start().

void start();

Пример

Вот пример, который создает новый поток и запускает его.

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
public class TestThread {
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

Итог

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Иммитация браузера с помощью cURL

Иногда сайт, к которому мы обращаемся может фильтровать запросы, защищаясь от парсинга. Если для этого используются упрощенные способы защиты, например проверка User-Agent, то мы можем легко притвориться, что являемся реальным польователем, который взаимодействует с сайтом через браузер, мы можем послать заголовки и cookie, которые обычно посылает браузер.

В данном примере установлены заголовки, которые посылает Chrome.

$url = 'https://phpstack.ru/';

$headers = [
    'Connection: keep-alive',
    'Upgrade-Insecure-Requests: 1',
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'Accept-Encoding: gzip, deflate',
    'Accept-Language: ru,en-US;q=0.9,en;q=0.8',
];

$cookieFile = __DIR__ . '/cookie.txt';

$curl = curl_init(); // создаем экземпляр curl

curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookieFile);
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookieFile);
curl_setopt($curl, CURLOPT_POST, false); //
curl_setopt($curl, CURLOPT_URL, $url);

$result = curl_exec($curl);

В простых ситуациях этого хватает. Но если используется защита при помощи javascript или что-то более продвинутое, то здесь cURL бессилен, и следует использовать либо BAS либо Zennoposter. Либо если вы хотите попытать счастье с PHP, то Selenium.

Не используйте эти знания в противоправных целях.

Пример

Можно решать разные задачи в несколько потоков. Мне было интересно решить одну конкретную и как мне кажется весьма типовую задачу. Напомню ее еще раз. Есть пул заданий, их надо побыстрее выполнить.

Так давайте приступим. Для этого создадим провайдер данных (Threaded), он будет один и общий для всех потоков.

Для каждого потока у нас будет (Worker), где будет храниться ссылка на провайдер.

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

Обратите внимание, что данные из провайдера забираем в. Иначе есть вероятность часть данных обработать более 1 раза, или пропустить часть данных.
Теперь заставим все это работать с помощью

Вот и все! Ну почти все. На самом деле есть то, что может огорчить пытливого читателя. Все это не работает на стандартном PHP, скомпилированным с опциями по умолчанию. Чтобы насладиться многопоточностью, надо, чтобы в вашем PHP был включен ZTS (Zend Thread Safety).

pthreads и (не)изменяемость

Последний класс, которого мы коснёмся, – , – новое дополнение к pthreads v3

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

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

Давайте взглянем на пример, который продемонстрирует новые ограничения неизменяемости:

class Task extends Threaded // a Threaded class
{
    public function __construct()
    {
        $this->data = new Threaded();
        // $this->data is not overwritable, since it is a Threaded property of a Threaded class
    }
}

$task = new class(new Task()) extends Thread { // a Threaded class, since Thread extends Threaded
    public function __construct($tm)
    {
        $this->threadedMember = $tm;
        var_dump($this->threadedMember->data); // object(Threaded)#3 (0) {}
        $this->threadedMember = new StdClass(); // invalid, since the property is a Threaded member of a Threaded class
    }
};

-свойства у классов , с другой стороны, изменяемы:

class Task extends Volatile
{
    public function __construct()
    {
        $this->data = new Threaded();
        $this->data = new StdClass(); // valid, since we are in a volatile class
    }
}

$task = new class(new Task()) extends Thread {
    public function __construct($vm)
    {
        $this->volatileMember = $vm;

        var_dump($this->volatileMember->data); // object(stdClass)#4 (0) {}

        // still invalid, since Volatile extends Threaded, so the property is still a Threaded member of a Threaded class
        $this->volatileMember = new StdClass();
    }
};

Мы видим, что класс переопределяет неизменяемость, навязанную родительским классом , чтобы предоставить возможность изменять -свойства (а также -ить).

Есть ещё один предмет обсуждения чтобы раскрыть тему изменяемости и класса – массивы. В pthreads массивы автоматически приводятся к -объектам при присвоении к свойству класса . Это потому что просто небезопасно манипулировать массивом из нескольких контекстов PHP.

Давайте снова взглянем на пример, чтобы лучше понимать некоторые вещи:

$array = ;

$task = new class($array) extends Thread {
    private $data;

    public function __construct(array $array)
    {
        $this->data = $array;
    }

    public function run()
    {
        $this->data = 4;
        $this->data[] = 5;

        print_r($this->data);
    }
};

$task->start() && $task->join();

/* Вывод:
Volatile Object
(
     => 1
     => 2
     => 3
     => 4
     => 5
)
*/

Мы видим, что -объекты могут быть обработаны так, как если бы они были массивами, т. к. они поддерживают операции с массивами, такие как (как показано выше) оператор подмножеств (). Однако, классы не поддерживают базовые функции с массивами, такие как и . Вместо этого, класс предоставляет нам подобные операции как встроенные методы.

В качестве демонстрации:

$data = new class extends Volatile {
    public $a = 1;
    public $b = 2;
    public $c = 3;
};

var_dump($data);
var_dump($data->pop());
var_dump($data->shift());
var_dump($data);

/* Вывод:
object(class@anonymous)#1 (3) {
  => int(1)
  => int(2)
  => int(3)
}
int(3)
int(1)
object(class@anonymous)#1 (1) {
  => int(2)
}
*/

Другие поддерживаемые операции включают в себя и .

Многопоточные вычисления в PHP: pthreads +35

  • 17.05.16 06:22


mnv

#300952

Хабрахабр

12600

Программирование, Параллельное программирование, PHP

Недавно я попробовал pthreads и был приятно удивлен — это расширение, которое добавляет в PHP возможность работать с несколькими самыми настоящими потоками. Никакой эмуляции, никакой магии, никаких фейков — все по-настоящему.

Я рассматриваю такую задачу. Есть пул заданий, которые надо побыстрее выполнить. В PHP есть и другие инструменты для решения этой задачи, тут они не упоминаются, статья именно про pthreads.

Стоит отметить, что автор расширения, Joe Watkins, в своих статьях предупреждает, что многопоточность — это всегда не просто и надо быть к этому готовым.

Кто не испугался, идем далее.

cURL запросы через прокси

Простой пример для отправки запросов через proxy. Если ваш прокси предполагает авторизацию, то раскомментируйте соответствующие строчки.

$url = 'http://dynupdate.no-ip.com/ip.php'; // тут мы можем узнать свой IP адрес
$proxy = '127.0.0.1:8888';
//$proxyauth = 'user:password'; // если прокси с авторизацией, то раскомметируйте

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_PROXY, $proxy);
//curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyauth); // если прокси с авторизацией, то раскомметируйте
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
$html = curl_exec($ch);
curl_close($ch);

echo $html;

Несколько примеров на Guzzle

GET запросы на Guzzle

// Создаем клиента с базовым URL
$client = new GuzzleHttp\Client(['base_uri' => 'https://foo.com/api/']);
// Посылаем запрос на https://foo.com/api/test
$response = $client->request('GET', 'test');
// Посылаем запрос на https://foo.com/root
$response = $client->request('GET', '/root');

Разные типы запросов на Guzzle

$response = $client->get('http://httpbin.org/get');
$response = $client->delete('http://httpbin.org/delete');
$response = $client->head('http://httpbin.org/get');
$response = $client->options('http://httpbin.org/get');
$response = $client->patch('http://httpbin.org/patch');
$response = $client->post('http://httpbin.org/post');
$response = $client->put('http://httpbin.org/put');

Асинхронные запросы на Guzzle

$promise = $client->getAsync('http://httpbin.org/get');
$promise = $client->deleteAsync('http://httpbin.org/delete');
$promise = $client->headAsync('http://httpbin.org/get');
$promise = $client->optionsAsync('http://httpbin.org/get');
$promise = $client->patchAsync('http://httpbin.org/patch');
$promise = $client->postAsync('http://httpbin.org/post');
$promise = $client->putAsync('http://httpbin.org/put');

Если интересно, то читайте: Guzzle Quick Start

Пишите комментарии, если что-то осталось непонятно.

Помогла ли Вам эта статья?

Да
Нет

Пример

Можно решать разные задачи в несколько потоков. Мне было интересно решить одну конкретную и как мне кажется весьма типовую задачу. Напомню ее еще раз. Есть пул заданий, их надо побыстрее выполнить.

Так давайте приступим. Для этого создадим провайдер данных (Threaded), он будет один и общий для всех потоков.

Для каждого потока у нас будет (Worker), где будет храниться ссылка на провайдер.

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

Обратите внимание, что данные из провайдера забираем в. Иначе есть вероятность часть данных обработать более 1 раза, или пропустить часть данных.
Теперь заставим все это работать с помощью

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

Вот и все! Ну почти все. На самом деле есть то, что может огорчить пытливого читателя. Все это не работает на стандартном PHP, скомпилированным с опциями по умолчанию. Чтобы насладиться многопоточностью, надо, чтобы в вашем PHP был включен ZTS (Zend Thread Safety).

Категории

1: запросы MySQL

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

Типичная настройка в , которая влияет на производительность потока:

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

Если вы используете Solaris, тогда вы можете использовать

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

Эта переменная устарела с MySQL 5.6.1 и удалена в MySQL 5.7. Вы должны удалить это из файлов конфигурации MySQL всякий раз, когда вы видите это, если они не для Solaris 8 или ранее.

InnoDB::

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

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

Другие:

Другие конфигурации, на которые следует обратить внимание, включают в себя , , и т.д., Которые могут привести к повышению производительности

PHP:

В чистом PHP вы можете создать MySQL Worker, где каждый запрос выполняется в отдельных потоках PHP

2. Разбор HTML-контента

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

Работа над одним документом по одному может быть очень, очень медленным и болезненным процессом. @ka однажды взломав свой выход с помощью ajax для вызова нескольких запросов, некоторые творческие умы просто разветвляют процесс, используя pcntl_fork, но если вы используете , то вы не можете воспользоваться из

С , поддерживающим как Windows, так и Unix-системы, у вас нет таких ограничений. Это так же просто, как… Если вам нужно разобрать 100 документов? Spawn 100 Threads… Простой

Сканирование HTML

Выход

Класс используется

Эксперимент

Попробуйте проанализировать файлы, которые имеют ссылки без потоков, и посмотрите, сколько времени это займет.

Лучшая архитектура

Создание слишком большого количества потоков, что не очень разумно делать в производстве. лучше было бы использовать пул. Имейте пул определения рабочих, а затем стека с

Улучшение производительности

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

3. Обновление поискового индекса

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

Представляем мероприятие

@rdlowrey Цитата 1:

@rdlowrey Цитата 2:

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

Я знаю, что весь этот вопрос , но если у вас есть время, вы можете посмотреть этот Ядерный реактор, написанный на PHP @igorw

Как получить заголовки ответа

В предыдущем примере мы научились посылать заголовки. Самый правильный способ принять заголовки:

$ch = curl_init();

$headers = [];

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// ... остальные опции

// this function is called by curl for each header received
curl_setopt($ch, CURLOPT_HEADERFUNCTION,  function($curl, $header) use (&$response_headers)  {
    $len = strlen($header);
    $header = explode(':', $header, 2);
    if (count($header) < 2) { // ignore invalid headers
      return $len;
    }

    $headers))][] = trim($header);

    return $len;
  }
);

$data = curl_exec($ch);

print_r($response_headers); // выводим заголовки ответа

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

Рассмотрим такой пример:

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
// ...

$response = curl_exec($ch);

// Then, after your curl_exec call:
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);

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

Авторизация с помощью cURL

HTTP Авторизация

Чтобы с помощью cURL авторизироваться на сайте, который использует Basic HTTP-аутентификацию нужно установить опцию CURLOPT_USERPWD, в которой будет наш логин и пароль.

Пример:

$login = 'test_login'; // наш логин
$password = 'test_password'; // наш пароль
$url = 'https://phpstack.ru/';

$ch = curl_init($url);

curl_setopt($ch, CURLOPT_USERPWD, "$login:$password");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$result = curl_exec($ch);

curl_close($ch);

OAuth авторизация

$url = 'https://phpstack.ru/';
$oauthToken = 'Bearer dsfgdsfgdsfgdsfgdsfg'; // наш токен

$ch = curl_init($url);

curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization: $oauthToken"));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$html = curl_exec($ch);

curl_close($ch);

Авторизация через форму

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

Допустим на сайте есть такая форма:

<html>
<body>
 
<form method = "POST" action="https://phpstack.ru/admin/' >
  <input  name="login"  type="text"> 
  <input  name="password"  type="text">
  <input  type="submit"  name="submit"  value="Отправить" >
</form>
</body>
</html>

Тогда наш cURL запрос должен быть сформирован так:

$url = 'http://phpstack.ru/admin/'; // url, на который отправляется запрос

$postData = [ // поля нашего запроса
    'login' => 'our_login', // наш логин
    'password' => 'our_password', // наш пароль
];

$cookieFile = __DIR__ . '/cookie.txt';

// притворяемся браузером
$headers = [
    'Connection: keep-alive',
    'Upgrade-Insecure-Requests: 1',
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'Accept-Encoding: gzip, deflate',
    'Accept-Language: ru,en-US;q=0.9,en;q=0.8',
];

$post_data = http_build_query($post_data);

$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookieFile);
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookieFile);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true); // true 

$result = curl_exec($curl);

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

Скачивание больших файлов с помощью cURL

Для того, чтобы скачать большой файл пригодится этот способ:

$url = 'https://example.com/big_file.zip'; // откуда скачиваем файл
$path = __DIR__ . '/big_file.zip';  // куда сохраняем файл

$fp = fopen($path, 'w');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$data = curl_exec($ch);
curl_close($ch);
fclose($fp);

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

Также к памяти будет требователен следующий код:

$ch = curl_init('https://example.ru/big_file.zip');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
$data = curl_exec($ch);
curl_close($ch);

file_put_contents(__DIR__ . '/big_file.zip', $data);

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

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: