fcl-web/es

From Lazarus wiki
Jump to navigationJump to search

English (en) español (es) français (fr) русский (ru)

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.

New cgi.PNG


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:

Webmodule.PNG


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.

Leyendo datos GET

Para leer las variables GET se puede utilizar ARequest.QueryFields, que es un descendiente de TStrings. Cada variable estará en una línea separada del TStrings en el formato variablename=value, de manera similar a como se muestran en la línea de direcciones del navegador. Para buscar por una variable específica se puede utilizar ARequest.QueryFields.Values[], pasando el nombre de la variable entre corchetes para recibir de vuelta su valor, en lugar de pasar la cadena manualmente.

El código resultante es:

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;

Leyendo datos POST

Los datos enviados por solicitueds POST se puede obtener de TRequest.Content. Nos retornará sin las cabeceras de requerimiento - en otras palabras contendrá el cuerpo (BODY) del requerimiento.

Los datos enviados por el formulario también pueden ser accedidos mediante TRequest.ContentFields el cual es el contenido pasado como campos separados por & y decodificados. Por ejemplo, el siguiente conjunto de valores de formularo:

login: dfg 345&&
login_senha: ====
email: dfg

Se codificará en 'APPLICATION/X-WWW-FORM-URLENCODED' tal como esto (Ver wikipedia POST (HTTP)):

login=dfg+345%26%26&login_senha=%3D%3D%3D%3D&email=dfg

Y que estará disponible en TRequest.ContentFields a través del indice de línea o utilizando la propiedad Values, que es más conveniente:

TRequest.ContentFields[0]: login=dfg 345&&
TRequest.ContentFields[1]: login_senha=====
TRequest.ContentFields[2]: email=dfg
TRequest.ContentFields.Values['email']: dfg

Si se utilizan otros tipos MIME distintos de 'MULTIPART/FORM-DATA' y 'APPLICATION/X-WWW-FORM-URLENCODED' (los tipos soportados por formularios HTML):

  • El contenido estará solamente disponible para TRequest.Content, no para TRequest.ContentFields.
  • El uso de estos tipos MIME genera una excepción para FPC 2.4.2 (solucionado en FPC 2.5+).

Ten en cuenta que HTTP POST puede además enviar campos de consulta (en la URL), e.g. http://server/bla?question=how, y estos son accesible mediante TRequest.QueryFields tal como se explicó en la sección previa.

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
  AResponse: TResponse; var Handled: Boolean);
var
  lData: String;
begin
  lData := ARequest.Content;

Leyendo datos binarios POST

Para recibir datos en el servidor que ha sido posteado (POSTed), e.g. multipart/form-data, utiliza algo como esto:

procedure TMainWebModule.TFPWebActions2Request(Sender: TObject;
  ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
var
  i: Integer;
begin  
  // Procesa todos los ficheros recibidos.
  for i := 0 to ARequest.Files.Count - 1 do
  begin
    // Doing something else than writeln is highly recommended ;)
    writeln ('Nombre de fichero recibido: '+ARequest.Files[i].LocalFileName);
  end;
      
  Handled := true;
end;

El cliente puede enviar datos utilizando e.g. FileFormPost.

Utilizando múltiples módulos

Si solamente hay un módulo en la aplicación web entonces todos los requerimientos se dirigirán a dicho módulo.

Según tu aplicación web vaya creciendo, probablemente será necesario utilizar más módulos. Para esto se pueden añadir nuevos módulos mediante 'File - New' y después 'Web module' o 'HTML Web Module'.

FCL-web utiliza la URL para determinar como será manejado el requerimiento HTTP. Debe conocer de antemano que módulos web existen en la aplicación. Para lograr esto, cada módulo debe ser registrado.

Cada módulo se registra con fcl-web en la sección de inicialización de la unidad que está definida en:

RegisterHTTPModule('location', TMyModule);

Entonces el módulo será invocado si se utiliza una URL de la forma

http://www.mysite.org/mycgi.cgi/location

o

http://www.mysite.org/mycgi.cgi?module=location

Si están presentes múltipls módulos entonces debe aparecer el nombre del módulo en la URL, caso contrario se generará un error.

Este comportamiento puede también forzarse por parte de las aplicaciones que solamente tienen un módulo mediante el establecimiento de la propiedad AllowDefaultModule de la aplicación.

Application.AllowDefaultModule := False;

En este caso, la aplicación fcl-web requerirá siempre el nombre del módulo en la URL.

El nombre de la variable requerida que determina el nombre del módulo (por defecto, este es 'module') se puede establecer en la propiedad Application.ModuleVariable. El siguiente código:

Application.ModuleVariable := 'm';

asegura que la siguiente dirección es dirigida a TMyModule:

http://www.mysite.org/mycgi.cgi?m=location

Si todo esto no es suficiente para determnar el módulo al cual se debe pasar el requerimiento, se puede utilizar el evento Application.OnGetModule. Es del tipo TGetModuleEvent:

type

  TGetModuleEvent = Procedure (Sender : TObject; ARequest : TRequest;
                               Var ModuleClass : TCustomHTTPModuleClass) of object;

Crear un manejador de evento para este evento permite afinar el control sobre el módulo que se crea para manejar el requerimiento: el requerimiento (pasado en ARequest) se puede examinar, y la variable 'ModuleClass' se debe establecer a la clase del módulo que deba manejar el requerimiento.

Si 'ModuleClass' vale 'Nil' al ser retornada, entonces se enviará un error al navegador.

Utilizando acciones (Actions)

Un módulo puede utlizarse para agrupar cierto tipo de acciones que lógicamente permanecen unidas. Imagina un módulo TUserModule que sea utilizado para manejar la gestión de usuario en páginas web. Puede haber múltiples acciones asociadas con un usuaro:

  • Creación.
  • Eliminación.
  • Edición.
  • Presentación.

Estas distintas acciones se pueden manejar mediante un mismo módulo. Uno puede determinar la acción desde la URL manualmente, como en:

http://mysite/mycgi.cgi/user?action=delete&id=12

Esto se puede automatizar.

En orden a hacer más fácil el distinguir entre varias acciones, el módulo tiene una propiedad actions: es una colección en la que cada elemento se asocia con una respuesta distinta para el requerimiento. Estas acciones tienen varias propiedades:

Name
El nombre de la acción. Examinará la URL para determinar el nombre de la acción.
Content
Una cadena (string). Si se establece entonces se enviará al visor web (navegador).
Contents
Un stringlist. Si se establece también se enviará al visor web (navegador).
ContentProducer
Si se establece hará que el contentproducer maneje el requerimiento.
Default
Si se establece a 'True' entonces esta será la acción por defecto. Esto significa que si FCL-Web no puede determinar el nombre de la acción entonces se ejecutará esta acción.
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'.

La propiedad 'ActionVar' del módulo puede utilizarse para estableceer el nombre del la variable de requerimiento a usar. Estableciendo

UserModule.ActionVar := 'a';

puede utilizarse para cambiar esa URL a

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

Enviando códigos de resultado

Para enviar una respuesta HTTP distinta de 200 OK, utiliza AResponse.Code y AResponse.CodeText, e.g.

AResponse.Code:=404;
AResponse.CodeText:='Documento no encontrado';

Enviando datos binarios

Una aproximación que parece funcionar para enviar pongamos por caso un fichero de imagen tiff desde el servidor al cliente - adaptado desde $(fpcdirectory)\packages\fcl-web\examples\jsonrpc\demo1\wmdemo.pp - es algo así como:


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.
  • 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