Web - И снова отказываемся от модуля CGI? - PRCY⮭net
Введение

Нет, нет и еще раз нет! Изобретение "велосипедов" не преследуется по закону, но и не особо приветствуется. Просто иногда хочется понять механизм работы некоторых элементов, к которым давно привык, и не обращаешь на них внимание. Для обработки данных, получаемых из формы, существует много модулей: CGI, CGI::Simple, CGI::Lite, CGI::WebIn, это из тех, которые знаю я. Наверняка их еще больше. А что я вижу в скриптах "неизвестного производства"?

$buffer = $ENV{'QUERY_STRING'};

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'})
}

@pairs = split(/&/, $buffer);

foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ s/<!--(.|\n)*-->/<br>/g;
$value =~ s/</&lt;/g;
$value =~ s/>/&gt;/g;
$value =~ s/\cM/<br>/g;
$value =~ s/\n/ /g;
$value =~ s/\|/ /g;
$value =~ s/\|/ /g;
$value =~ s/<([^>]|\n)*>/<br>/g;
$FORM{$name} = $value;
}

После чего, начинающие специалисты копируют этот код в свои скрипты и начинают "флудить" на форумах (каюсь: сам таким был и так делал). Но это не самое интересное, проблемы начинаются после того, как потребуется "фильтровать" данные, но не все и не так; потом, иногда форма отправляет несколько значений для одного параметра, а мы получаем только одно; про upload вообще помолчу. В итоге, этот код начинает "обрастать" дополнительными "фишками". А требований все больше и больше:
...выносим этот код в отдельную внешнюю процедуру, так как при доработках постоянно при ходится править кучу скриптов...
...старые доработки и фильтры удалять нельзя, из используют некоторые скрипты, приходится делать дополнительные...
...upload - черт с ним, цепляем модуль CGI, но не везде...
...некоторые параметры надо получить в виде массива, заносим эти параметры отдельно...
и так далее... в общем полный улет... А когда это все надоедает, начинаем писать use CGI в скриптах, и не морочим себе голову.

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

1. Какие данные мы получаем

В основном (если не всегда), данные передаются только двумя методами:

* GET;
* POST;


Но во время отправки данных методом POST мы можем передать дополнительные данные в URI.

А так же, данные предаются практически всегда (если не всегда) двумя типами данных:

* text/plain (text/html);
* multipart/form-data (только для метода POST);


Формат передаваемых данных можно посмотреть здесь. Но особо не вдаваясь в подробности можно сказать так:

если тип данных text/... данные передаются в виде:

param1=value1&param2=value2

Где "&" - разделитель параметров, а "=" - разделитель между параметром и значением. При этом можно не волноваться по поводу того, что в имени или значении параметра могут быть эти символы, так как браузер автоматически конвертирует эти символы (и некоторые другие) в шестнадцатеричный формат.

если тип данных multipart/form-data:

-----------------------------7d513a1b160308
Content-Disposition: form-data; name="param1"

value1
-----------------------------7d513a1b160308
Content-Disposition: form-data; name="file"; filename="D:\param2.txt"
Content-Type: text/plain

blablablablablablablablabla
blablablablablablablabla
blablablablablablabla
blablablablablabla
blablablablabla
blablablabla;
blablabla?
blabla;

bla
blabla
bla
blablabla

blablablablablabla
-----------------------------7d513a1b160308--

При этом мы видим, что предварительного ковертирования символов - нет, то есть данные передаются "как есть". Отправной точкой для нас является только уникальный разделитель, в нашем случае - "-----------------------------7d513a1b160308" (естественно, что он каждый раз новый).

Какие данные передает нам Cookies:

Данные передаются в переменной окружения $ENV{'HTTP_COOKIE'} ($ENV{'COOKIE'}), формат:

param1=value1; param2=value2; param3 = value3;

В общем, ничего сложного, итак:

2. Начало модуля и объявление объекта:

Ничего нового, все как по учебнику:

package My::CGI;
# Без него никак нельзя :)
use strict;
# С помощью этого модуля, будем определять FILEHANDLE
# Модуль выбран первый попавшийся, если кому нравится другой - пожалуйста
use IO::File;
# Версия, что бы потом не запутаться
our $VERSION = '1.0.0';

# Процедура объявления объекта
sub new {
# %common - дополнительные сведения, в нашем случае, максимальный объем принимаемых данных
my ($self, %common) = @_;
$self = {
max_upload => 262144, # Default 256 Kb
# Здесь будем хранить имена и значения параметров
data => {},
# Здесь будем хранить куки
cookies => {},
# Здесь будем хранить ссылки на временные файлы которые загрузили из формы
tmp => {},
};
# Определяем максимальный объем передаваемых данных, если надо
$self->{'max_upload'} = $common{'MAX_UPLOAD'} if $common{'MAX_UPLOAD'};
# Запускаем процедуру разбора полученных данных
$self = &_parse_common_data($self);
# "Благославляем" наш объект
bless $self;
# ... и возвращаем
return $self;
}

Сама собой выплыла следующая процедура (_parse_common_data) - разбор полученных данных.

3. Разбор полученных данных

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

* данные переданные методом GET или в URI (QUERY_STRING);
* данные переданные методом POST (CONTENT_LENGTH + STDIN) при этом определить какого они типа;
* данные Cookies (HTTP_COOKIE);


Код:

sub _parse_common_data {

my $self = shift;

# Проверяем наличие QUERY_STRING, при этом не имеет значение метод передачи
# данных, так при методе GET у нас в этой переменной передаются значения формы,
# при методе POST, дополнительные даные в URI, а может просто быть запрос с
# какими-либо параметрами
if ($ENV{'QUERY_STRING'}) {
# и если у нас есть значение, то обрабатываем данные, при этом отдельно указывая
# метод, так для метода POST - 'POST', остальные - GET;
$self = &_parse_QUERY_STRING($self, 'GET')
}

# Проверяем метод передачи данных, для обработки POST
if (uc($ENV{'REQUEST_METHOD'}) eq 'POST') {
# Если тип данных multipart, то передаем обработку в соответсвующую процедуру
if (exists($ENV{'CONTENT_TYPE'}) && $ENV{'CONTENT_TYPE'} =~m |^\s*multipart/form-data|i) {
$self = &_parse_MultiPart($self)
# иначе стандартная обработка, с указанием, что обрабатываются данные метода POST
} else {
$self = &_parse_QUERY_STRING($self, 'POST')
}
}

# Проверяем наличие переданных Cookies
if ($ENV{'HTTP_COOKIE'} || $ENV{'COOKIE'}) {
# Если есть, то обрабатываем
$self = &_parse_COOKIES($self)
}
# Возвращаем заполненый данными массив
return $self
}

Процедура небольшая, и несложная, пора переходить к самому интересному:

4. Разбор данных типа text

Что нам нужно, собственно алгоритм:

* разобрать по отдельности все параметры;
* разобрать имя параметра и его значение;
* обработать эти данные (так как некоторые символы при отправке конвертируются в шестнадцатеричный код);
* положить результат в наш хеш (который в последствии станет объектом), но мы должны учесть, что параметр может быть один, а значений несколько;


Код:

sub _parse_QUERY_STRING {
my ($self, $type) = @_;
my $data;
# Выбираем данные в соответсвии с методом передачи данных
if ($type && $type eq 'POST') {
read(STDIN, $data, $ENV{'CONTENT_LENGTH'})
} else {
$data = $ENV{'QUERY_STRING'}
}
# Разделяем отдельно параметры. В общем, по сути, достаточно было бы и одного
# символа "&", в качестве разделителя, но иногда проявляется символ "?", а в
# модуле CGI еще используется символ ";", но впрочем, хуже не будет если мы укажем
# все символы, тем более как сказано выше, боятся того, что в имени параметра или
# его значении может проскочить этот символ - не стоит, так что:
my @pairs = split(/[\?\&\;]/,$data);
foreach (@pairs) {
# Отделяем имя параметра от его значения, цифра 2 говорит о том, что переменная $_
# разбивается только на 2 части, хотя это лишнее, но тоже не помешает
my ($param, $value) = split('=', $_, 2);
# Если какого-либо значения нет, то данный параметр пропускаем
next unless $param && $value;
# Декодируем полученные значения из шестнадцатеричного формата отдельной
# Хотя, отдельно процедуру выносить не обязательно, только для удобства
$param = &URLDecode($param);
$value = &URLDecode($value);
# Внедряем в наш хеш полученные данные, так как данная функция пригодится нам и
# при обработке данных типа multipart, то выносим её отдельно
$self = &_include_data($self, $param, $value);
}

return $self
}

Быстро обрисуем процедуру URLDecode, она взята как есть у Дмитрия Котерова (CGI::WebIn), и сложного в ней ничего нет:

sub URLDecode {my $s = shift; $s =~tr /+/ /; $s =~s /%([0-9A-Fa-f]{2})/chr(hex($1))/esg; return $s}
Information
  • Posted on 27.04.2013 15:27
  • Просмотры: 1039