Нет, нет и еще раз нет! Изобретение "велосипедов" не преследуется по закону, но и не особо приветствуется. Просто иногда хочется понять механизм работы некоторых элементов, к которым давно привык, и не обращаешь на них внимание. Для обработки данных, получаемых из формы, существует много модулей: CGI, CGI::Simple, CGI::Lite, CGI::WebIn, это из тех, которые знаю я. Наверняка их еще больше. А что я вижу в скриптах "неизвестного производства"?
$buffer = $ENV{QUERY_STRING};
if ($ENV{REQUEST_METHOD} eq POST) { read(STDIN, $buffer, $ENV{CONTENT_LENGTH}) }
После чего, начинающие специалисты копируют этот код в свои скрипты и начинают "флудить" на форумах (каюсь: сам таким был и так делал). Но это не самое интересное, проблемы начинаются после того, как потребуется "фильтровать" данные, но не все и не так; потом, иногда форма отправляет несколько значений для одного параметра, а мы получаем только одно; про upload вообще помолчу. В итоге, этот код начинает "обрастать" дополнительными "фишками". А требований все больше и больше: ...выносим этот код в отдельную внешнюю процедуру, так как при доработках постоянно при ходится править кучу скриптов... ...старые доработки и фильтры удалять нельзя, из используют некоторые скрипты, приходится делать дополнительные... ...upload - черт с ним, цепляем модуль CGI, но не везде... ...некоторые параметры надо получить в виде массива, заносим эти параметры отдельно... и так далее... в общем полный улет... А когда это все надоедает, начинаем писать use CGI в скриптах, и не морочим себе голову.
Но, с использованием, CGI и альтернативных модулей, начинаешь "лениться" и про механизм обработки полученных данных - забываешь. В данной статье мы рассмотрим принцип обработки данных и в процессе напишем небольшой модуль, "без претензий" на первенство.
1. Какие данные мы получаем
В основном (если не всегда), данные передаются только двумя методами:
GET;
POST;
Но во время отправки данных методом POST мы можем передать дополнительные данные в URI.
А так же, данные предаются практически всегда (если не всегда) двумя типами данных:
text/plain (text/html);
multipart/form-data (только для метода POST);
Формат передаваемых данных можно посмотреть здесь. Но особо не вдаваясь в подробности можно сказать так:
если тип данных text/... данные передаются в виде:
param1=value1¶m2=value2
Где "&" - разделитель параметров, а "=" - разделитель между параметром и значением. При этом можно не волноваться по поводу того, что в имени или значении параметра могут быть эти символы, так как браузер автоматически конвертирует эти символы (и некоторые другие) в шестнадцатеричный формат.
При этом мы видим, что предварительного ковертирования символов - нет, то есть данные передаются "как есть". Отправной точкой для нас является только уникальный разделитель, в нашем случае - "-----------------------------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-datai) { $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), и сложного в ней ничего нет: