CGI на Lazarus. Минимальное приложение. POST & GET запросы
Windows / Lazarus 0.9.28.2 beta / fpc 2.2.4
Механизм взаимодействия cgi
1. При получении запроса обозревателя к cgi сервер запускает приложение, передает ему данные из командной строки запроса
2. cgi формирует ответ и помещает его в выходной поток
3. сервер посылает ответ, используя http протокол обратно обозревателю
недостатки cgi - невысокая скорость обработки, повышенная загрузка web сервера, т.к. в случае паралелльной обработки сервер запускает отдельный процесс, для каждого процесса - копию модуля расширения памяти компьютера
для передачи/получения параметров используются стандартный входной/выходной потоки (stdin/stdout)
для передачи параметров и получения системной информации могут использоваться переменные окружения
Само CGI приложение - это ни что иное, как обычное консольное приложение (Win32 CONSOLE). Вот только устройством ввода для него служит строка браузера (URL), в котором содержаться get запросы или форма на html странице с кнопочкой submit, которая отправляет post запросы.
Запустить cgi приложение можно просто набрав в строке браузера адрес, который приведет нас к cgi приложению
http://127.0.0.1:8081/cgi/cgifirst.exe
Можно компилировать cgi приложения с расширением cgi, а можно с расширением exe, это не принципиально.
Настройки Apache для запуска cgi программ: (http://httpd.apache.org/docs/2.2/howto/c gi.html)
Файл конфигурации сервера httpd.conf
При старте должны загружаться модули: * mod_alias * mod_cgi
По умолчанию путь к cgi модулям находится в директории [Путь куда установлен Apache]/cgi-bin
Директива ScriptAlias позволяет задать псевдоним для директории со скриптами
ScriptAlias /cgi/ "C:/Apache/test.pfr/www/cgi/"
Опции
specify that CGI execution was permitted in a particular directory:
<Directory /usr/local/apache2/htdocs/somedir>
Options +ExecCGI
</Directory>
Директива AddHandler сообщает серверу, что файлы с таким расширением (.cgi .pl) будут опознаны как CGI программы
AddHandler cgi-script .cgi .pl
Пример. Определяем путь к приложениям для виртуального хоста.
Настройка виртуального хоста у нас находится в файле httpd-vhosts.conf.
В этом файле мы определили виртуальный хост, который будет слушать 8081 порт,
директория с документами будет располагаться в "C:/Apache/test.pfr/www",
а cgi приложения будут располагаться здесь "C:/Apache/test.pfr/www/cgi/"
(для linux'ов )
NameVirtualHost *:8081
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for all requests that do not
# match a ServerName or ServerAlias in any <VirtualHost> block.
#
<VirtualHost *:8081>
ServerAdmin webmaster@dummy-host2.localhost
DocumentRoot "C:/Apache/test.pfr/www"
ServerName test.pfr
ErrorLog "c:/apache/test_error.log"
CustomLog "c:/apache/test_access.log" common
ScriptAlias /cgi/ "C:/Apache/test.pfr/www/cgi/"
</VirtualHost>
Разбираемся с переменныеми окружения.
Для получения от web сервера служебной информации, параметров http запроса служат переменные окружения.
REQUEST_METHOD - способ передачи параметров (POST или GET)
PATH_INFO содержит подстроку передаваемых параметров после символа "/"
QUERY_STRING Строка переданных серверу параметров
CONTENT_LENGTH Длина содержимого, например, text/html
CONTENT_TYPE MIME-тип содержимого, например, text/html
Полный список переменных можно легко нагуглить
POST && GET
Чтобы определить, каким именно методом CGI-программе переданы параметры, в программе надо прочитать переменную среды REQUEST_METHOD.
Параметры могут быть прочитаны:
1. Из переменной окружения QUERY_STRING для метода GET
2. Из стандартного ввода (STDIN) с помощью процедуры ReadLn для метода POST
Минимальное приложение из демо лазаря (http://wiki.lazarus.freepascal.org/CGI_ Web_Programming)
эхо, читает данные и отправляет их обратно клиенту.
1. Setting a cookie
2. Outputting the content-type (ie make it put out legal text for HTTP)
3. Reading Cookies
4. Reading form data via GET
5. Reading form data via POST
создаем обычное консольное приложение, где и пишем:
program mini;
uses dos;
var
a:string;
c:char;
begin
// set a cookie (must come before content-type line below)
// don't forget to change the expires date
writeln('Set-cookie:widget=value; path=/; expires= Mon, 29-Dec-2010 8:37:00 GMT');
// output legal http page
writeln('Content-Type:text/html',#10#13) ;
// demonstrate get cookies
a:= GetEnv('HTTP_COOKIE');
writeln('cookies:',a);
// demonstrate GET result
a:='';
a:= GetEnv('QUERY_STRING');
writeln('GET: ',a);
// demonstrate POST result
a:='';
while not eof(input) do
begin
read(c);
a:= a+c;
end;
writeln('POST: ',a);
end.
Результат работы http://127.0.0.1:8081/cgi/mini.exe?test=1 234 - отправили get запрос
Set-cookie:widget=value; path=/; expires= Mon, 29-Dec-2010 8:37:00 GMT Content-Type:text/html cookies:
GET: test=1234 POST:
**************************************** ****************************************
GET запросы
Помещаются в URL запроса
Формат запроса: GET сценарий?параметры Переменные окружения:
QUERY_STRING содержат переданные значения и параметры
REQUEST_METHOD=GET. Метод запроса
Полезные переменные
'SCRIPT_NAME' - содержит URL адрес выполняемого модуля (без подстроки параметров)
пример
http://127.0.0.1:8081/cgi/cgifirst.exe/n ame - исходная строка
/cgi/cgifirst.exe - SCRIPT_NAME
'PATH_INFO' - содержит подстроку передаваемых параметров после символа "/"
пример
http://127.0.0.1:8081/cgi/cgifirst.exe/n ame - исходная строка
/name - PATH_INFO
Пишем CGI приложение на Lazarus, fcl-web
Открываем Lazarus, создаем новую программу, вписываем туда следующий код, сохраняем как cgifirst.pp, компилируем программу fpc cgifirst.pp или из самой IDE (Ctrl-F9)
за данные, которые отправляет сервер отвечает класс TRequest
за данные, которые CGI отправляет клиенту отвечает класс TResponse
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi;
Type
{ TCGIApp }
TCGIApp = class(TCustomCGIApplication)
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
begin
inherited HandleRequest(ARequest, AResponse);
// пишем клиенту
AResponse.Content := 'Hello Word !'; // отправляем клиенту приветственное сообщение
end;
begin
with TCGIApp.create(nil) do
try
Initialize;
Run;
finally
Free;
end;
end.
кладем exe файл в директорию C:/Apache/test.pfr/www/cgi/ и запускаем браузер, в командной строке которого набираем:
http://127.0.0.1:8081/cgi/cgifirst.exe
вуаля! перед нами надпись "Hello Word !" во всей своей красе :)
Теперь напишем приложение, которое реагирует на get запросы
GET запросы передаются в строке URL адреса, например
http://127.0.0.1:8081/cgi/cgifirst.exe?t est=1234
часть строки после вопроса test=1234 - и есть get запрос
в cgi приложении мы получаем ее с помощью переменной окружения QUERY_STRING
Теперь наша программа выглядит так:
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi, cgiModules;
Type
{ TCGIApp }
TCGIApp = class(TCustomCGIApplication)
private
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
var
response_text: TStringList;
begin
inherited HandleRequest(ARequest, AResponse);
AResponse.Content := 'You get variable is ' + EnvironmentVariable['QUERY_STRING'];
end;
begin
with TCGIApp.create(nil) do
try
Initialize;
Run;
finally
Free;
end;
end.
Проверяем в браузере, вводим
http://127.0.0.1:8081/cgi/cgifirst.exe?t est=1234
получаем: You get variable is test=1234
Можно анализировать запрос, который содержится в url строке и с помощью переменной PATH_INFO
Например такой запрос http://127.0.0.1:8081/cgi/cgifirst.exe/t est
можно проанализировать следующим образом
var := EnvironmentVariable['PATH_INFO'];
if strcomp(var, '/test') <> 0
then... else ...
Как правило, такого рода переменные разработчики компонентов помещают в Action List, в некоторых компонентах есть даже событие OnAction.
Еще мы можем загрузить html файл (сформированный нами заранее) с диска, в компонентах fcl-web это выглядит следующим образом.
AResponse.ContentType := 'text/html;charset=utf-8'; // указываем тип содержимого
AResponse.Contents.LoadFromFile(ExtractF ilePath(ParamStr(0)) + 'mainpage.html'); // грузим шаблон из файла
Handled := True;
**************************************** ***************************************
Часть 2. POST запросы
POST помещает данные не в URL, а в тело запроса.
В программе мы получаем их из стандартного потока ввода (STDIN). При считывании входящего потока программа не должна пытаться читать больше чем указано в переменной CONTENT_LENGTH. (В консольных программах обычно поток ввода - это клавиатура или файл, а поток вывода - это консоль или экран)
Формат запроса: POST
Переменные окружения:
REQUEST_METHOD = POST.
CONTENT_LENGTH - содержит длину данных (в bytes)
Рисуем форму, которая будет отправлять POST запросы
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//RU">
<html>
<head>
<title>Заголовок</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="resourse-type" content="document">
</head>
<body>
<FORM ACTION="http://127.0.0.1:8081/cgi/cgifir st.exe?varget=testget"
METHOD="POST">
<DIV>Введите что-нибудь:<BR>
<INPUT NAME="data" SIZE="60" MAXLENGTH="80"><BR>
<INPUT TYPE="SUBMIT" VALUE="Send"></DIV>
</FORM>
</body>
</html>
FORM ACTION=... указываем скрипт, который будет обрабатывать наши данные
в этом запросе мы указываем еще и get запрос ?varget=testget,
метод, которым мы отрпавляем наши запросы значится в описании формы
<FORM ACTION="..." METHOD="POST">
Теперь, как только мы нажмем на кнопку "Send", данные уйдут на сервер.
На с++ код cgi может выглядеть так:
весь код http://www.cs.tut.fi/~jkorpela/forms/cgi c.html
#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 80
#define EXTRA 5
/* 4 for field name "data", 1 for "=" */
#define MAXINPUT MAXLEN+EXTRA+2
/* 1 for added line break, 1 for trailing NUL */
#define DATAFILE "../data/data.txt"
void unencode(char *src, char *last, char *dest)
{
for(; src != last; src++, dest++)
if(*src == '+')
*dest = ' ';
else if(*src == '%') {
int code;
if(sscanf(src+1, "%2x", &code) != 1) code = '?';
*dest = code;
src +=2; }
else
*dest = *src;
*dest = '\n';
*++dest = '\0';
}
int main(void)
{
char *lenstr;
char input[MAXINPUT], data[MAXINPUT];
long len;
printf("%s%c%c\n",
"Content-Type:text/html;charset=iso-8 859-1",13,10);
printf("<TITLE>Response</TITLE>\n");
lenstr = getenv("CONTENT_LENGTH");
if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!=1 || len > MAXLEN)
printf("<P>Error in invocation - wrong FORM probably.");
else {
FILE *f;
fgets(input, len+1, stdin);
unencode(input+EXTRA, input+len, data);
f = fopen(DATAFILE, "a");
if(f == NULL)
printf("<P>Sorry, cannot store your data.");
else
fputs(data, f);
fclose(f);
printf("<P>Thank you! Your contribution has been stored.");
}
return 0;
}
lenstr = getenv("CONTENT_LENGTH"); // читаем длину
fgets(input, len+1, stdin); // читаем входной поток stdin
Функция unencode служит для преобразования символов, декодирования
Декодирование.
В CGI приложение данные приходят в специальном формате.
1. Имена полей и их значения разделяются знаком равно (=), name=value
2. Пары имя поля-значения разделяются амперсандом (&), name1=value1&name2=value2
3. Некондиционные символы (Inconvenient characters), такие как пробелы (заменяются +), амперсанды (%26), русские буквы заменяются hex эквивалентами
Например
data=Русский текст // исходный
data=%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B 8%D0%B9+%D1%82%D0%B5%D0%BA%D1%81%D1%82 // то, что получим в программе
Метод POST на delphi для windows
uses windows, sysutils;
...
// Получение переданных параметров
if getvar('REQUEST_METHOD') = 'POST' then
begin
post_vars := getvar('CONTENT_LENGTH'); // длина
if post_vars <> '' then
begin
size := strtoint(post_vars);
setlength(parmstring, size);
for i := 1 to size do read(post_vars[i]); // читаем поток
end;
end;
Продолжаем доработку того же проекта. Попытаемся получить POST запрос.
Используя стандартную библиотеку fcl (наше приложение по прежнему является наследником от TCustomCGIApplication
TCGIApp = class(TCustomCGIApplication))
Путем исследования исходного кода выясняем, что TCustomCGIApplication использует модуль httpdefs и его классы:
TCGIRequest - наследник httpdefs.TRequest, а он в свою очередь наследник httpdefs.THTTPHeader, в этом классе и есть
замечательные списки
// Lists
Property CookieFields : TStrings Read FCookieFields Write SetCookieFields; // здесь куки
Property ContentFields: TStrings read FContentFields; // здесь хранятся данные POST запросов
property QueryFields : TStrings read FQueryFields; // здесь хранятся данные GET запросов
Таким образом, мы избавлены от небходимости самостоятельно читать поток STDIN, перекодировать переменные, вместо этого дописываем код в HandleRequest строки, отвечающие за получение данных POST запросов
Resp_list.Add('<br>' + ' POST VARIABLE ');
if ARequest.ContentFields.Count <> 0 then
begin
for i := 0 to ARequest.ContentFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ARequest.ContentFields[i]);
end;
вся программа
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi;
Type
{ TCGIApp } // класс наследник от TCustomCGIApplication
TCGIApp = class(TCustomCGIApplication)
private
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
var
Resp_list: TstringList;
len, i: integer;
stdIn, Size, Actual: cardinal;
//Req_List: TstringList;
begin
inherited HandleRequest(ARequest, AResponse);
//AResponse.Content := inttostr(RequestVariableCount);
Resp_list := TStringList.create;
try
// просто список с переменными
Resp_list.Add(' hello ');
Resp_list.Add('<br>');
Resp_list.Add(' You get variable is ' + EnvironmentVariable['QUERY_STRING']);
Resp_list.Add(' method is ' + EnvironmentVariable['REQUEST_METHOD']);
Resp_list.Add('<br>');
Resp_list.Add(' SCRIPT_NAME ' + EnvironmentVariable['SCRIPT_NAME']);
Resp_list.Add('<br>');
Resp_list.Add(' PATH_INFO ' + EnvironmentVariable['PATH_INFO']);
// длина контента
Resp_list.Add(' length ' + inttostr(ARequest.ContentLength)+ LineEnding);
// или так + EnvironmentVariable['CONTENT_LENGTH']);
Resp_list.Add('<br>');
// GET данные
Resp_list.Add('<br>' + ' GET VARIABLE ');
if ARequest.QueryFields.Count <> 0 then
begin
for i := 0 to ARequest.QueryFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ' '+ ARequest.QueryFields[i]);
end;
// POST данные
Resp_list.Add('<br>' + ' POST VARIABLE ');
if ARequest.ContentFields.Count <> 0 then
begin
for i := 0 to ARequest.ContentFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ' '+ ARequest.ContentFields[i]);
end;
AResponse.Contents := Resp_list;
finally
Resp_list.free;
end;
end;
// тело программы
begin
with TCGIApp.create(nil) do
try
Initialize;
// методы инициализации
// FRequest:=TCGIRequest.CreateCGI(Self);
// InitRequestVars; - инициализация переменных POST и GET
Run;
finally
Free;
end;
end.
Получаем
You get variable is varget=testget method is POST
SCRIPT_NAME /cgi/cgifirst.exe
PATH_INFO
length 12
GET VARIABLE
0 varget=testget
POST VARIABLE
0 data=russian
Почитать
http://httpd.apache.org/docs/2.2/howto/c gi.html настройка cgi в apache
http://w-wb.com/cgi/ - учебник по cgi
http://www.cs.tut.fi/~jkorpela/forms/cgi c.html - cgi start (c++ пример)
http://php.su/learnphp/cgi
http://www.intuit.ru/department/internet/c gi/5/ - интуит
Delphi CGI
http://www.delphisources.ru/pages/faq/ba se/cgi_apps.html
http://mf.grsu.by/UchProc/konspekt/delph i_book/ch04/ch01
http://www.drbob42.com/books/cgi.htm
lazarus powtil (теперь объектно-ориентированный, известен также под именем PSP)
http://www.freepascal.ru/forum/viewtopic.p hp?f=6&t=3606 - простые примеры
http://powtils.googlecode.com/svn/dev/
http://sourceforge.net/projects/pascal-w ebdev/
http://z505.com/powtils/news.shtml
Пакет fcl-web (weblaz.lpk) Можно взять на source forge
установка
fcl-web, если он не установлен - топаем C:\lazarus\components\fpweb\weblaz.lpk
и устанавливаем этот пакет (запускаем Лазаря, открываем пакет, компилируем, даем команду установить и соглашаемся на пересборку Лазаря)
После установки этого пакета в меню создать ... мы можем лицезреть новый тип приложения - CGI Application, его и выбираем.
При этом создастся TFPWebModule с событиями:...
компоненты лазаря
http://wiki.lazarus.freepascal.org/Compo nents_and_Code_examples
статья Леонардо CGI + ExtJs + Firebird на Лазаре !!! Особая благодарность
блог Леонардо leonardorame.blogspot
Windows / Lazarus 0.9.28.2 beta / fpc 2.2.4
Механизм взаимодействия cgi
1. При получении запроса обозревателя к cgi сервер запускает приложение, передает ему данные из командной строки запроса
2. cgi формирует ответ и помещает его в выходной поток
3. сервер посылает ответ, используя http протокол обратно обозревателю
недостатки cgi - невысокая скорость обработки, повышенная загрузка web сервера, т.к. в случае паралелльной обработки сервер запускает отдельный процесс, для каждого процесса - копию модуля расширения памяти компьютера
для передачи/получения параметров используются стандартный входной/выходной потоки (stdin/stdout)
для передачи параметров и получения системной информации могут использоваться переменные окружения
Само CGI приложение - это ни что иное, как обычное консольное приложение (Win32 CONSOLE). Вот только устройством ввода для него служит строка браузера (URL), в котором содержаться get запросы или форма на html странице с кнопочкой submit, которая отправляет post запросы.
Запустить cgi приложение можно просто набрав в строке браузера адрес, который приведет нас к cgi приложению
http://127.0.0.1:8081/cgi/cgifirst.exe
Можно компилировать cgi приложения с расширением cgi, а можно с расширением exe, это не принципиально.
Настройки Apache для запуска cgi программ: (http://httpd.apache.org/docs/2.2/howto/c
Файл конфигурации сервера httpd.conf
При старте должны загружаться модули: * mod_alias * mod_cgi
По умолчанию путь к cgi модулям находится в директории [Путь куда установлен Apache]/cgi-bin
Директива ScriptAlias позволяет задать псевдоним для директории со скриптами
ScriptAlias /cgi/ "C:/Apache/test.pfr/www/cgi/"
Опции
specify that CGI execution was permitted in a particular directory:
<Directory /usr/local/apache2/htdocs/somedir>
Options +ExecCGI
</Directory>
Директива AddHandler сообщает серверу, что файлы с таким расширением (.cgi .pl) будут опознаны как CGI программы
AddHandler cgi-script .cgi .pl
Пример. Определяем путь к приложениям для виртуального хоста.
Настройка виртуального хоста у нас находится в файле httpd-vhosts.conf.
В этом файле мы определили виртуальный хост, который будет слушать 8081 порт,
директория с документами будет располагаться в "C:/Apache/test.pfr/www",
а cgi приложения будут располагаться здесь "C:/Apache/test.pfr/www/cgi/"
(для linux'ов )
NameVirtualHost *:8081
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for all requests that do not
# match a ServerName or ServerAlias in any <VirtualHost> block.
#
<VirtualHost *:8081>
ServerAdmin webmaster@dummy-host2.localhost
DocumentRoot "C:/Apache/test.pfr/www"
ServerName test.pfr
ErrorLog "c:/apache/test_error.log"
CustomLog "c:/apache/test_access.log" common
ScriptAlias /cgi/ "C:/Apache/test.pfr/www/cgi/"
</VirtualHost>
Разбираемся с переменныеми окружения.
Для получения от web сервера служебной информации, параметров http запроса служат переменные окружения.
REQUEST_METHOD - способ передачи параметров (POST или GET)
PATH_INFO содержит подстроку передаваемых параметров после символа "/"
QUERY_STRING Строка переданных серверу параметров
CONTENT_LENGTH Длина содержимого, например, text/html
CONTENT_TYPE MIME-тип содержимого, например, text/html
Полный список переменных можно легко нагуглить
POST && GET
Чтобы определить, каким именно методом CGI-программе переданы параметры, в программе надо прочитать переменную среды REQUEST_METHOD.
Параметры могут быть прочитаны:
1. Из переменной окружения QUERY_STRING для метода GET
2. Из стандартного ввода (STDIN) с помощью процедуры ReadLn для метода POST
Минимальное приложение из демо лазаря (http://wiki.lazarus.freepascal.org/CGI_
эхо, читает данные и отправляет их обратно клиенту.
1. Setting a cookie
2. Outputting the content-type (ie make it put out legal text for HTTP)
3. Reading Cookies
4. Reading form data via GET
5. Reading form data via POST
создаем обычное консольное приложение, где и пишем:
program mini;
uses dos;
var
a:string;
c:char;
begin
// set a cookie (must come before content-type line below)
// don't forget to change the expires date
writeln('Set-cookie:widget=value; path=/; expires= Mon, 29-Dec-2010 8:37:00 GMT');
// output legal http page
writeln('Content-Type:text/html',#10#13)
// demonstrate get cookies
a:= GetEnv('HTTP_COOKIE');
writeln('cookies:',a);
// demonstrate GET result
a:='';
a:= GetEnv('QUERY_STRING');
writeln('GET: ',a);
// demonstrate POST result
a:='';
while not eof(input) do
begin
read(c);
a:= a+c;
end;
writeln('POST: ',a);
end.
Результат работы http://127.0.0.1:8081/cgi/mini.exe?test=1
Set-cookie:widget=value; path=/; expires= Mon, 29-Dec-2010 8:37:00 GMT Content-Type:text/html cookies:
GET: test=1234 POST:
****************************************
GET запросы
Помещаются в URL запроса
Формат запроса: GET сценарий?параметры Переменные окружения:
QUERY_STRING содержат переданные значения и параметры
REQUEST_METHOD=GET. Метод запроса
Полезные переменные
'SCRIPT_NAME' - содержит URL адрес выполняемого модуля (без подстроки параметров)
пример
http://127.0.0.1:8081/cgi/cgifirst.exe/n
/cgi/cgifirst.exe - SCRIPT_NAME
'PATH_INFO' - содержит подстроку передаваемых параметров после символа "/"
пример
http://127.0.0.1:8081/cgi/cgifirst.exe/n
/name - PATH_INFO
Пишем CGI приложение на Lazarus, fcl-web
Открываем Lazarus, создаем новую программу, вписываем туда следующий код, сохраняем как cgifirst.pp, компилируем программу fpc cgifirst.pp или из самой IDE (Ctrl-F9)
за данные, которые отправляет сервер отвечает класс TRequest
за данные, которые CGI отправляет клиенту отвечает класс TResponse
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi;
Type
{ TCGIApp }
TCGIApp = class(TCustomCGIApplication)
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
begin
inherited HandleRequest(ARequest, AResponse);
// пишем клиенту
AResponse.Content := 'Hello Word !'; // отправляем клиенту приветственное сообщение
end;
begin
with TCGIApp.create(nil) do
try
Initialize;
Run;
finally
Free;
end;
end.
кладем exe файл в директорию C:/Apache/test.pfr/www/cgi/ и запускаем браузер, в командной строке которого набираем:
http://127.0.0.1:8081/cgi/cgifirst.exe
вуаля! перед нами надпись "Hello Word !" во всей своей красе :)
Теперь напишем приложение, которое реагирует на get запросы
GET запросы передаются в строке URL адреса, например
http://127.0.0.1:8081/cgi/cgifirst.exe?t
часть строки после вопроса test=1234 - и есть get запрос
в cgi приложении мы получаем ее с помощью переменной окружения QUERY_STRING
Теперь наша программа выглядит так:
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi, cgiModules;
Type
{ TCGIApp }
TCGIApp = class(TCustomCGIApplication)
private
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
var
response_text: TStringList;
begin
inherited HandleRequest(ARequest, AResponse);
AResponse.Content := 'You get variable is ' + EnvironmentVariable['QUERY_STRING'];
end;
begin
with TCGIApp.create(nil) do
try
Initialize;
Run;
finally
Free;
end;
end.
Проверяем в браузере, вводим
http://127.0.0.1:8081/cgi/cgifirst.exe?t
получаем: You get variable is test=1234
Можно анализировать запрос, который содержится в url строке и с помощью переменной PATH_INFO
Например такой запрос http://127.0.0.1:8081/cgi/cgifirst.exe/t
можно проанализировать следующим образом
var := EnvironmentVariable['PATH_INFO'];
if strcomp(var, '/test') <> 0
then... else ...
Как правило, такого рода переменные разработчики компонентов помещают в Action List, в некоторых компонентах есть даже событие OnAction.
Еще мы можем загрузить html файл (сформированный нами заранее) с диска, в компонентах fcl-web это выглядит следующим образом.
AResponse.ContentType := 'text/html;charset=utf-8'; // указываем тип содержимого
AResponse.Contents.LoadFromFile(ExtractF
Handled := True;
****************************************
Часть 2. POST запросы
POST помещает данные не в URL, а в тело запроса.
В программе мы получаем их из стандартного потока ввода (STDIN). При считывании входящего потока программа не должна пытаться читать больше чем указано в переменной CONTENT_LENGTH. (В консольных программах обычно поток ввода - это клавиатура или файл, а поток вывода - это консоль или экран)
Формат запроса: POST
Переменные окружения:
REQUEST_METHOD = POST.
CONTENT_LENGTH - содержит длину данных (в bytes)
Рисуем форму, которая будет отправлять POST запросы
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//RU">
<html>
<head>
<title>Заголовок</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="resourse-type" content="document">
</head>
<body>
<FORM ACTION="http://127.0.0.1:8081/cgi/cgifir
METHOD="POST">
<DIV>Введите что-нибудь:<BR>
<INPUT NAME="data" SIZE="60" MAXLENGTH="80"><BR>
<INPUT TYPE="SUBMIT" VALUE="Send"></DIV>
</FORM>
</body>
</html>
FORM ACTION=... указываем скрипт, который будет обрабатывать наши данные
в этом запросе мы указываем еще и get запрос ?varget=testget,
метод, которым мы отрпавляем наши запросы значится в описании формы
<FORM ACTION="..." METHOD="POST">
Теперь, как только мы нажмем на кнопку "Send", данные уйдут на сервер.
На с++ код cgi может выглядеть так:
весь код http://www.cs.tut.fi/~jkorpela/forms/cgi
#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 80
#define EXTRA 5
/* 4 for field name "data", 1 for "=" */
#define MAXINPUT MAXLEN+EXTRA+2
/* 1 for added line break, 1 for trailing NUL */
#define DATAFILE "../data/data.txt"
void unencode(char *src, char *last, char *dest)
{
for(; src != last; src++, dest++)
if(*src == '+')
*dest = ' ';
else if(*src == '%') {
int code;
if(sscanf(src+1, "%2x", &code) != 1) code = '?';
*dest = code;
src +=2; }
else
*dest = *src;
*dest = '\n';
*++dest = '\0';
}
int main(void)
{
char *lenstr;
char input[MAXINPUT], data[MAXINPUT];
long len;
printf("%s%c%c\n",
"Content-Type:text/html;charset=iso-8
printf("<TITLE>Response</TITLE>\n");
lenstr = getenv("CONTENT_LENGTH");
if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!=1 || len > MAXLEN)
printf("<P>Error in invocation - wrong FORM probably.");
else {
FILE *f;
fgets(input, len+1, stdin);
unencode(input+EXTRA, input+len, data);
f = fopen(DATAFILE, "a");
if(f == NULL)
printf("<P>Sorry, cannot store your data.");
else
fputs(data, f);
fclose(f);
printf("<P>Thank you! Your contribution has been stored.");
}
return 0;
}
lenstr = getenv("CONTENT_LENGTH"); // читаем длину
fgets(input, len+1, stdin); // читаем входной поток stdin
Функция unencode служит для преобразования символов, декодирования
Декодирование.
В CGI приложение данные приходят в специальном формате.
1. Имена полей и их значения разделяются знаком равно (=), name=value
2. Пары имя поля-значения разделяются амперсандом (&), name1=value1&name2=value2
3. Некондиционные символы (Inconvenient characters), такие как пробелы (заменяются +), амперсанды (%26), русские буквы заменяются hex эквивалентами
Например
data=Русский текст // исходный
data=%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B
Метод POST на delphi для windows
uses windows, sysutils;
...
// Получение переданных параметров
if getvar('REQUEST_METHOD') = 'POST' then
begin
post_vars := getvar('CONTENT_LENGTH'); // длина
if post_vars <> '' then
begin
size := strtoint(post_vars);
setlength(parmstring, size);
for i := 1 to size do read(post_vars[i]); // читаем поток
end;
end;
Продолжаем доработку того же проекта. Попытаемся получить POST запрос.
Используя стандартную библиотеку fcl (наше приложение по прежнему является наследником от TCustomCGIApplication
TCGIApp = class(TCustomCGIApplication))
Путем исследования исходного кода выясняем, что TCustomCGIApplication использует модуль httpdefs и его классы:
TCGIRequest - наследник httpdefs.TRequest, а он в свою очередь наследник httpdefs.THTTPHeader, в этом классе и есть
замечательные списки
// Lists
Property CookieFields : TStrings Read FCookieFields Write SetCookieFields; // здесь куки
Property ContentFields: TStrings read FContentFields; // здесь хранятся данные POST запросов
property QueryFields : TStrings read FQueryFields; // здесь хранятся данные GET запросов
Таким образом, мы избавлены от небходимости самостоятельно читать поток STDIN, перекодировать переменные, вместо этого дописываем код в HandleRequest строки, отвечающие за получение данных POST запросов
Resp_list.Add('<br>' + ' POST VARIABLE ');
if ARequest.ContentFields.Count <> 0 then
begin
for i := 0 to ARequest.ContentFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ARequest.ContentFields[i]);
end;
вся программа
program cgifirst;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, httpDefs, custcgi;
Type
{ TCGIApp } // класс наследник от TCustomCGIApplication
TCGIApp = class(TCustomCGIApplication)
private
public
Procedure HandleRequest(ARequest: Trequest; AResponse: TResponse);override;
end;
{ TCGIApp }
procedure TCGIApp.HandleRequest(ARequest: Trequest; AResponse: TResponse);
var
Resp_list: TstringList;
len, i: integer;
stdIn, Size, Actual: cardinal;
//Req_List: TstringList;
begin
inherited HandleRequest(ARequest, AResponse);
//AResponse.Content := inttostr(RequestVariableCount);
Resp_list := TStringList.create;
try
// просто список с переменными
Resp_list.Add(' hello ');
Resp_list.Add('<br>');
Resp_list.Add(' You get variable is ' + EnvironmentVariable['QUERY_STRING']);
Resp_list.Add(' method is ' + EnvironmentVariable['REQUEST_METHOD']);
Resp_list.Add('<br>');
Resp_list.Add(' SCRIPT_NAME ' + EnvironmentVariable['SCRIPT_NAME']);
Resp_list.Add('<br>');
Resp_list.Add(' PATH_INFO ' + EnvironmentVariable['PATH_INFO']);
// длина контента
Resp_list.Add(' length ' + inttostr(ARequest.ContentLength)+ LineEnding);
// или так + EnvironmentVariable['CONTENT_LENGTH']);
Resp_list.Add('<br>');
// GET данные
Resp_list.Add('<br>' + ' GET VARIABLE ');
if ARequest.QueryFields.Count <> 0 then
begin
for i := 0 to ARequest.QueryFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ' '+ ARequest.QueryFields[i]);
end;
// POST данные
Resp_list.Add('<br>' + ' POST VARIABLE ');
if ARequest.ContentFields.Count <> 0 then
begin
for i := 0 to ARequest.ContentFields.Count -1 do
Resp_list.Add(' <br> '+ inttostr(i) + ' '+ ARequest.ContentFields[i]);
end;
AResponse.Contents := Resp_list;
finally
Resp_list.free;
end;
end;
// тело программы
begin
with TCGIApp.create(nil) do
try
Initialize;
// методы инициализации
// FRequest:=TCGIRequest.CreateCGI(Self);
// InitRequestVars; - инициализация переменных POST и GET
Run;
finally
Free;
end;
end.
Получаем
You get variable is varget=testget method is POST
SCRIPT_NAME /cgi/cgifirst.exe
PATH_INFO
length 12
GET VARIABLE
0 varget=testget
POST VARIABLE
0 data=russian
Почитать
http://httpd.apache.org/docs/2.2/howto/c
http://w-wb.com/cgi/ - учебник по cgi
http://www.cs.tut.fi/~jkorpela/forms/cgi
http://php.su/learnphp/cgi
http://www.intuit.ru/department/internet/c
Delphi CGI
http://www.delphisources.ru/pages/faq/ba
http://mf.grsu.by/UchProc/konspekt/delph
http://www.drbob42.com/books/cgi.htm
lazarus powtil (теперь объектно-ориентированный, известен также под именем PSP)
http://www.freepascal.ru/forum/viewtopic.p
http://powtils.googlecode.com/svn/dev/
http://sourceforge.net/projects/pascal-w
http://z505.com/powtils/news.shtml
Пакет fcl-web (weblaz.lpk) Можно взять на source forge
установка
fcl-web, если он не установлен - топаем C:\lazarus\components\fpweb\weblaz.lpk
и устанавливаем этот пакет (запускаем Лазаря, открываем пакет, компилируем, даем команду установить и соглашаемся на пересборку Лазаря)
После установки этого пакета в меню создать ... мы можем лицезреть новый тип приложения - CGI Application, его и выбираем.
При этом создастся TFPWebModule с событиями:...
компоненты лазаря
http://wiki.lazarus.freepascal.org/Compo
статья Леонардо CGI + ExtJs + Firebird на Лазаре !!! Особая благодарность
блог Леонардо leonardorame.blogspot