Создание системы голосования на Perl/CGI (исходники)Источник: IBM developerWorks Россия Аллан Педа
В данной статье представлен реальный пример (Web-приложение для голосования), в котором минимальное число внешних модулей, не применяются cookies на стороне клиента и используются преимущества CGI-атрибутов. Не секрет, что программное обеспечение становится все более сложным и в системы добавляются все новые уровни в целях сохранения модульности компонентов. Главный результат - простота обслуживания и масштабируемость систем. Но иногда использование таких методик - просто излишество, которое приводит к излишне усложненному программному обеспечению. В других случаях разработчики выбирают излишне сложную, но хорошо знакомую технологию вместо изучения более простой, но менее знакомой альтернативы. В конце концов, если под рукой имеется только молоток, то каждая проблема кажется гвоздем. Меня недавно попросили разработать маленькую программу подсчета избирательных бюллетеней для университетской студенческой организации. Это был простой проект, поскольку обслуживалось бы не более 500 студентов курса за одну неделю; потом результаты подсчитывались бы и удалялись. Поскольку этот проект требовал почти тривиального уровня обслуживания, не имело никакого смысла передавать запросы во внешнюю базу данных. Вместо этого сценарий мог бы быстро читать и писать структуры данных напрямую. Тем не менее, я хотел скомпоновать проект так, чтобы он не представлял собой несколько страниц запутанного кода. Мне нужен был продуманный самодостаточный дизайн, который предполагал бы также упрощенное развертывание. CGI: Простота против сложности Perl казался очевидным выбором для данного проекта - он поддерживается на большинстве обычных платформ, а также существует множество удобных библиотек, доступных в репозитории Perl-библиотек CPAN. Что касается лежащей в основе архитектуры, Common Gateway Interface (CGI) был первым широко используемым подходом для расширения возможностей Web-серверов в области предоставления интерактивного содержимого. Разработчики часто говорят о превосходстве более новых стандартов, таких как JSP, .NET, mod_perl, PHP и ISAPI, и они правы, указывая на недостатки CGI. Но в случае с данным проектом CGI-сценарий, который считает голоса для нескольких сотен пользователей, едва ли образует крупномасштабное приложение, поскольку вся информация о бюллетенях может легко сохраняться в системной RAM Web-сервера. Это позволяет загружать в память всю справочную таблицу каждый раз, когда пользователь передает запрос на чтение или запись данных. Более того, логическая последовательность составления бюллетеня, его подтверждения и подсчета результатов подходит для разбиения логических данных на три различных физических файла. Это минимизировало бы попытки открыть заблокированные файлы. Также не должен был быть серьезной проблемой и откат транзакции, происходящий иногда из-за заблокированного файла. Независимо от того, произошел откат транзакции из-за проблем сети или из-за заблокированного файла, результат был бы одинаков: пользователь просто щелкнул бы мышкой второй (или третий) раз, и его голос, вероятнее всего, засчитался бы в одной из этих попыток. Однако о таком поведении нужно помнить, поскольку различные приложения могут не простить недоступность для процесса параллельных транзакций. Для данного проекта использование CGI предлагает несколько преимуществ:
Однако помните, что из-за ограничений платформы, CGI-приложения (создающие новые процессы) на Win32-системах работают намного медленнее. Кроме того, Web-сервер Apache все же считается Linux™/UNIX®-приложением, хотя может отлично работать под Windows®. В разделе "Ресурсы" приводятся ссылки на информацию о других (не IIS) Web-серверах для систем Win32, а также на классическое описание исходной спецификации CGI на сайте National Center for Supercomputing Applications (NCSA). Давайте перейдем к главной задаче этого маленького проекта - функциональности дизайна. Идея вот в чем. Пользователю предоставляется начальный экран, предлагающий ввести информацию об адресе электронной почты и выбрать одного из нескольких кандидатов в Web-форме. Подтвержденная информация записывается локально как черновик бюллетеня, затем по указанному адресу электронной почты передается письмо для верификации. В данном случае я предполагаю, что проверенного адреса электронной почты достаточно для установления идентичности пользователя. Возникает проблема многократного голосования. В принципе, можно подумать о том, как сделать невозможным множественное голосование с использованием нескольких адресов e-mail, но можно, также, ограничить подсчет голосов таким образом, чтобы только один голос был разрешен для одного адреса e-mail. Это верификационное письмо содержит ссылку на исходный CGI-сценарий, который позволяет сравнить эту ссылку с записанной в локальном DBM-файле. Если две ссылки совпадают, делается запись о бюллетене в таблице castBallot и голос учитывается. Если ссылки не совпадают, никакой записи не делается и голос считается не подтвержденным. Генерируется новое верификационное письмо с новой записью в базе данных. При этом перезаписываются все записи о черновых бюллетенях, связанные с данным почтовым адресом, эффективно начиная процесс с самого начала. Если ссылки совпадают, избиратель может подтвердить черновик бюллетеня. В данный момент, если пользователь передумает, он может просто вернуться на Web-форму и создать новый черновик бюллетеня, который заменяет предыдущий. Такой дизайн представляет благоразумно защищенную систему; до тех пор, пока каждый голосующий пользователь имеет один и только один адрес электронной почты, существует обоснованная уверенность в том, что пользователь не проголосует дважды (я вернусь к этому вопросу позже). Давайте рассмотрим систему подробно. Использование хешированных ключей для создания ассоциативных массивов в Perl позволяет оперативно разрабатывать комплексные структуры данных. Комбинируя эту возможность с возможностью сохранять эти (произвольно комбинированные) структуры в бинарный DBM-файл, можно разработать эквивалент крошечной системы управления базами данных. Отсутствующий компонент, позволяющий все это сделать, предоставляется модулями Модуль MLDBM позволяет плавно сохранить в локальный файл комплексные Perl хеш-значения. Модуль По существу, логика последовательности действий проста, что показано в листинге 1. Листинг 1. Псевдокод логической последовательности
После разработки условного алгоритма оставалось создать объекты, которые соответствовали бы этой последовательности действий. Как я уже упоминал, необходимые структуры hash-данных извлекались и обновлялись при помощи связанных переменных и блокировки Другими словами, список бюллетеней использовался для создания Хотя я понимаю, что обычно считается плохой практикой пользоваться конструкторами, которые основываются на внешних структурах, таких как файлы (поскольку они могут повредиться и привести к непредсказуемым результатам), код в данном случае было намного проще понять, сделав именно так. Поскольку Perl не полагается на указатели, я не вижу причин не использовать преимущества такого упрощения. Разрешение пользователям посылать e-mail с вашего Web-сервера является рискованным ходом, поскольку спаммеры потенциально могут использовать ваш хост для передачи незатребованных писем. Для минимизации такой возможности сценарий всегда выполняет проверку для определения того, передается ли письмо по допустимому адресу. Вы можете усилить защищенность системы, изменяя метод проверки Другие методы предотвращения дублирования голосов могли бы собирать IP-адреса или устанавливать cookies на клиентской машине, но я отбросил эти подходы, поскольку многие студенты используют общедоступные терминалы в кампусе. Подробности: Несекретные бюллетени Вызов метода При отключенном сервере можно раскомментировать этот раздел и перезапустить Web-сервер, временно установив его на прослушивание только адреса localhost. Затем полные результаты можно возвратить любому, кто переходит по предварительно переданной ссылке, которую можно взять через копию, переданную на выделенную для этого свободную учетную запись e-mail. Обратите внимание на то, что в данном примере бюллетени не считаются дважды. Эти результаты скрываются при помощи короткой JavaScript-функции, если было решено ненавязчивым способом сделать их доступными каждому. Общеизвестно, что некоторые люди предпочли бы полностью анонимное голосование, но поскольку клубные выборы часто выполняются поднятием рук, вряд ли уместно использовать секретные бюллетени. Обдумывая эту схему работы, я понял, что необходимость использования основанной на Этот идентификатор был основан на идентификаторе процесса операционной системы (process identifier - PID), выполняющегося сценария. Он объединялся со случайным числом, для того чтобы затруднить предсказание URL для подтверждения черновика бюллетеня. Я беспокоился об этом, потому что индивидуум со злыми намерениями мог бы разобрать на части видимые URL-шаблоны, чтобы создать фальшивые бюллетени для подтверждения. Это одна из частей кода, которая не транслировалась напрямую в версию Оглядываясь назад, я понимаю, что лучшим способом закодировать данную ссылку было бы использование генерируемого по MD5 хеш-значения, что эффективно скрыло бы всю информацию о голосующем. Это имело бы двойную выгоду: сложность для обмана и сохранение переносимости в основанные на Требуется три типа каталогов на Web-сервере:
Также обратите внимание на то, что права доступа вероятнее всего должны быть скорректированы и Web-сервер мог выполнять запись в каталог для DBM-файлов. В листинге 2 приведен процесс создания некоторых типичных каталогов на Web-сервере. Листинг 2. Настройка каталогов на Web-сервере
Собственно говоря, абсолютно необходимы только каталоги cgi-bin (/var/www/cgi-bin) и DBM (/var/www/db), поскольку они хранят исполняемые сценарии и данные о голосовании соответственно. Схема, приведенная в листинге 1, специфична для Linux, а имена пользователя и группы процесса Web-сервера могут быть другими. Существенным является то, что есть несколько компонентов, которые нужно поместить в корректную область файловой системы, чтобы они были доступны для Web-сервера. После копирования файлов поддержки в их каталоги обновите все псевдонимы в конфигурационных файлах Web-сервера (например, httpd.conf). После создания каталогов по примеру листинга 2 скопируйте файлы из ZIP-архива в аналогичные подкаталоги вашей системы. Самое важное: файлы ballot, DraftBallot.pm, BallotBox.pm и CastBallot.pm должны находиться в каталоге cgi-bin. Необходимы только три нестандартных Perl-модуля. Процесс их установки представлен в листинге 3 (более подробно - в файлах README модуля). Листинг 3. Установка Perl-модулей
Подробности: Статический DNS против динамического DNS Хотя имелась возможность установить этот сервис на сайте с назначенным доменом и статическим IP-адресом, я чувствовал, что динамический DNS предложил бы определенные преимущества в плане защищенности. Обычно сервер не доступен из Web вовсе без статического IP-адреса, но динамический DNS-сервис позволяет мне установить временно разрешимое имя машины под другим доменом верхнего уровня. Это позволяет мне быстро появляться и исчезать из Интернета, минимизируя возможность встречи со злодеями. А лучше всего то, что данный сервис бесплатен. Также стоит отметить, что может быть желательным настроить сервер на прослушивание порта с нестандартным номером (например, 8000), поскольку многие ISP блокируют входящие запросы на порту 80. Потом клиента (избирателя) можно было бы направить на сервер для голосования только через ссылку с хорошо известного статического адреса (например, Web-страницы школы). После завершения голосования сервер с Web-приложением мог бы быть полностью удален из Web удаленно без его остановки или перенастройки. Отсутствовали бы какие-либо уязвимости, влияющие на ссылающуюся страницу,которая может администрироваться кем-то другим (ссылки на дополнительную информацию по использованию динамического DNS-сервиса приведены в разделе "Ресурсы"). Браузеры могут сохранять состояние, передавая данные на страницы, используя методы При использовании данного сценария можно и нужно учесть еще несколько соображений по защите приложения. Любая программа, позволяющая вводить данные извне, уязвима для злонамеренных действий (например, переполнение буфера и встроенные управляющие символы). Наоборот, использование отдельной процедуры для чтения и записи локальных DBM-файлов имеет как минимум одно преимущество: нет возможности для атак типа SQL-внедрение, когда нет SQL-базы. Для фильтрации входящих данных я установил переменные
Наличие возможности установить эту систему и попытки сохранить ее простой и самодостаточной позволили мне обнаружить несколько очень полезных Perl-модулей. Процесс совершенствования возможностей и разработка функциональных спецификаций для такого простого проекта оказались интересными и приятными. Я надеюсь, что эти соображения помогут вам при создании подобного рода проектов. |