fcl-web/ru
│
English (en) │
español (es) │
français (fr) │
русский (ru) │
Что такое fpWeb
fpWeb можно использовать для создания приложений веб-сервера. Приложение веб-сервера может быть одним из следующих
- Приложение CGI
- Приложение FastCGI
- Модуль Apache
- Автономный HTTP-сервер, использующий компоненты HTTP-сервера Free Pascal.
- Автономный сервер HTTP с использованием GNU libmicrohttp
- Приложение Windows SysHTTP.
Во всех случаях модель разработки одинакова: вы регистрируете обработчики для набора URL-адресов (называемых маршрутами), которые будет поддерживать ваше приложение.
Затем вы встраиваете эти маршруты в одну из указанных выше сред: Это означает, что вы выбираете объект приложения, который будете использовать.
В lazarus IDE у вас есть выбор приложений, когда вы запускаете новый проект. После запуска приложения можно изменить тип приложения, обычно это означает просто изменение имени модуля в разделе uses проекта.
В одном двоичном файле можно поддерживать несколько сред, хотя это требует дополнительной работы.
fpWeb также предлагает некоторую готовую поддержку для REST-сервера базы данных или для механизмов JSON RPC 2.0. Они построены поверх вышеупомянутой конструкции. Платформа Brook также построена на основе базовой архитектуры, описанной выше.
Подробнее об этом можно прочитать в fpWeb Tutorial
Использование fpWeb вместе с Lazarus
Установка пакета weblaz fpWeb Lazarus
Первым делом необходимо установить пакет, который находится по пути lazarus/components/fpweb/weblaz.lpk. Как обычно с пакетами времени разработки, вам придется пересобрать Lazarus.
Создание CGI-приложения
После установки пакета weblaz можно создать очень простое веб-приложение CGI, отображающее HTML-страницу, перейдя в меню Lazarus «Файл - Новый ...». Из списка возможных приложений выберите «Приложение CGI», как показано на изображении ниже, которое создаст основной файл проекта CGI и веб-модуль fpweb.
TFPWebModule позволяет вам управлять свойствами и событиями с помощью инспектора объектов.
Чтобы добавить код для отображения страницы, необходимо добавить обработчик запроса. Для этого дважды щелкните свойство OnRequest в инспекторе объектов, как показано на изображении ниже:
В обработчике событий нужно написать HTML-код, который будет отображаться браузером. Чтобы избежать смешивания Pascal и HTML, эту страницу можно загружать из каталога, в котором находится исполняемый файл CGI, с помощью AResponse.Contents.LoadFromFile()
.
Тип ответа должен быть установлен в AResponse.ContentType
. Для HTML-страниц это должно иметь значение 'text/html;charset=utf-8'.
Наконец, для параметра Handled должно быть установлено значение True, чтобы указать, что запрос был успешно обработан (и вернуть в браузер код состояния 200 OK). После добавления этого кода веб-модуль должен выглядеть так:
unit mainpage;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, HTTPDefs, websession, fpHTTP, fpWeb;
type
{ TFPWebModule1 }
TFPWebModule1 = class(TFPWebModule)
procedure DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
private
{ private declarations }
public
{ public declarations }
end;
var
FPWebModule1: TFPWebModule1;
implementation
{$R *.lfm}
{ TFPWebModule1 }
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
begin
AResponse.ContentType := 'text/html;charset=utf-8';
AResponse.Contents.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'mainpage.html');
Handled := True;
end;
begin
RegisterHTTPModule('TFPWebModule1', TFPWebModule1);
end.
Развертывание приложения CGI
В этом разделе предполагается, что используется веб-сервер Apache. Конечно, можно использовать и другие веб-серверы, поддерживающие CGI (nginx, cherokee).
Apache можно загрузить здесь: http://httpd.apache.org/download.cgi или установить с помощью диспетчера пакетов вашего дистрибутива.
При установке Apache по умолчанию все файлы, расположенные в его каталоге cgi-bin, будут обрабатываться как программы CGI, поэтому пользователь не сможет получить доступ к размещенным там простым файлам HTML. Этот каталог можно задать в файле httpd.conf в следующем разделе:
# # ScriptAlias: определяет, какие каталоги содержат сценарии сервера. # ScriptAliases по сути то же, что и Aliases(псевдонимы), за исключением того, что # документы в целевом каталоге рассматриваются как приложения и # запускается сервером по запросу, а не как документы, отправленные на # клиент. К директивам ScriptAlias применяются те же правила для завершающего символа "/", # что и к Alias. # ScriptAlias /cgi-bin/ "C:/Program Files/Apache Software Foundation/Apache2.2/cgi-bin/"
Если вы поместите исполняемый файл с именем «mywebpage.cgi» в этот каталог, тогда к странице можно будет получить доступ как http://localhost/cgi-bin/mywebpage.cgi (или удаленно с соответствующим IP-адресом или доменным именем).
Fcl-web с Lazarus в Windows создает файлы .exe. Чтобы Apache обслуживал эти файлы, вам необходимо добавить это:
AddHandler cgi-script .exe
И чтобы обслуживать ваши исполняемые файлы из другого каталога, вы добавляете это:
ScriptAlias /bin/ "C:/lazarus_projects/test-fclweb/bin/"
<Directory "C:/lazarus_projects/test-fclweb/bin/">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
Форматирование HTML и читаемых полей запроса
В предыдущем примере была показана простая HTML-страница. Можно, например, пожелать
- динамически изменять вывод HTML-страницы и
- при чтении переменных, которые браузер передал веб-странице в полях запроса (например, в действиях HTTP GET и HTTP POST).
Простое решение для первой проблемы - просто использовать стандартную подпрограмму Паскаля Format с добавлением %s или %d в файл HTML в соответствующие места, которые получат настраиваемое значение.
Чтение данных GET
Чтобы прочитать переменные GET, можно использовать ARequest.QueryFields
, который является потомком TStrings. Каждая переменная будет находиться в отдельной строке в TStrings в формате имя_переменной = значение, аналогично тому, как они отображаются в адресе страницы браузера. Для поиска определенной переменной можно использовать ARequest.QueryFields.Values[]
, передав имя переменной в скобках, чтобы получить обратно ее значение, вместо ручного анализа строки.
Итоговый код:
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
var
HexText, AsciiText: string;
begin
HexText := ARequest.QueryFields.Values['hex'];
AsciiText := HexToAnsii(HexText);
AResponse.ContentType := 'text/html;charset=utf-8';
AResponse.Contents.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'mainpage.html');
AResponse.Contents.Text := Format(AResponse.Contents.Text,
[HexText, AsciiText]);
Handled := True;
end;
Чтение данных POST
Данные, отправленные запросами POST, можно получить из TRequest.Content
. Он будет без заголовков запроса - другими словами, он будет содержать тело запроса.
К отправленным данным формы также можно получить доступ с помощью TRequest.ContentFields
, который представляет собой содержимое, проанализированное как поля, разделенные знаком &
и декодированные. Например, следующий набор значений формы:
login: dfg 345&& login_senha: ==== email: dfg
Будут преобразованы в 'APPLICATION/X-WWW-FORM-URLENCODED' следующим образом (см. wikipedia POST (HTTP)):
login=dfg+345%26%26&login_senha=%3D%3D%3D%3D&email=dfg
И будет доступно в TRequest.ContentFields
через индекс строки или через свойство Values, что удобнее:
TRequest.ContentFields[0]: login=dfg 345&& TRequest.ContentFields[1]: login_senha===== TRequest.ContentFields[2]: email=dfg TRequest.ContentFields.Values['email']: dfg
Если используются другие типы mime, кроме 'MULTIPART/FORM-DATA' и 'APPLICATION/X-WWW-FORM-URLENCODED' (типы, поддерживаемые формами HTML):
- контент доступен только в
TRequest.Content
, но не вTRequest.ContentFields
- использование этих mime-типов вызывает исключение для FPC 2.4.2 (исправлено в FPC 2.5+).
Обратите внимание, что HTTP POST также может отправлять поля запроса (в URL-адресе), например http://server/bla?question=how, и к ним обращается TRequest.QueryFields
, как описано в предыдущем разделе.
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
var
lData: String;
begin
lData := ARequest.Content;
Чтение двоичных данных POST
Загруженные файлы сохраняются во временном расположении с временным именем файла, чтобы сохранить файл, вы должны переместить его в постоянное расположение.
Для получения данных на сервере, который был размещен, например, как multipart/form-data используйте что-то вроде этого:
procedure TMainWebModule.TFPWebActions2Request(Sender: TObject;
ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
var
i: Integer;
begin
// Обработать все полученные файлы
for i := 0 to ARequest.Files.Count - 1 do
begin
// Настоятельно рекомендуется делать что-то еще, кроме Writeln ;)
writeln ('Полученное имя файла: '+ARequest.Files[i].LocalFileName);
end;
Handled := true;
end;
Клиент может отправлять данные, например, FileFormPost.
Специализированные модули
Класс *TFPWebModule* (используемый ниже) - это простой пример WEb-модуля, который можно использовать для всех видов HTTP-запросов.
Однако fpweb поставляется с некоторыми специализированными модулями, которые имеют дополнительную поддержку для специализированных задач:
- Класс TSimpleFileModule в модуле fpwebfile.pp можно использовать для отправки файлов. Вы указываете ему каталог, и он делает все остальное.
- Класс TFPHTMLModule в модуле fphtml.pp можно использовать для создания HTML.
- Класс TProxyWebModule в модуле fpwebproxy.pp - это готовый прокси для пересылки.
- Класс TFPWebProviderDataModule в модуле fpwebdata.pp обслуживает данные в формате JSON, которые могут использоваться хранилищами ExtJS.
- Класс TSQLDBRestModule в модуле sqldbrestmodule.pp реализует полноценный сервер REST, поддерживаемый SQLDB. См. Дополнительную информацию в SQLDBRestBridge.
- Класс TJSONRPCModule в модуле webjsonrpc.pp реализует службу JSON-RPC.
- Класс TExtDirectModule в модуле fpextdirect.pp реализует вариант Ext.Direct службы JSON-RPC.
Using multiple modules
If there is only one module in the web application, all requests will be directed to this module.
As your web application grows, multiple modules can be used. A new module can be added by choosing 'File - New' and then one of 'Web module' or 'HTML Web Module'.
FCL-web uses the URL to determine how a HTTP request should be handled. It must therefore know which web-modules exist in the application. To achieve this, each module must be registered.
Each module is registered with fcl-web in the initialization section of the unit it is defined in:
RegisterHTTPModule('location', TMyModule);
The module will then be invoked if an URL of the form
http://www.mysite.org/mycgi.cgi/location
or
http://www.mysite.org/mycgi.cgi?module=location
is used.
If multiple modules are present, the name of the module must appear in the URL, or an error will be raised.
This behaviour can also be forced for applications that have only a single module by setting the Application's property AllowDefaultModule to false:
Application.AllowDefaultModule := False;
In that case, the fcl-web application will always require the name of the module in the URL.
The name of the request variable that determines the module name (by default, this is 'module') can be set in the Application.ModuleVariable property. The following code
Application.ModuleVariable := 'm';
ensures that the following URL is directed to TMyModule:
http://www.mysite.org/mycgi.cgi?m=location
If all this is not enough to determine the module to which the request should be passed, the Application.OnGetModule event can be used. It is of type TGetModuleEvent:
type
TGetModuleEvent = Procedure (Sender : TObject; ARequest : TRequest;
Var ModuleClass : TCustomHTTPModuleClass) of object;
Creating an event handler for this event allows fine control over the module that is created to handle the request: the request (passed in ARequest) can be examined, and the 'ModuleClass' variable must be set to the class of the module that should handle the request.
If 'ModuleClass' is 'Nil' on return, an error will be sent to the browser.
Using Actions
A module can be used to group certain kinds of actions that logically belong together. Imagine a module TUserModule that is used to handle user management in the webpages. There can be multiple actions associated with a user:
- Creating
- Deleting
- Editing
- Displaying
These different actions can be handled by the same module. One can determine the action from the URL manually, as in:
http://mysite/mycgi.cgi/user?action=delete&id=12
This can be automated.
In order to make it easier to distinguish between various actions, the module has a property actions: this is a collection, in which each item is associated with a different response to the request. The actions have various properties:
- Name
- The name of the action. The URL will be examined to determine the name of the action.
- Content
- A string. If set, this is sent to the browser.
- Contents
- A stringlist. If set, this is sent to the browser.
- ContentProducer
- If set, the contentproducer will handle the request.
- Default
- if set to 'True', then this action is the default action. That means that if FCL-Web cannot determine the action name, this action will be executed.
- Template
- If set, this template will be processed, and the results sent to the browser.
There are also some events:
- BeforeRequest
- executed before the request is processed. Can be used to set the 'Content' or other properties.
- AfterRequest
- executed after the request is processed.
- OnRequest
- an event handler to handle the request. If set, the handler is used to handle the request.
Again, as in the case of multiple modules, the URL is used to determine which action to execute. The part right after the module part ("user" in this example) is used:
http://mysite/mycgi.cgi/user/delete&id=12
would execute the action named 'delete'.
The 'ActionVar' property of the module can be used to set the name of the request variable to use. Setting
UserModule.ActionVar := 'a';
may be used to change the above URL to
http://mysite/mycgi.cgi/user?a=delete&id=12
If there is only one module in the application, the URL can be shortened to
http://mysite/mycgi.cgi/delete&id=12
Using HTML Templates
For information about using templates, template tags and template tag parameters to generate response pages, please refer to the fptemplate.txt file under your FPC directory in /packages/fcl-base/texts/.
Example projects that demonstrate using templates can be found under your FPC directory in /packages/fcl-web/examples/fptemplate/ (see the README.txt in there for more). (In earlier versions, these examples were in the Lazarus directory in /components/fpweb/demo/fptemplate/)
Tips & troubleshooting
Sending result codes
To send a different HTTP response than 200 OK, use AResponse.Code and AResponse.CodeText, e.g.
AResponse.Code := 404;
AResponse.CodeText := 'Document not found';
Sending binary data
An approach that seems to work to send e.g. a tiff file from the web server to the client - adapted from $(fpcdirectory)\packages\fcl-web\examples\jsonrpc\demo1\wmdemo.pp - something like:
AResponse.ContentStream := TMemoryStream.Create;
try
AResponse.ContentStream.LoadFromFile('/tmp/sample.tiff');
AResponse.ContentType := 'image/tiff'; //or whatever MIME type you want to send
// to do: there is an fpweb example that gets the mime type from the file extension...
AResponse.ContentLength:=AResponse.ContentStream.Size; //apparently doesn't happen automatically?
AResponse.SendContent;
finally
AResponse.ContentStream.Free;
end;
Handled := true;
Error: Could not determine HTTP module for request
You may have multiple modules and multiple actions. If you specify an URL with only 1 item, like:
http://localhost/cgi-bin/somemodule
then fpweb assumes you're specifying an action. If you don't have a default module set, you will get a 500 internal server error (Could not determine HTTP module for request)
You can modify this behaviour to let fpweb map to a module name instead by setting the application's PreferModuleName property to true.
Error: response code 500 Internal Server error when trying to handle DELETE method
In FPC 2.6.2 and lower, fcl-web does not accept the DELETE method and generates an error.
fpWeb/FCGI and Apache 2.4 (mod_proxy_fcgi mode)
When you want to deploy FCGI application behind Apache 2.4+ reverse proxy, you need PATH_INFO variable in headers that are sent via socket to FCGI daemon.
1, Enable following modules: setenvif_module, proxy_module and proxy_fcgi_module
(on CentOS 7 modules are defined in file /etc/httpd/conf.modules.d/00-base.conf and /etc/httpd/conf.modules.d/00-proxy.conf).
LoadModule setenvif_module modules/mod_setenvif.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
2, Reverse proxy configuration for fpWeb/FCGI listening on port 9000.
$1 = module name
$2 = action name
SetEnvIf Request_URI . proxy-fcgi-pathinfo=full ProxyPassMatch "/fcgi/(.*)/(.*)" "fcgi://127.0.0.1:9000/$1/$2"
fpWeb/FCGI and nginx
For correct routing, fcl-web requires the PATH_INFO variable in headers sent from nginx. For this you have to split the whole URL into FastCGI application name and the path info part whith the configuration statement fastcgi_split_path_info. The statement accepts a regular expression and puts the second match into $fastcgi_path_info configuration variable. This variable can be passed to your application with fastcgi_param statement.
The following configuration example includes a full virtual host for nginx which passes everything from http://myserver:8080/mycgiapp/ to an external FastCGI application.
server { listen 8080; listen [::]:8080; server_name _; root "/var/www"; location /mycgiap/ { # setting up PATH_INFO fastcgi_split_path_info ^(/mycgiapp/)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; # other parameters fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # pass requests to your FastCGI application fastcgi_pass 127.0.0.1:9000; } }
Notes
- The cgiapp unit is deprecated, please use fpcgi as much as possible.
- The fastcgi, custfcgi, and fpfcgi units are not supported on Darwin at this time, because it uses a different mechanism than the MSG_NOSIGNAL option for recv() to indicate that no SIGPIPE should be raised in case the connection breaks. See http://lists.apple.com/archives/macnetworkprog/2002/Dec/msg00091.html for how this should be fixed.
- If you deploy your CGI application and get errors like "Error: No action name and no default action", you should make sure there's an action assigned to the URL, or catch non-supported actions with an action marked Default. In both cases, an OnRequest event handler for the action that sets Handled:=true should be used.
See also
- fpWeb Tutorial
- fphttpclient Part of fcl-web that can be used stand-alone in client applications
- write me!fphttpserver Small stand alone Object Pascal web server. Can be used to serve fcl-web CGI applications.
- CGI_Web_Programming#Debugging_CGI Information about debugging CGI applications
- Part of a tutorial by Leonardo Ramé that shows how to program an fcl-web CGI application; includes example of generating JSON content