Запускаем PHP-скриптики через php-fpm без web-сервера. Или свой FastCGI-клиент (под капотом)

    Приветствую всех читателей "Хабра".


    Дисклеймер


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


    Вступление


    В данной статье хотелось бы рассказать о решении довольно нестандартной задачи, с которой пришлось столкнуться во время рабочего процесса. А именно, нам понадобилось запускать в цикле кучу php скриптов. О причинах и о спорности подобного архитектурного решения в данной статье распространяться не буду, т.к. собственно она и не про это вовсе, просто была задача, ее нужно было решить и решение показалось мне достаточно интересным чтобы им поделиться с Вами, тем более манов по данному вопросу в интернете я не нашел совсем (ну разумеется кроме официальных спецификаций). Спеки конечно это хорошо и в них конечно все есть, но думаю вы согласитесь, что если вы не особо знакомы с темой, да и еще и ограничены по времени то разбираться в них то еще удовольствие.


    Для кого эта статья


    Для всех кто работает с web-ом и о протоколе FastCgi знает лишь что это протокол в соответствии с котороым web-сервер запускает php скриптики, но хочет более детально его изучить и заглянуть под капот.


    Обоснование (зачем эта статья)


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


    shell_exec('php \path\to\script.php')

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


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


    Хоть задачу в итоге и решили, но разобраться в FastCgi детально я все таки решил, и в добавок решил написать об этом статью, в которой будет просто и подробно описано как заставить php-fpm запустить php скрипт без web-сервера, а точнее в качестве web-сервера будет другой скрипт, далее его буду называть Fcgi клиент. В общем надеюсь что данная статья поможет тем кто столкнулся с такой же задачей как и мы и прочитав ее сможет быстро все написать как ему надо.


    Творческий поиск (ложный путь)


    Итак проблема обозначена, надо приступать к решению. Естественно как любой "нормальный" программист для решения задачи, про которую ни где не написано что делать и что вводить в консоль, я не стал читать и переводить спецификацию, а сразу же придумал свое "гениальное" решение. Суть его в следующем, я знаю что nginx (мы используем nginx и чтобы не писать далее дурацкое — web-сервер, буду писать nginx, так как то посимпатичнее) что то передает в php-fpm, это что то php-fpm обрабатывает и на основе него запускает скрипт, что ж вроде все просто, возьму да залогирую то что передает nginx и передам то же самое.


    Тут поможет великолепный netcat (UNIX-утилита для работы с сетевым трафиком, которая по моему может практически все). Итак ставим netcat на прослушивание локального порта, а nginx настраиваем на работу с php файлами через сокет (естественно сокет на том же порту который слушает netcat)


    слушаем 9000 порт


    nc -l 9000

    Проверить что все ок, можно обратившись через браузер на адрес 127.0.0.1:9000 должна быть следующая картина



    настраиваем nginx чтобы он php скрипты обрабатывал через сокет на 9000 порту (в настройках '/etc/nginx/sites-available/default', конечно могут отличаться)


    location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass 127.0.0.1:9000;
    }
    

    После этих манипуляций проверим что же получилось, обратившись к php скрипту через браузер



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



    Но это тоже мало что нам дает, наверное чисто теоретически их можно перевести в двоичную кодировку, каким то образом (даже не представляю каким) их отправить в сокет fpm, и даже есть вероятность что весь этот велосипед как то сработает, и даже запустит какой то скрипт, но уж как то все это страшненько и кривенько.


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


    Все пропало, изучения спецификации не миновать!


    Решение (тут собственно начинается вся соль данной статьи)


    Теоретическая подготовка


    Давайте теперь рассмотрим как же все таки происходит соединение и обмен данными между nginx и php-fpm. Немного теории, все общение происходит как уже понятно через сокеты, далее будем рассматривать конкретно соединение через TCP сокет.


    Единицей информации в протоколе FastCgi является cgi запись. Такие записи сервер отправляет приложению и точно такие же записи получает в ответ.


    Немного теории (структуры)

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


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


    //допустим вам пришли такие данные
    1101111000000010010110000010011100010000

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


    //получилось у нас 5 байт
    11011110 00000010 01011000 00100111 00010000
    
    //переведем в десятичную систему
    222 2 88 39 16

    Отлично мы интерпретировали их и получили какие то результаты, допустим что эти данные отвечают за то сколько определенная квартира должна за электроэнергию. Получается что в доме 222 квартира номер 2 должна заплатить 88 рублей. А что еще за две цифры, что с ними делать просто отбросить? Конечно нет! дело в том что мы не имели нотации (формата) которая подсказала бы нам как интерпретировать данные, и интерпретировали их по своему, в связи с этим получили не только бесполезный, но и вредный результат. В итоге квартира 2 заплатила совершенно не то что должна была. (примеры конечно надуманные и служат лишь для того чтобы более понятно объяснить ситуацию)


    Теперь посмотрим как же мы должны были интерпретировать правильно эти данные, имея нотацию (формат). Далее буду называть вещи своими именами, а именно нотация = формат (вот тут форматы).


    //формат следующий
    "Cnn"
    
    //расшифровка формата
    //C - беззнаковый символ (char) (8 бит)
    //n - беззнаковый short (16 бит)
    
    //разобьем данные в соответствии с форматом
    11011110 0000001001011000 0010011100010000
    
    //переведем в десятичную систему
    222 600 10000

    Теперь все сходиться в доме №222 квартира 600 за электричество должна 1000 рублей Думаю теперь ясна важность формата, и теперь понятно как примерно выглядит условно Си подобная структура. (прошу обратить внимания, тут цель не детально объяснить что такое эти структуры, а дать общее понимание что это такое и как это работает)


    Условное обозначение данной структуры будет такое


    struct {
    unsigned char houseNumber;
    unsigned char flatNumperA1;
    unsigned char flatNumperA2;
    unsigned char summB1;
    unsigned char summB2;
    };
    
    //одинаковые имена, с разными окончаниями означают что в них хранится одно значение
    // houseNumber - дом
    // flatNumperA1 && flatNumperA2 - квартира
    // summB1 && summB2 - сумма долга

    Еще немного теории (FastCgi записи)

    Как я уже сказал выше единицей информации в протоколе FastCgi являются записи. Записи сервер отправляет приложению и такие же записи получает в ответ. Запись состоит из заголовка и тела с данными.


    Структура заголовка:


    1. версия протокола (всегда 1) обозначается 1 байтом ('C')
    2. тип записи. Для открытия, закрытия соединения и др. все не буду рассматривать, далее рассмотрю только то что понадобится для конкретной задачи, если нужны другие — добро пожаловать сюда спецификация. Обозначается 1 байтом ('C').
    3. ID запроса, произвольное число, обозначается 2 байтами ('n')
    4. длинна тела записи (данных), обозначается 2 байтами ('n')
    5. длинна выравнивающих данных и зарезервированные данные, по одному байту (тут не нужно особо обращать внимания, дабы не отвлекаться от главного в нашем случае всегда будет 0)

    Далее идет само тело записи:


    1. сами данные (тут то именно и передаются переменные), могут иметь довольно большой размер (до 65535 байт)

    Вот пример самой простой FastCgi записи в двоичном виде с форматом


    struct {
    //Заголовок
    unsigned char version;
    unsigned char type;
    unsigned char idA1;
    unsigned char idA2;
    unsigned char bodyLengthB1;
    unsigned char bodyLengthB2;
    unsigned char paddingLength;
    unsigned char reserved;
    //Тело записи
    unsigned char contentData; //до 65535 байт
    unsigned char paddingData;
    };

    Практика


    Скрипт клиент и передающий сокет

    Для передачи данных будем использовать стандартное php расширение socket. И первое что нужно будет сделать — это настроить php-fpm на прослушивание порта на локальном хосте, например 9000. Это делается в большинстве случаем в файле '/etc/php/7.3/fpm/pool.d/www.conf', путь конечно зависит от настроек вашей системы. Там нужно прописать примерно следующее (всю портянку привожу чтобы можно было сориентироваться, главная секция здесь listen)


    ; The address on which to accept FastCGI requests.
    ; Valid syntaxes are:
    ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
    ; a specific port;
    ; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
    ; a specific port;
    ; 'port' - to listen on a TCP socket to all addresses
    ; (IPv6 and IPv4-mapped) on a specific port;
    ; '/path/to/unix/socket' - to listen on a unix socket.
    ; Note: This value is mandatory.
    ;listen = /run/php/php7.3-fpm.sock
    listen = 127.0.0.1:9002

    После настройки fpm, следующим этапом будет подключение к сокету


    $service_port = 9000;
    $address = '127.0.0.1';
    
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    $result = socket_connect($socket, $address, $service_port);

    Начало запроса FCGI_BEGIN_REQUEST


    Для открытия соединения мы должны отправить запись с типом FCGI_BEGIN_REQUEST = 1 Заголовок записи будет такой (для приведения числовых значений к бинарной строке с заданным форматом будет использована php функция pack())


    socket_write($socket, pack('CCnnCx', 1, 1, 1, 8, 0));
    //версия протокола - 1
    //тип записи - 1 - FCGI_BEGIN_REQUEST
    //id - 1
    //длинна тела запроса - 8 бит
    //выравнивание - 0

    Тело записи для открытия соединения должно содержать роль записи и флаг управляющий соединением


    //структура тела записи для открытия соединения
    //struct {
    // unsigned char roleB1;
    // unsigned char roleB0;
    // unsigned char flags;
    // unsigned char reserved[5];
    //};
    
    //php реализация
    socket_write($socket, pack('nCxxxxx', 1, 0));
    //роль - 1 - открытие
    //флаг - 1 - если упростить то 1 значит удерживать соединение

    Итак запись для открытия соединения успешно отправлена, php-fpm ее примет и далее будет ожидать от нас дальнейшей записи в которой нужно передать данные для разворачивания окружения и запуска скрипта.


    Передача параметров окружения FCGI_PARAMS


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


    Минимальные необходимые параметры окружения


    $url = '/path/to/script.php'
    
    $env = [
    'REQUEST_METHOD' => 'GET',
    'SCRIPT_FILENAME' => $url,
    ];

    Первое что нам тут нужно сделать — это подготовить необходимые переменные, то есть пары имя => значение, которые мы передадим приложению.


    Структура пар имя значение будет такая


    //для пар в которых значение имени и данных в менее 128 байт
    typedef struct {
    unsigned char nameLength;
    unsigned char valueLength;
    unsigned char nameData
    unsigned char valueData;
    };
    //имя и значение кодируется 1 байтом

    Идет сначала 1 байт — длинна имени, потом 1 байт значение


    //для пар в которых значение имени и данных более 128 байт
    typedef struct {
    unsigned char nameLengthA1;
    unsigned char nameLengthA2;
    unsigned char nameLengthA3;
    unsigned char nameLengthA4;
    unsigned char valueLengthB1;
    unsigned char valueLengthB2;
    unsigned char valueLengthB3;
    unsigned char valueLengthB4;
    unsigned char nameData
    unsigned char valueData;
    };
    //имя и значение кодируется 4 байтами

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


    Закодируем наши переменные в соответствии форматом


    $keyValueFcgiString = '';
    
    foreach ($env as $key => $value) {
    //длинна разных по длине значений кодируется по разному
    //если меньше 128 байт то одним байтом если больше то четырьмя
    $keyLen = strlen($key);
    $lenKeyChar = $keyLen < 128 ? chr($keyLen) : pack('N', $keyLen);
    
    $valLen = strlen($value);
    $valLenChar = $valLen < 128 ? chr($valLen) : pack('N', $valLen);
    
    $keyValueFcgiString .= $lenKeyChar . $valLenChar . $key . $value;
    }

    Тут значения меньше 128 бит кодируются функцией chr($keyLen), больше pack('N', $valLen), где 'N' обозначает 4 байта. И затем все это слепляется в одну строку в соответствии с форматом структуры. Тело записи готово.


    В заголовке записи передаем все то же самое как и в предыдущей записи, кроме типа (он будет FCGI_PARAMS = 4) и длинны данных (она будет равна длине пар имя => значение, или длине строки $keyValueFcgiString которую ранее мы сформировали).


    //отправка заголовка
    socket_write($socket, pack('CCnnCx', 1, 4, 1, strlen($keyValueFcgiString), 0));
    
    //отправка body
    socket_write($socket, $keyValueFcgiString);
    
    //для перевода приложения в режим выполнения и отправки ответа посылаем еще одну запись
    //с нулевым body
    socket_write($socket, pack('CCnnCx', 1, 4, 1, 0, 0));

    Получение ответа FCGI_PARAMS


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


    Получаем заголовок, он всегда равен 8 байт (получать данные будем по байту)


    $buf = '';
    $arrData = [];
    $len = 8;
    
    while ($len) {
    socket_recv($socket, $buf, 1, MSG_WAITALL); //получаем данные по 1 байту и пишем их в массив
    $arrData[] = $buf;
    $len--;
    }
    
    //интерпретируем заголовок в соответствии с форматом 'CCnnCx'
    $protocol = unpack('C', $arrData[0]);
    $type = unpack('C', $arrData[1]);
    $id = unpack('n', $arrData[2] . $arrData[3]);
    $dataLen = unpack('n', $arrData[4] . $arrData[5])[1]; //длинна данных в ответе, их нам надо будет получить после заголовка (unpack возвращает массив, по этому там индекс)
    $foo = unpack('C', $arrData[6]);
    
    var_dump($dataLen); //сколько байт будет в теле ответа

    Теперь в соответствии с полученной длинной тела ответа сделаем еще одно чтение из сокета


    $buf2 = '';
    $result = [];
    
    while ($dataLen) {
    socket_recv($socket, $buf2, 1, MSG_WAITALL);
    $result[] = $buf2;
    $dataLen--;
    }
    
    var_dump(implode('', $result)); //тут будет то что отдаст искомый скрипт
    
    socket_close($socket);

    Ура все сработало! Наконец то!
    Что мы имеем в ответе, если например в этом файле


    $url = '/path/to/script.php' //переменная окружения которую задали ранее

    мы пропишем


    <?php
    
    echo "My fcgi script";

    то в ответе получим в итоге


    image


    Итоги


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


    Полная версия скрипта
    <?php
    
    $url = '/path/to/script.php';
    $env = [
    'REQUEST_METHOD' => 'GET',
    'SCRIPT_FILENAME' => $url,
    ];
    
    $service_port = 9000;
    $address = '127.0.0.1';
    
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    $result = socket_connect($socket, $address, $service_port);
    
    //ОТКРЫТИЕ СОЕДИНЕНИЯ
    //запрос на начало сессии с php-fpm
    //параметры в пордке следования версия, тип записи (что эта запись будет делать), id запроса, длинна тела записи, длинна данных для выравнивания
    socket_write($socket, pack('CCnnCx', 1, 1, 1, 8, 0));
    
    //тело записи для открытия соединения
    //параметры роль, флаг управляющий закрытыием соединения
    socket_write($socket, pack('nCxxxxx', 1, 0));
    
    $keyValueFcgiString = '';
    
    foreach ($env as $key => $value) {
    //длинна разных по длинне значений кодируется по разному
    //если меньше 128 байт то одним байтом если больше то четырьмя
    $keyLen = strlen($key);
    $lenKeyChar = $keyLen < 128 ? chr($keyLen) : pack('N', $keyLen);
    
    $valLen = strlen($value);
    $valLenChar = $valLen < 128 ? chr($valLen) : pack('N', $valLen);
    
    $keyValueFcgiString .= $lenKeyChar . $valLenChar . $key . $value;
    }
    
    //следующая запись, тут уже будем передавать в php-fpm параметры в каком окружении и какой скрипт мы хотим запустить
    //из особенностей опишу параметры которые передаю
    //1-версия (по старому), 4-тип записи (новое, означает передачу пар имя-значение FCGI_PARAMS), id запроса (тот же), длинна тела записи (длинна моих пар ключ-значение), длинна данных для выравнивания
    socket_write($socket, pack('CCnnCx', 1, 4, 1, strlen($keyValueFcgiString), 0));
    
    //отправляем пары ключ значение на сервер
    socket_write($socket, $keyValueFcgiString);
    
    //финишируем запрос
    socket_write($socket, pack('CCnnCx', 1, 4, 1, 0, 0));
    
    $buf = '';
    $arrData = [];
    $len = 8;
    
    while ($len) {
    socket_recv($socket, $buf, 1, MSG_WAITALL); //получаем данные по 1 байту и пишем их в массив
    $arrData[] = $buf;
    $len--;
    }
    
    //интерпритируем заголовок в соответствии с форматом 'CCnnCx'
    $protocol = unpack('C', $arrData[0]);
    $type = unpack('C', $arrData[1]);
    $id = unpack('n', $arrData[2] . $arrData[3]);
    $dataLen = unpack('n', $arrData[4] . $arrData[5])[1]; //длинна данных в ответе, их нам надо будет получить после заголовка (unpack возвращает массив, по этому там индекс)
    $foo = unpack('C', $arrData[6]);
    
    $buf2 = '';
    $result = [];
    
    while ($dataLen) {
    socket_recv($socket, $buf2, 1, MSG_WAITALL);
    $result[] = $buf2;
    $dataLen--;
    }
    
    var_dump(implode('', $result)); //тут будет то что отдас искомый скрипт
    
    socket_close($socket);
    
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 17

      +1
      Любопытная статья.
      А почему было не использовать собственно nginx, а скрипты дергать по http?
        +1
        Из скрипта тогда нужно было бы отправлять запросы к nginx, а он в свою очередь возвращал бы их назад, получается лишнее звено. Но в итоге как я и написал в статье реализовали этот компонент совсем по другому через rabbit. В этой же статье реализация не главное, основное тут это разбор протокола
        +1
          0
          Спасибо, буду иметь это ввиду. Тем более на Golang, стоит изучить
            0

            Кажется там изучать особо ничего и не надо, ибо с Go (с языком имею ввиду) общаться почти не придётся. Ну и как бы воркеры\очереди\форки (как угодно можно называть) там из коробки идут.

          +4

          Какой-то странный подход.


          Если нужно было прослушать обмен, то wireshark вам в руки. Возможно, он даже сам протокол декодировать сможет.


          Ну и отдельно совершенно непонятно, почему php_fpm использовать можно, а поставить перед ним nginx, который будет принимать запросы и отдавать в php_fpm — нет.
          Экономия памяти? Да там копейки…
          Лишняя точка отказа? Тоже не факт, nginx наоборот может балансировать трафик на несколько демонов php_fpm и отрабатывать ошибки.

            0
            Совершенно не претендую на то что данный подход хорош, и не сомневаюсь что можно было реализовать все это намного лучше (собственно сама первоначальная задача и была реализована по другому).
            В этой статье основное что хотелось рассказать — это как работает протокол, и как на php можно с ним работать. Конечно врядли php лучший выбор для этого в реальном коде, но для разбора теории вполне норм.
            +1
            Из необычного применения такой интеграции php -> (fast-cgi) php-fpm -> php можно придумать следующую схему. Простой брокер сообщений в виде php демона и php-fpm как балансировщик воркеров. В такой схеме воркеры получат преимущества короткоживущего процесса без хранения состояния.
              0
              Да что то такое и планировали изначально, но к сожалению из-за нехватки времени быстро не получилось реализовать, воспользовались готовым решением.
              –1
              Supervisor
                0
                Непонятно как это относится к статье, там не про шаблоны, там про реализацию и принципиальную схему работы протокола FastCGI через PHP
                +1

                А чем cgi-fcgi не подошла? Она вроде бы придумана специально для решения вашей проблемы.
                https://www.opennet.ru/man.shtml?topic=cgi-fcgi&category=1&russian=2

                  0

                  Удобная штука, мы используем в частности для очистки opcache при деплое без перезапуска php-fpm.


                  Примерно так, может кому пригодится:


                  #!/bin/bash
                  
                  WEBDIR="/var/local/www"
                  RANDOM_NAME=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13).php
                  
                  sudo -u www-data mkdir -p ${WEBDIR}/opcache
                  sudo -u www-data touch ${WEBDIR}/opcache/${RANDOM_NAME}
                  
                  echo "<?php echo \"\\t\"; if (function_exists('opcache_reset')) echo (opcache_reset() ? 'OK' : 'FAIL'); else echo 'FAIL'; ?>" > ${WEBDIR}/opcache/${RANDOM_NAME}
                  
                  printf "\nTCP/IP\n\t"
                  sudo -u www-data bash -c "SCRIPT_NAME=/${RANDOM_NAME} SCRIPT_FILENAME=${WEBDIR}/opcache/${RANDOM_NAME} REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000"
                  
                  printf "\nSocket\n\t"
                  sudo -u www-data bash -c "SCRIPT_NAME=/${RANDOM_NAME} SCRIPT_FILENAME=${WEBDIR}/opcache/${RANDOM_NAME} REQUEST_METHOD=GET cgi-fcgi -bind -connect /var/run/php/php7-fpm.sock"
                  
                  rm -R ${WEBDIR}/opcache
                  
                  printf "\n\n"
                  –2

                  Может быть я не прав, но почему бы не использовать AWS Lambda или Yandex Cloud Functions?

                    0

                    Как "разбор протокола" не плохо. Но это адский ад. Это вообще лишнее, для "дергания" скриптов.

                      0
                      Да именно как разбор, просто даже зная теорию, пока сам не реализуешь это на практике хорошего понимания не получается (ну у меня по крайней мере)
                      0

                      спасибо, очень интересно.
                      отмечу только, что готовых fastcgi клиентов вполне достаточно https://packagist.org/?query=fastcgi

                      Only users with full accounts can post comments. Log in, please.