FastCGI-приложение на Perl. Часть первая.

    Некоторое время назад мне поручили написать движок для онлайн-аукциона. Передо мной встал выбор — сделать движок как обычный CGI или же сделать что-то поинтереснее. Я принял решение применить в разработке движка технологию FastCGI.

    FastCGI — это клиент-серверный протокол, обеспечивающий взаимодействие между веб-сервером и приложением. FastCGI является дальнейшим развитием протокола CGI.

    В чем разница?

    В случае обычного CGI-приложения взаимодействие между веб-сервером и приложением осуществляется через STDIN и STDOUT. В случае же, когда применяется FastCGI, взаимодействие между клиентом и сервером осуществляется через Unix Sockets или через TCP/IP.

    Более интересным из этих двух вариантов является взаимодействие через TCP/IP. Этот вариант позволяет получить два ключевых преимущества:

    1) FastCGI-приложения могут быть запущены не только на том же сервере, на котором работает веб-сервер, но и на любом другом. Таких серверов и запущенных на них FastCGI-приложений может быть сколько угодно, а это, в свою очередь, дает практически неограниченный простор для масштабирования системы. Система перестала справляться с нагрузками? Ничего страшного — можно очень просто, совершенно ничего не перестраивая, добавить нужное количество серверов. Максимальное вмешательство в существующую систему будет заключаться, всего лишь, в добавлении строчки со списком новых IP в конфиг веб-сервера.

    2) FastCGI-приложение может быть реализовано в виде демона, т.е. оно само может являться сервером. Обычное CGI-приложение запускается веб-сервером при каждом новом запросе. На запуск приложения уходит львиная часть времени, зачастую запуск занимает больше времени, чем выполняемая им далее полезная работа. А FastCGI-приложение запущено всегда, ему нет нужды тратить время на запуск, ему достаточно только выполнять полезную работу.

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

    Начнем с веб-сервера.

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

    Для настройки nginx'а на работу с нашим приложением в конфиг nginx'а нужно добавить следующую конструкцию:

    # Любые запросы, в которых путь будет начинаться с директории fcgi-bin,
    # будут передаваться на обработку FastCGI-приложению.
    location /fcgi-bin/ {
        # Куда передавать запросы
        # в данном случае FastCGI-приложение находится на этом же компе и слушает порт 9000
        fastcgi_pass   localhost:9000;
    }
    

    (за подробностями настройки nginx'а обращаться к первоисточнику)

    Запускаем nginx. Допустим, в nginx'е был настроен виртуальный хост test.host, тогда запрос в браузере к адресу test.host/fcgi-bin будет передан nginx'ом нашему FastCGI-приложению. Пока самого приложения еще нет, в браузер будет выдано что-то типа такого:

    «The page you are looking for is temporarily unavailable. Please try again later. „

    Это нормально. Так и должно быть.

    Теперь перейдем непосредственно к написанию FastCGI-приложения.

    Протокол FastCGI реализован в Perl в виде модуля FCGI. Обратите внимание — на CPAN'е в наличии есть и другие модули, позволяющие работать с FastCGI, например, CGI::Fast, но все они являются просто надстройками над FCGI. Мы будем работать напрямую с “фундаментальным» модулем FCGI.

    #!/usr/bin/perl
    
    # Для пущего порядку
    use strict;
    use warnings;
    
    # Этот модуль реализует протокол FastCGI.
    use FCGI;
    
    # Открываем сокет
    # наш скрипт будет слушать порт 9000
    # длина очереди соединений (backlog)- 5 штук
    my $socket = FCGI::OpenSocket(":9000", 5);
    
    # Начинаем слушать
    my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
    
    my $count = 1;
    
    # Бесконечный цикл
    # при каждом принятом запросе выполняется один "оборот" цикла.
    while($request->Accept() >= 0) {
        # Внутри цикла происходит выполнение всей полезной работы
        print "Content-Type: text/plain\r\n\r\n";
        print $count++;
    };
    

    Разберем некоторые моменты подробнее.

    Функция OpenSocket открывает сокет и биндит на него порт 9000. Не забывайте — именно этот порт мы раньше указали в конфиге nginx'а. Еще не забывайте — порты ниже 1024 нельзя открыть, не будучи пользователем root.

    Параметр backlog этой функции задает количество соединений, которые будут ждать в очереди, пока обслуживаются предыдущие соединения. Честно говоря, я не понял, как работает этот параметр. Теоретически, если очередь заполнена, новые соединения должны сбрасываться. Но у меня изменение этого параметра видимого эффекта не имеет, какое число не указывай — все соединения все-равно выстраиваются в очередь, ни одно не сбрасывается. Вероятно, я где-то что-то упускаю, но на работоспособность скрипта это, похоже, никак не влияет.

    Функция Request создает обработчик запросов. Обработчик «перехватывает» стандартные дескрипторы и связывает их с веб-сервером. Это означает, в частности, что если при запросе в скрипте произойдет какая-то ошибка, то сообщение о ней будет выведено не на консоль, а отдано вебсерверу. Искать это сообщение нужно в логе nginx'а. Ну, в общем-то, у обычных CGI-скриптов поведение в этом плане аналогично, с той только разницей, что логи CGI-приложения находятся рядом, а логи FastCGI-приложения могут находиться совсем на другом сервере.

    Не пренебрегайте выводом заголовка «Content-Type» (ну, или хоть какого-нибудь заголовка). Без этого nginx откажется что-либо выводить в браузер. В общем, такое поведение также аналогично поведению веб-сервера при работе с обычным CGI-приложением.

    Хорошо, написали скрипт, запускаем его. Если вы нигде не допустили синтаксической ошибки, то скрипт запустится молча, ничего не выведет и не отпустит консоль — это нормально, так и должно быть. Сейчас скрипт должен слушать порт 9000 и ждать, пока к нему не обратится веб-сервер.

    Теперь запускаем браузер и открываем адрес test.host/fcgi-bin — помните, вы его уже открывали, когда проверяли настройку nginx'а? Но на этот раз вместо сообщения о недоступности страницы должна появиться незатейливая цифра 1. Понажимайте кнопку F5. Цифра должна увеличиваться.

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

    В следующей части — демонизация приложения, превращаем скрипт в сервер.

    (оригинал статьи)
    Поделиться публикацией

    Комментарии 18

      +1
      После цикла еще хорошо бы вызвать
      FCGI::CloseSocket($socket);
      И нужно сделать обработку более одного запроса одновременно с помощью того же FCGI::ProcManager, или это и будет в следующей части…
        0
        Закрытие сокета не имеет смысла, цикл бесконечный, до закрытия сокета процесс никогда не доберется.

        Обработка нескольких одновременных запросов будет дальше, да.
          0
          Цикл не бесконечный, $request->Accept() может вернуть значение меньше 0 в случае ошибки.
          Конечно это не особо принципиальный вопрос, все и так будет прекрасно работать, но если вы приводите этот код как пример для использования другими людьми, то мне кажется все таки логичнее поставить CloseSocket.
            0
            А, ну для учета такой ситуации да, имеет смысл поставить CloseSocket.
        0
        а нельзя ли холивара радиради интереса провести бенчмарк производительности?
          0
          Ммм… Да, надо будет подумать над этим.
            0
            можно даже сравнить перфоманс с каким-нибудь самописным сервером типа
          0
          А что с чем сравнивать?
          Если с CGI то по личным ощущениям (на тестовой машине) с FastCGI в единицу времени обрабатывается в 10-12 раз больше запросов чем c CGI.
            0
            с тем же CGI, например, да.

            мне вообще очень интересно узнать, на что способны самые разные языки/фрейморки/аппсерверы в простейших приложениях, но у меня, увы, нету времени и знания большого кол-ва языков, чтобы провести все бенчмарки. Может кто-то что-то готовое встречал?
              0
              С mod_perl было бы интересней сравнить
                0
                Скорее всего в плане производительности — без разницы.
                Преимущество mod_perl перед FastCGI в том, что первый может вклиниваться в обработку запросов на разных стадиях. Зато второй может располагаться на других серверах.
                0
                Сие есть «сферический конь в вакууме». Подобные бенчмарки есть в сети (ссылок не помню), но смысла в них — ноль. Потому что для более или менее реального проекта результаты будут совсем другие.
            0
            Есть мнение (личное) что самый простой переход с CGI на FastCGI делается через mod_fastcgi в апаче и CGI::Fast в Перле. А потом уже можно и на слушание сокетов переходить :)
              0
              Не пробовал, возможно. Я с нуля делал, так что у меня вопросов с переходом не возникало.
              0
              А почему бы не встроить вебсервер в свое приложение? Тогда основной сервер и приложение будут обмениваться HTTP, без всяких лишних FastCGI.
                0
                Так можно сделать только в простейшем случае, когда используется одна-единственная копия приложения.

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

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

                Самое читаемое