fcl-web/es
Template:Web and Networking Programming
Que es fpWeb
fpWeb se puede utilizar para crear aplicaciones cgi . En este punto necesitamos más información: que otra funcionalidad tiene, como es en comparación con otros frameworks/tools, e.g. Brook
Utilizando fpWeb junto con Lazarus
Instalando el paquete weblaz fpWeb para Lazarus
El primer paso a realizar es instalar el paquete que viene en el trayecto lazarus/components/fpweb/weblaz.lpk. Tal como es usual con los paquetes en tiempo de diseño, tendrás que reconstruir Lazarus.
Creando una aplicación CGI
Después de instalar el paquete weblaz se puede crear una aplicación CGI (Common Gateway Interface) muy simple, la cual muestra una página HTML. Para ello vamos a menu "File->New...". Del listado de las posibles aplicaciones seleccionamos "CGI Application" como en la imagen de abajo, lo cual creará un fichero de proyecto principal CGI y un módulo web fpweb.
TFPWebModule permite manipular las propiedades y eventos utilizando el Inspector de Objetos.
Para añadir código para mostrar la página se debe añadir un manejador (handler) de peticiones. Esto se consigue haciendo doble click en la propiedad OnRequest del inspector de objetos, tal como se muestra en la siguiente imagen:
En el manejador de eventos se debe escribir el código HTML que se quiere mostrar en el navegador. Para evitar mezclar Pascal y HTML, se puede cargar esta página del directorio en el que se encuentra el ejecutable CGI mediante el uso de AResponse.Contents.LoadFromFile().
El tipo de respuesta debe establecerse en AResponse.ContentType. Para páginas HTML debe tener el valor 'text/html;charset=utf-8'.
Finalmente, el manejador (handled) debería establecerse a True para indicar que la peticion fue manejada satisfactoriamente y retornar un código (200 OK status) al navegador. Después de añadir este código el módulo web debería mostrarse como:
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.
Diseñando la aplicación CGI
Esta sección asume que se está usando un Servidor Web Apache. Por supuesto también se pueden utilizar otros servidores web que soporten CGI (nginx, cherokee).
Apache se puede descargar desde: http://httpd.apache.org/download.cgi o utilizando el gestor de paquetes de la distribución en uso.
La instalación por defecto de Apache tratará todos los ficheros ubicados en su directorio cgi-bin como programas CGI, por lo que los usuarios no serán capaces de acceder a ficheros en formato plano HTML que se encuentren en el. Este directorio puede establecerse en el fichero httpd.conf dentro de la siguiente sección:
# # ScriptAlias: This controls which directories contain server scripts. # ScriptAliases are essentially the same as Aliases, except that # documents in the target directory are treated as applications and # run by the server when requested rather than as documents sent to the # client. The same rules about trailing "/" apply to ScriptAlias # directives as to Alias. # ScriptAlias /cgi-bin/ "C:/Program Files/Apache Software Foundation/Apache2.2/cgi-bin/"
Si tu situas un ejecutable llamado "mywebpage.cgi" en este directorio, entonces la página será accesible comos http://localhost/cgi-bin/mywebpage.cgi (o remotamente con la dirección IP o nombre de dominio correspondientes).
fcl-web con Lazarus bajo Windows produce ficheros .exe. Para que Apache sirva estos ficheros hay que añadir esto:
AddHandler cgi-script .exe
Y para servir los ejecutables desde otro directorio hay que añadir esto otro:
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>
Formateando el HTML y leyendo campos de consulta (Query)
El ejemplo previo simplemente muestra una página en HTML plano, pero se puede desear por ejemplo:
- Cambiar dinámicamente la salida de la página HTML, y
- En cuanto el navegador lee las variables las pasa a la página web en formato de campos de consulta (en e.g. acciones HTTP GET y HTTP POST).
Una solución simple para este primer problema consiste simplemente en utilizar el formato de rutina estandar de Pascal y añadir %s o %d en el fichero HTML en los lugares apropiados en los que recibirá un valor personalizado.
Reading GET data
To read the GET variables one can use ARequest.QueryFields, which is a TStrings descendent. Each variable will be in a separate line in the TStrings in the format variablename=value, similarly to how they are shown in the browser page address. To search for a specific variable one can use ARequest.QueryFields.Values[], passing the variable name in the brackets to receive its value back, instead of parsing the string manually.
The resulting code:
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;
Reading POST data
Data submitted by POST requests can be obtained from TRequest.Content
. It will come without the request headers - in other words it contains the body of the request.
Submitted form data can also be accessed using TRequest.ContentFields
which is the content parsed as fields separated by & and decoded. For example, the following set of form values:
login: dfg 345&& login_senha: ==== email: dfg
Will be encoded in 'APPLICATION/X-WWW-FORM-URLENCODED' like this (see wikipedia POST (HTTP)):
login=dfg+345%26%26&login_senha=%3D%3D%3D%3D&email=dfg
And will be available in TRequest.ContentFields via the line index or using the Values property, which is more convenient:
TRequest.ContentFields[0]: login=dfg 345&& TRequest.ContentFields[1]: login_senha===== TRequest.ContentFields[2]: email=dfg TRequest.ContentFields.Values['email']: dfg
If using other mime-types than 'MULTIPART/FORM-DATA' and 'APPLICATION/X-WWW-FORM-URLENCODED' (the types supported by HTML forms):
- content is only available in TRequest.Content, not TRequest.ContentFields
- use of these mime-types raises an exception for FPC 2.4.2 (fixed in FPC 2.5+).
Note that HTTP POST can also send Query fields (in the URL), e.g. http://server/bla?question=how, and those are accessed by TRequest.QueryFields as explained in the previous section.
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
var
lData: String;
begin
lData := ARequest.Content;
Reading POST binary data
To receive data on the server that has been POSTed e.g. as multipart/form-data, use something like this:
procedure TMainWebModule.TFPWebActions2Request(Sender: TObject;
ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
var
i: Integer;
begin
// Process all received files
for i := 0 to ARequest.Files.Count - 1 do
begin
// Doing something else than writeln is highly recommended ;)
writeln ('Received Filename: '+ARequest.Files[i].LocalFileName);
end;
Handled := true;
end;
The client can send data using e.g. FileFormPost.
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:
var
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"
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
- 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