Difference between revisions of "fpWeb Tutorial"

From Lazarus wiki
Jump to navigationJump to search
(added link to github site)
Line 1: Line 1:
 
{{fpWeb Tutorial}}
 
{{fpWeb Tutorial}}
  
Originally based on: fcl-web tutorial (in PDF) by forum user [https://forum.lazarus.freepascal.org/index.php?action=profile;u=7523 Leledumbo].
+
Originalmente basado en: tutorial de fcl-web (en PDF) por un usuario del foro [https://forum.lazarus.freepascal.org/index.php?action=profile;u=7523 Leledumbo].
  
== Introduction ==
+
== Introducción ==
  
fpWeb is a web application framework shipped by FPC in its default distribution as a part of fcl-web
+
fpWeb es un esquema de aplicación web que viene con FPC por defecto como parte del paquete fcl-web. El esquema en sí mismo está construido encima de las características de fcl-web. El esquema está construido con mentalidad de RAD para hacer buen uso de la componentización en producir contenido dinámico. Un paquete Lazarus es provisto que pueda usar el esquema en forma de arrastrar y soltar para su administración de sesión y producción de contenido.
package. The framework itself is built on top of fcl-web features. The framework is built with RAD mind to
 
make a good use of componentization in producing dynamic content. A Lazarus package is provided that
 
can use the framework in a drag n drop manner for its session management and content production.
 
This tutorial will attempt to cover basic functionality of fpWeb, so that one can build common web
 
application with it. Note that this tutorial does '''NOT''' attempt to teach HTTP protocol, HTML, CSS, JavaScript
 
or database manipulation as the protocol and client languages should be the prerequisites for every web
 
application programmer and database manipulation does not differ from desktop implementation.
 
  
== Architecture (PLEASE read) ==
+
Este tutorial intentará cubrir la funcionalidad básica de fpWeb, así que uno puede construir una aplicación común para web con él.  Note que este tutorial '''NO''' intenta enseñar ningún protocolo HTTP, HTML, CSS, JavaScript o manipulación de base de datos como los lenguajes de protocolo y cliente debieran ser los prerequisitos para cada programador de aplicaciones web y la manipulación de base de datos no difiere de la implementación de escritorio.
  
Before starting, it's necessary to know the architecture and application flow to avoid confusion when certain
+
== Arquitectura (lea POR FAVOR) ==
things don't work or work unexpectedly. So please spend some time reading this section.
 
  
=== Application ===
+
Antes de iniciar, es necesario conocer la arquitectura y flujo de aplicación para evitar confusiones cuando ciertas cosas no funcionen o lo hagan de forma inesperada.  Así que por favor pase algo de tiempo leyendo esta sección.
  
Application here refers to the protocol that your app will implement. fpWeb will happily switch from CGI,
+
=== Aplicación ===
FCGI, Apache module to embedded server, and more if fcl-web implements another one in the future. Each
+
 
application is implemented in its own unit, so to switch from one application to another, with the exception
+
La aplicación aquí se refiere al protocolo que su aplicación implementará. fpWeb felizmente se cambia de módulos CGI, FCGI, Apache a un servidor embebido, y más si fcl-web implementa otro en el futuro. Cada aplicación se implementa en su propia unidad, así que para cambiar de una aplicación a otra, con la excepción del módulo Apache, sólo se necesita cambiar el respectivo identificador en la cláusula uses. Para 3.0.0 en adelante son:
of Apache module, one just needs to change the respective identifier in the uses clause. Currently (as of
 
3.0.0 / 3.1.1), they are:
 
  
 
* fpCGI -> CGI
 
* fpCGI -> CGI
 
* fpFCGI -> FastCGI
 
* fpFCGI -> FastCGI
* fpApache (requires httpd as well) -> Apache module
+
* fpApache (requiere httpd también) -> módulo Apache
* fpHttpApp -> embedded server
+
* fpHttpApp -> servidor embebido
* microhttpapp -> embedded server using GNU libmicrohttp library.
+
* microhttpapp -> servidor embebido usando la biblioteca GNU libmicrohttp.
* fphttpsys -> Windows system support for HTTP protocol.
+
* fphttpsys -> soporte de sistema Windows para el protocolo HTTP.
 +
 
 +
A través de este tutorial, usaremos el servidor embebido en beneficio de simplicidad porque no tiene que tratar con configurar el servidor virtual ni enredarse con el manejo complicado de archivos y servicios.  Su aplicación será una aplicación portátil binaria para web!  Otra razón sería que hay más de una aplicación de servidor web y que cada una tiene una forma distinta de configurar.  Sería exagerado cubrir todos mientras que su documentación hace el trabajo.
 +
El módulo Apache se implementa como una biblioteca (dinámica), mientras que otros protocolos son aplicaciones normales.  Cada aplicación puede que tenga propiedades específicas (como puertos) disponibles y que sólo competen a esa aplicación.  Eso es el porqué de ver los ejemplos fcl-web, pares de .lpi / .lpr para cada protocolo están puestos en sus propios directorios, sólo los módulos web se comparten.
  
Throughout this tutorial, we will use embedded server for the sake of simplicity because you don't have to
 
deal with setting up virtual server and messing up with complicated configuration file and service
 
management. Your app will be a single binary portable web application! Another reason would be there are
 
more than just one web server application out there and each has different way to configure. It would be
 
overkill to cover all of them while their documentation already does the job.
 
Apache module is implemented as a (dynamic) library, while other protocols are normal application. Each
 
application may have specific properties (such as port) available and meaningful only to that application.
 
That's why if you look at fcl-web examples, .lpi / .lpr pairs for each protocol are put in their own directories,
 
only the web modules are shared.
 
  
=== Web Modules ===
+
=== Módulos Web ===
  
 
[[File:fpWeb-overview.png|thumb]]
 
[[File:fpWeb-overview.png|thumb]]
  
fpWeb applications consist of web modules which do the actual content production. A web module can
+
Las aplicaciones fpWeb constan de módulos web los que hacen la producción actual de contenido. Un módulo web puede contener acciones web que pueden dividir la funcionalidad incluso más. Por ejemplo, un módulo web auténtico puede que tenga acciones web de login y logout. Mientras que un módulo web de acerca de, puede no necesitar una acción específica y sirve sólo para un contenido. El módulo Web se integra con fpTemplate que puede usarse para producir contenido dinámico con una plantilla. Esto es bastante similar a lo que hace PHP, sólo el espacio entre la lógica y la presentación se impone en vez de lo que se sugiere. Algunos dicen que fpTemplate implementa una vista pasiva mientras que PHP por defecto implementa un patrón de diseño de vista activa.
contain web actions that can divide the functionality even more. For instance, an auth web module
+
 
might have login and logout web actions. While an about web module might not need an action at all and
 
serves just one content. Web module is integrated with fpTemplate that can be used for producing dynamic
 
content from a template file. This is roughly similar to what PHP does, only the gap between logic and
 
presentation is forced rather than suggested. Some say that fpTemplate implements passive view while PHP
 
by default implements an active view design pattern.
 
  
== Installation ==
+
== Instalación ==
  
The fpWeb package for Lazarus is not installed by default (but it's shipped), to enable fpWeb:
+
El paquete fpWeb para Lazarus no se instala por defecto (pero viene con él), para activar fpWeb:
# Open Lazarus and choose '''Package->Install/Uninstall Package'''
+
# Abra Lazarus y escoja '''Paquetes->Instalar/Desinstalar Paquetes’’’
# In the Available for installation listbox, look for '''weblaz''' and press '''Install selection'''. Press '''Save and rebuild IDE''' and confirm with '''Continue'''
+
# En el listbox disponible para la instalación, busque '''weblaz''' y presione '''Instala selección'''. Presione '''Guardar y reconstruir IDE''' y confirme con '''Continuar'''
# Let the IDE rebuild and restart itself. If everything goes well, you should now have fpWeb tab in the component palette, as shown below:
+
# Deje que el IDE se reconstruya y reincide solo. Si todo va bien, deberá ahora tener una pestaña ftweb en la paleta de componentes, como se muestra:
 
::[[File:fpwebtutorial_inst.png|Installed weblaz package]]
 
::[[File:fpwebtutorial_inst.png|Installed weblaz package]]
  
== Specialized modules ==
+
== Módulos Especializados ==
The *TFPWebModule* class (used below) is a simple example of a fpWEB module that can be used for all kinds of HTTP requests.
+
La clase *TFPWebModule* (usada abajo) es un simple ejemplo del módulo fpWEB que puede usarse para toda clase de peticiones HTTP.
  
However, fpWEB comes with some specialized modules, that have extra support for specialized tasks:
+
Sin embargo, fpWEB viene con algunos módulos especializados que tienen soporte extra para tareas especializadas:
  
* The '''TSimpleFileModule''' class in unit ''fpwebfile.pp'' can be used to send files. You point it to a directory, and it does the rest.
+
* La clase '''TSimpleFileModule''' en la unidad ''fpwebfile.pp'' puede usarse para enviar archivos. Sólo apunte a un directorio, y él hace el resto.
* The '''TFPHTMLModule''' class in unit ''fphtml.pp'' can be used to produce HTML.
+
* La clase '''TFPHTMLModule''' en la unidad ''fphtml.pp'' puede usarse para producir HTML.
* The '''TProxyWebModule''' class in unit ''fpwebproxy.pp'' is a ready-made forwarding proxy.  
+
* La clase '''TProxyWebModule''' en la unidad ''fpwebproxy.pp'' es un proxy de redirección listo para usar.  
* The '''TFPWebProviderDataModule''' class in unit ''fpwebdata.pp'' serves data in JSON format that can be consumed by ExtJS stores.
+
* La clase '''TFPWebProviderDataModule''' en la unidad ''fpwebdata.pp'' sirve de datos en formato JSON que puede guardar ExtJS.
* The '''TSQLDBRestModule''' class in unit ''sqldbrestmodule.pp'' implements a full REST server backed by SQLDB. See more info in [[SQLDBRestBridge]].
+
* La clase '''TSQLDBRestModule''' en la unidad ''sqldbrestmodule.pp'' implementa un servidor REST completo respaldado por SQLDB. Vea más información en [[SQLDBRestBridge]].
* The '''TJSONRPCModule''' class in unit ''webjsonrpc.pp'' implements a JSON-RPC service.
+
* La clase '''TJSONRPCModule''' en la unidad ''webjsonrpc.pp'' implementa un servicio JSON-RPC.
* The '''TExtDirectModule''' class in unit ''fpextdirect.pp'' implements a Ext.Direct variant of a JSON-RPC service.
+
* La clase '''TExtDirectModule''' en la unidad ''fpextdirect.pp'' implementa una variante Ext.Direct de un servicio JSON-RPC.
  
== Hello, World! ==
+
== Hola, Mundo! ==
  
Let's create a simple Web Application. As commonly taught when learning programming, "Hello, World!" will be our first app.<br /><br />
+
Creemos una aplicación Web sencilla. Como se enseñó comúnmente cuando se aprendía programación, "Hola, Mundo!" será nuestra primera app.<br /><br />
  
1. Open up Lazarus and choose '''Project->New Project''' then pick '''HTTP server Application'''
+
1. Abra Lazarus y escoja '''Proyecto->Nuevo Proyecto''' luego '''HTTP server Application'''
 
:[[File:fpwebtutorial_create_app.png|Create new HTTP server application]]<br />
 
:[[File:fpwebtutorial_create_app.png|Create new HTTP server application]]<br />
  
2. Another dialog shall appear for serving static files, port selection and multithreading. Just use default port 8080.
+
2. Otro diálogo deberá aparecer para servir archivos estáticos, selección de puerto y multihilo. Sólo use por defecto el puerto 8080.
  
:<syntaxhighlight lang=pascal>You may skip the static files serving (go to tips and tricks section if you want to know it more).</syntaxhighlight>
+
:<syntaxhighlight lang=pascal>Puede que omita servir archivos estáticos (vaya a la sección de tips and tricks si quiere saber más).</syntaxhighlight>
 
: [[Image:fpwebtutorial_port_selection.PNG|Static files, port selection and multithreading options]]
 
: [[Image:fpwebtutorial_port_selection.PNG|Static files, port selection and multithreading options]]
 
<br />
 
<br />
:'''IMPORTANT!:'''
+
:'''IMPORTANTE!:'''
:If you choose to use threads on *nix, don't forget to add cthreads as the first unit in the .lpr's uses clause, otherwise a RTE 232 will be generated. When running from console, a message shall appear:
+
:Si escogió usar hilos en *nix, no olvide agregar cthreads como la primera unidad en la cláusula uses del archivo .lpr, de otra forma se generará un error RTE 232. Cuando se ejecuta desde una consola, un mensaje deberá aparecer:
:<syntaxhighlight lang=pascal>This binary has no thread support compiled in. Recompile the application with a thread-driver in the program uses clause before other units using thread.</syntaxhighlight>
+
:<syntaxhighlight lang=pascal>Este binario no se compiló con soporte de hilos. Recompile la aplicación con un manejado de hilasen la cláusula uses del programa antes de cualquier unidad que use hilos.</syntaxhighlight>
 
<br />
 
<br />
3. Since Jan 14, 2017 (or FPC 3.0.4), you may need to open the .lpr and add the following line in the main body if it's not already there:
+
3. Desde Enero 14 de 2017 (o FPC 3.0.4), pueda que necesite abrir el archivo .lpr y agregar la siguiente línea en el cuerpo principal si no está puesto:
 
:<syntaxhighlight lang=pascal>Application.LegacyRouting := true;</syntaxhighlight>
 
:<syntaxhighlight lang=pascal>Application.LegacyRouting := true;</syntaxhighlight>
:reason will be explained in chapter [[#Routing]].
+
:la razón estará explicada en el capítulo [[#Enrutamiento]].
 
<br />
 
<br />
4. Whatever you choose, pick "OK" and you'll be presented in the default one module fpWeb app. <br />
+
4. Cualquier cosa que escoja, clique "OK" y se le presentará en el módulo de aplicación fpWeb por defecto. <br />
  
5. Focus the module and move to '''Object Inspector'''. Feel free to rename the module if you wish.<br />
+
5. Enfoque el módulo y muévase al '''Inspector de Objetos'''. Siéntase libre de renombre el módulo si así lo prefiere.<br />
  
6. Choose '''Events''' tab and click the button at the right of the second column of the '''OnRequest''' row to create the event handler.
+
6. Escoja la pestaña '''Events''' y clique el botón a la derecha de la segunda columna de la fila '''OnRequest''' para crear el manejador del evento.
 
:[[File:fpwebtutorial_on_request.png|Creating web module's OnRequest handler in the object inspector]]<br />
 
:[[File:fpwebtutorial_on_request.png|Creating web module's OnRequest handler in the object inspector]]<br />
  
:You will be redirected to the source editor with the following code:
+
:Será dirigido al editor de código fuente con el código:
 
:<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
:<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
AResponse: TResponse; var Handled: Boolean);
 
AResponse: TResponse; var Handled: Boolean);
Line 111: Line 90:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
:Fill in the event with:
+
:Escriba:
  
 
:<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
:<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
AResponse: TResponse; var Handled: Boolean);
 
AResponse: TResponse; var Handled: Boolean);
 
begin
 
begin
   AResponse.Content := 'Hello, World!';
+
   AResponse.Content := 'Hola, Mundo!';
 
   Handled := true;
 
   Handled := true;
 
end;</syntaxhighlight><br />
 
end;</syntaxhighlight><br />
  
7. Then run your app (or press F9).<br />
+
7. Luego ejecute su aplicación (o presione F9).<br />
  
8. Open your browser and type:
+
8. Abra su navegador y escriba:
 
:  http://localhost:8080/ <br />
 
:  http://localhost:8080/ <br />
9. You should see "Hello, World!" displayed.<br />
+
9. Deberá ver "Hola, Mundo!'.<br />
 
<br />
 
<br />
  
If it doesn't, check below:
+
En caso contrario, revise:
  
* The framework does a lot of exception handling and the IDE's debugger might catch them and interrupts your app. It's OK to add most of the exceptions to the ignore list so you can concentrate more on your app flow. Keep skipping and continue until no more dialog appears and the browser shows the output.
+
* El marco de trabajo hace mucho de manejo de excepciones y el depurador del IDE podrá atraparlos e interrumpe su aplicaciónEstá bien agregar muchas de las excepciones para la lista del olvido así que se pueda concentrar más en su flujo de aplicación. Manténgase evitando y continuando hasta que no aparezcan más diálogos y el navegador muestre el contenido.
  
* '''Handled := true''' is the way we tell the framework that the request has been handled. Not setting it (or setting it to '''false''') will show error page instead. For now, this doesn't affect the request flow yet, but it will be later on. So keep it that way until the time comes to further make a good use of it.
+
* '''Handled := true''' es la forma que le decimos que la solicitud ha sido manejada. Si no se coloca (o se pone en  '''false''') mostrará la página de error. Por ahora, esto no afecta el flujo de peticiones, pero lo hará después. Así que déjelo así hasta que el momento en que se le de buen uso.
  
* other track: test without a firewall loaded in RAM (as an application, as a service or deamon, or as both).
+
* otra pista: pruebe sin un cortafuegos cargado en RAM (como una aplicación, como un servicio o daemon, o ambos).
  
== Reading GET & POST data ==
 
  
A dynamic content is likely to be triggered from user input, either through forms, providing values in the
+
== Leyendo los datos GET & POST ==
URL, etc. Those data are sent along the request, which is represented in the method as '''ARequest'''
 
parameter of type '''TRequest'''.
 
  
=== Reading GET ===
+
Es probable que un contenido dinámico se active a partir de la entrada del usuario, tanto a través de formularios, proveyendo valores en el URL, etc.  Esos datos se envían a través de la petición, la cual se representa en el método como '''ARequest''' parámetro de tipo '''TRequest'''.
  
GET data is provided as '''ARequest.QueryFields''' , which is a '''TStrings''' descendant. In short, whatever you
+
=== Leyendo GET ===
usually do with TStrings, is applicable here such as accessing the data in a map style through the '''Values'''
 
property.
 
  
Reusing above code, replace the method body with:
+
Los datos GET se proveen como '''ARequest.QueryFields''' , el cual desciende de '''TStrings'''. Resumiendo, cualquier cosa que haga con TStrings, se aplica aquí como acceder a los datos en un estilo de mapa a través de la propiedad '''Values'''.
  
 +
Reusando el código anterior, reemplace el cuerpo del método con:
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
  AResponse: TResponse; var Handled: Boolean);
 
  AResponse: TResponse; var Handled: Boolean);
Line 154: Line 129:
 
   LName: String;
 
   LName: String;
 
begin
 
begin
   LName := ARequest.QueryFields.Values['Name'];
+
   LName := ARequest.QueryFields.Values['Name']; // QueryFields para GET
 
   if LName = EmptyStr then
 
   if LName = EmptyStr then
     with AResponse.Contents do
+
     with AResponse.Contents do begin       // En el formulario <form> el
    begin
+
       Add('<form action="%s" method="GET"', [aRequest.URI]);// solicitante
       Add('<form action="' + ARequest.URI + '" method="GET"');
+
       Add('<label for="name">Por favor dame tu nombre:</label>');// pondrá
       Add('<label for="name">Please tell me your name:</label>');
+
       Add('<input type="text" name="name" id="name" />');// name="name"
       Add('<input type="text" name="name" id="name" />');
 
 
       Add('<input type="submit" value="Send" />');
 
       Add('<input type="submit" value="Send" />');
 
       Add('</form>');
 
       Add('</form>');
 
     end
 
     end
 
   else
 
   else
     AResponse.Content := 'Hello, ' + LName + '!';
+
     AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
   Handled := true;
+
   Handled := true; // <— Se dio buen manejo!
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
'''ARequest.URI''' is just a convenience to refer to the current URI, so even when you change your registered
+
'''ARequest.URI''' es sólo una conveniencia referirse al URI actual, así que cuando cambie su módulo registrado, este código permanece igual.
module or action name, this code stays the same.
 
  
Note that as in Pascal, referring to the data is done case insensitively.
+
Note que como en Pascal, nos referimos a los datos se hace sin importar las mayúsculas/minúsculas.
  
Now you can try requesting /, which will display
+
Como es una petición GET, en la dirección de la página aparece la sintaxis de lo solicitado.  Ahora puede intentar la solicitud /, la cual mostrará
  Please tell me your name
 
and /?name=<write anything here, e.g.: Bob>, which will display
 
  Hello, Bob!
 
  
=== Reading POST ===
+
  Por favor dame tu nombre:
 +
y al final de la dirección coloque /?name=<escriba algo aquí, p.e.: Fernando>, lo que mostrará
 +
  Hola, Fernando!
  
POST is actually doesn't differ much from GET, only differs in which property to access. If GET is accessed
+
=== Leyendo POST ===
through '''ARequest.QueryFields''' , POST is accessed through '''ARequest.ContentFields'''. POST style of
 
previous code is:
 
  
 +
POST no difiere mucho de GET, sólo en la propiedad que accede.  Si GET se acceda a través de '''ARequest.QueryFields''', POST se accede a través de '''ARequest.ContentFields'''.  El estilo de POST del código previo es:
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
  AResponse: TResponse; var Handled: Boolean);
 
  AResponse: TResponse; var Handled: Boolean);
Line 190: Line 161:
 
   LName: String;
 
   LName: String;
 
begin
 
begin
   LName := ARequest.ContentFields.Values['Name'];
+
   LName := ARequest.ContentFields.Values['Name']; // ContentFields para POST
 
   if LName = EmptyStr then
 
   if LName = EmptyStr then
 
     with AResponse.Contents do
 
     with AResponse.Contents do
 
     begin
 
     begin
       Add('<form action="' + ARequest.URI + '" method="POST"');
+
       Add('<form action="%d" method="POST"', [ARequest.URI]);
       Add('<label for="name">Please tell me your name:</label>');
+
       Add('<label for="name">Por favor dame tu nombre:</label>');
 
       Add('<input type="text" name="name" id="name" />');
 
       Add('<input type="text" name="name" id="name" />');
 
       Add('<input type="submit" value="Send" />');
 
       Add('<input type="submit" value="Send" />');
Line 201: Line 172:
 
     end
 
     end
 
   else
 
   else
     AResponse.Content := 'Hello, ' + LName + '!';
+
     AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
 
   Handled := true;
 
   Handled := true;
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Reading File Uploads ===
+
=== Leyendo Subir Archivos ===
  
One exception is for reading '''multipart/form-data''' fields, i.e. files. That one is available in
+
Una excepción es para los archivos de lectura '''multipart/form-data''', p.e. los archivos. Eso está disponible en
'''ARequest.Files''' as a '''TUploadedFiles''' instance, which is a '''TCollection''' descendant. The following is
+
'''ARequest.Files''' como una instancia de '''TUploadedFiles''', la cual desciende de '''TCollection'''. Lo siguiente es la interfaz pública TUploadedFiles la cual puede usarse para acceder a los archivos:
TUploadedFiles public interface which you can use to access the files:
 
  
 
<syntaxhighlight lang=pascal>TUploadedFiles = Class(TCollection)
 
<syntaxhighlight lang=pascal>TUploadedFiles = Class(TCollection)
...
+
    ·
 +
    ·
 +
    ·
 
public
 
public
 
   Function First: TUploadedFile;
 
   Function First: TUploadedFile;
Line 223: Line 195:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
Each '''TUploadedFile''' itself has several properties:
+
// Cada '''TUploadedFile''' por sí mismo tiene varias propiedades:
  
 
<syntaxhighlight lang=pascal>TUploadedFile = Class(TCollectionItem)
 
<syntaxhighlight lang=pascal>TUploadedFile = Class(TCollectionItem)
...
+
    ·
 +
    ·
 +
    ·
 
Public
 
Public
 
   Destructor Destroy; override;
 
   Destructor Destroy; override;
Line 240: Line 214:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
They should be descriptive enough, with the exception of '''FileName''' and '''LocalFileName'''. '''FileName''' is
+
Debería ser lo suficientemente descriptivo, con la excepción de '''FileName''' y '''LocalFileName'''. '''FileName''' es el archivo original '''name''' como se subió desde el cliente, '''LocalFileName''' es la '''ruta''' del archivo en el servidor donde el archivo se guarda de forma temporal. Note la diferencia en los términos en negrita antes descritos.
the original file '''name''' as uploaded from client, '''LocalFileName''' is the file '''path''' in the server where the file
 
is temporarily stored. Note the difference in bold terms above.
 
  
Again, reusing the same request handler:
+
Nuevamente, reusemos el mismo manejador de petición, nótese que se puede optimizar el código usando una constante Req:
  
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
  AResponse: TResponse; var Handled: Boolean);
 
  AResponse: TResponse; var Handled: Boolean);
 +
const
 +
  Req = '<form id="form" action="%d" method=POST enctype="multipart' +
 +
'/form-data"><label for="name">Drag n drop or click to' +
 +
' add file:</label><input type="file" name="input" />' +
 +
'<input type="submit" value="Send" /></form>';
 
var
 
var
   n: Integer;
+
   n, i: Integer;
 
   f: TUploadedFile;
 
   f: TUploadedFile;
  i: Integer;
 
 
begin
 
begin
 
   n := ARequest.Files.Count;
 
   n := ARequest.Files.Count;
 
   if n = 0 then
 
   if n = 0 then
     with AResponse.Contents do
+
     with AResponse.Contents do  
    begin
+
       Add(Req, [ARequest.URI])
       Add('<form id="form" action="' + ARequest.URI + '" method="POST" enctype="multipart/form-data">');
+
   else begin
      Add('<label for="name">Drag n drop or click to add file:</label>');
 
      Add('<input type="file" name="input" />');
 
      Add('<input type="submit" value="Send" />');
 
      Add('</form>');
 
    end
 
   else
 
  begin
 
 
     f := ARequest.Files[0];
 
     f := ARequest.Files[0];
 
     AResponse.Contents.LoadFromStream(f.Stream);
 
     AResponse.Contents.LoadFromStream(f.Stream);
Line 271: Line 240:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
drag n drop a file (preferably text, as it's will be rendered as text) to the input file field (or click the respective
+
arrastrar y soltar un archivo (preferiblemente texto, tal si fuese presentado como texto) en el campo de entrada de archivo (o clique el botón respectivo) entonces clique en el botón '''Enviar'''. El contenido de archivo deberá mostrarse.
button) then click '''Send''' button. The file content should be displayed.
 
  
 
== Cookies ==
 
== Cookies ==
  
=== Setting ===
+
=== Configuración ===
 
 
[[File:cookie session Object Pascal.png|450px|The "cookie" concept, invented by Netscape in 1994, to allow the HTTP server to identify all its clients.]]<br>
 
  
Cookies are browser responsibility to save and keep, therefore server need to send it as part of the
+
[[File:cookie session Object Pascal.png|450px|El concepto de "cookie" (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes.]]<br>
response in order to set one. '''AResponse.Cookies''' contains a list of cookies to be sent. It's a descendant of
 
'''TCollection''', respectively the contained '''TCookie''' is a descendant of '''TCollectionItem'''. Therefore, you
 
can use TCollection way of managing items to manipulate it.
 
  
Here's an example:
+
El guardar y mantener las cookies son responsabilidad del navegador, por tanto el servidor necesita enviarlo como parte de la respuesta para mantener una.  '''AResponse.Cookies''' contiene una lista de cookies a enviarse.  Es descendiente de '''TCollection''', respectivamente el '''TCookie''' contenido es descendiente de '''TCollectionItem'''.  Por lo tanto, puede usar TCollection en la forma de manejar items para manipularlo.
  
 +
Aquí un ejemplo:
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
  AResponse: TResponse; var Handled: Boolean);
 
  AResponse: TResponse; var Handled: Boolean);
Line 293: Line 257:
 
begin
 
begin
 
   C := AResponse.Cookies.Add;
 
   C := AResponse.Cookies.Add;
   C.Name := 'mycookie';
+
   C.Name := 'miCookie';
   C.Value := 'somevalue';
+
   C.Value := 'algunValor';
 
   Handled := true;
 
   Handled := true;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
You won't see any output in your browser. But if you use some kind of developer tools (Chrome has one
+
No verá ninguna salida en su navegador. Pero si usa alguna herramienta de navegador (Chrome y Brave tienen una incorporada), puede ver el encabezado de la respuesta:
built-in), you can see the response header:
+
 
 +
[[File:fpwebtutorial_cookie_set.png|Encabezado de respuesta puesta de una Cookie response en las herramientas de desarrollador de Chrome's]]
  
[[File:fpwebtutorial_cookie_set.png|Set-Cookie response header in Chrome's developer tools]]
+
Note que la cookie tiene atributos, así que Name y Value no son las únicas cosas que pueda poner.  Navegue por la interfaz de TCookie para ver qué propiedades soporta.
  
Note that cookie has attributes, so Name and Value is not the only two you can set. Browse TCookie
+
=== Obteniéndola ===
interface to see what properties are supported.
 
  
=== Getting ===
+
Una vez se da el encabezado '''Set-Cookie''' arriba, la subsecuente petición a su sitio contendrá un encabezado adicional que contiene el valor que solicitó previamente poner:
  
Once you give '''Set-Cookie''' header above, subsequent request to your site will contain additional header
 
containing the value you ask to set previously:
 
  
[[File:fpwebtutorial_cookie_get.png|Cookie request header in Chrome's developer tools]]
+
[[File:fpwebtutorial_cookie_get.png|Encabezado de petición de una Cookie en las herramientas de desarrollo de Chrome's]]
  
Fortunately, the way to read it is no different from GET & POST data. The related property is
+
Afortunadamente, la forma de leerla no es distinta de los datos GET o POST. La propiedad relacionada es '''ARequest.CookieFields'''. Para leer una cookie previamente puesta:
'''ARequest.CookieFields'''. To read previously set cookie:
 
  
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 
<syntaxhighlight lang=pascal>procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
Line 323: Line 284:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
== Sessions ==
+
== Sesiones ==
  
TFPWebModule is a descendant of TSessionHTTPModule, so it has session management capability. Session
+
TFPWebModule es un descendiente de TSessionHTTPModule, así que tiene capacidad de manejo de sesiones. La sesión está basada en módulos, así que cada módulo puede escoger usar o no el manejo de sesiones.
is module based, so each module may choose to use or not to use session management.
 
  
Session is implemented in abstract manner. By default, no implementation is provided. One sample
+
La sesión se implementa de forma abstracta. Por defecto, no se provee implementación. Un ejemplo de implementación usando archivos .ini está en la unidad '''iniwebsession'''. Debe tener esta unidad en su proyecto o implementar una para que el manejo de sesión funcione.   Si decide implementar uno, básicamente necesita extender e implementar métodos abstractos en las clases '''TCustomSession''' y '''TSessionFactory'''.
implementation using .ini files is given in '''iniwebsession''' unit. You must have this unit in your project or
 
implement one for session management to work. If you decide to implement one, basically you need to
 
extend and implement abstract methods in '''TCustomSession''' and '''TSessionFactory''' classes.
 
  
=== Activating ===
+
=== Activando la Sesión ===
  
To activate session management, set '''CreateSession''' property to true. Session will be started prior to
+
Para activar el manejo de sesión, ponga la propiedad '''CreateSession''' en true. La sesión será iniciada antes del manejo de la petición. En caso de una nueva sesión, '''OnNewSession''' será invocado. Inicialice sus variables de sesión aquí.
request handling. In case of a new session, '''OnNewSession''' will be called. Initialize your session variables
 
here.
 
  
=== Session Variables Manipulation ===
+
=== Manipulación de las Variables de Sesión ===
  
Session variables are provided as '''Session.Variables''' (nb: the Session object is the equivalent of the $_SESSION array used in PHP). This is a string to string map like structure, so you
+
Las variables de sesión se proveen como '''Session.Variables''' (nb: el objeto Session es el equivalente al arreglo  $_SESSION que se usa en PHP). Esto es una estructura parecida a mapa de cadenas, así que puede leer / escribir así:
can read / write it like:
+
<syntaxhighlight lang=pascal>Session.Variables['miVar'] := miVar; // escritura
 
 
<syntaxhighlight lang=pascal>Session.Variables['myvar'] := myvar; // write
 
 
...
 
...
myvar := Session.Variables['myvar']; // read</syntaxhighlight>
+
miVar := Session.Variables['miVar']; // lectura</syntaxhighlight>
  
Setting a variable to empty string does '''NOT''' remove it. If you really want to remove a variable, call
+
Poner una variable como cadena vacía '''NO''' la remueve. En vez de eso, si realmente quiere remover una variable, llame '''Session.RemoveVariable'''.
'''Session.RemoveVariable''' instead.
 
  
=== Terminating ===
+
=== Terminando la Sesión ===
  
Call '''Session.Terminate''' whenever you want to terminate a session (e.g.: user logout). Session will also
+
Llame '''Session.Terminate''' cuando quiera terminar una sesión (p.e.: logout del usuario). La sesión también expirará automáticamente si la siguiente petición viene después de '''Session.TimeOutMinutes''' desde la última petición. Cuando termina la sesión, '''OnSessionExpired''' será invocado. Haga toda la limpieza que necesite aquí.
automatically expire if the next request comes after '''Session.TimeOutMinutes''' since last request. When
 
session terminates, '''OnSessionExpired''' will be called. Do whatever cleanup you need there.
 
  
== Routing ==
+
== Enrutamiento ==
 
 
Since FPC 3.0.4, a new routing mechanism has been implemented. Instead of maintaining backward
 
compatibility, it is decided that the new routing will be the default. Thus, any old code  (or new code
 
depending on old routing) must be ported by adding:
 
  
 +
Desde la versión FPC 3.0.4, un nuevo mecanismo se ha implementado.  En vez de mantener compatibilidad con lo anterior, se ha decidido que el nuevo enrolamiento será por defecto.  Así cualquier código anterior (o nuevo código dependiendo en el enrolamiento anterior) debe actualizarse agregando:
 
<syntaxhighlight lang=pascal>Application.LegacyRouting := true;</syntaxhighlight>
 
<syntaxhighlight lang=pascal>Application.LegacyRouting := true;</syntaxhighlight>
 +
en el archivo .lpr.
  
in the .lpr.
+
=== Mecanismo Anterior ===
  
=== Old Mechanism ===
+
==== usando Múltiples Módulos ====
  
==== Using Multiple Modules ====
+
Puede tener múltiples módulos en su aplicación.  Clique el menú "Archivo", luego clique "Nuevo…".  Un cuadro de diálogo deberá aparecer, seleccione "Web Module".
  
You can have multiple modules in your app. Click "File" menu, then click "New...". A dialog shall appear,
+
[[File:fpwebtutorial_new_module.png|Add new web module]]
select "Web Module" from the treeview.
 
  
[[File:fpwebtutorial_new_module.png|Add new web module]]
+
luego clique en Aceptar.
 +
 
 +
 
 +
Dado que existen múltiples módulos en su aplicación, ya no puede solicitar sólo con /.  El marco de trabajo no podrá seleccionar mágicamente qué módulo debe servir la respuesta, por lo que hay dos formas de indicar a qué módulo le gustaría llamar:
 +
 +
* /<nombre del módulo>
 +
* /?modulo=<nombre del módulo>
  
then click OK.
+
En el segundo formato, puede cambiar "módulo" (que es el valor por defecto) a cualquier clave de cadena de consulta válida modificando '''Application.ModuleVariable'''
  
  
With multiple modules exist in your app, you can no longer request just with /. The framework will not be
+
==== Usando Actions ====
able to magically select which module must serve the response, so there are two ways to state which
 
module you'd like to call:
 
  
* /<module name>
+
Hasta ahora, sólo hemos usado módulos web con un solo manejador de peticiones.  Esto no escala mucho a medida que su aplicación web se vuelve más y más compleja.  Además, algunas funciones pueden tener propiedades compartidas y es mejor agruparlas de forma lógica, por ejemplo:
* /?module=<module name>
 
  
In the 2nd format, you can change "module" (which is the default value) to whatever valid query string key
 
by modifying '''Application.ModuleVariable'''.
 
  
==== Using Actions ====
+
* Módulo Contable
 +
** Action para Ingreso
 +
** Action para Salir
 +
** Action para Registro
 +
* Módulo de Productos
 +
** Action para Crear
 +
** Action para Actualizar
 +
** Action para Eliminar
 +
** Action para Detalles
  
So far, we've only used web modules with single request handler. This doesn't scale much as your web app
 
gets more and more complex. Moreover, some features might have shared properties and be better
 
logically grouped, e.g.:
 
  
* Account module
+
===== Flujo de Manejo de Peticiones =====
** Login action
 
** Logout action
 
** Register action
 
* Product module
 
** Create action
 
** Update action
 
** Delete action
 
** Details action
 
  
===== Request Handling Flow =====
+
Antes de usar un Action, es importante que conozca el manejo de peticiones de fpWeb.  Si no lo hace podría inutilizar su Action porque es siempre su módulo de datos que maneja la petición.  Cómo podría pasar eso?  Retroceda algunos capítulos, recuerda el '''Handled := true''' que siempre hicimos al final de cada procedimiento?  Aquí es donde el parámetro '''Handled''' entra en juego.
  
Before using action, it is important to know fpWeb request handling flow. Failing to do so might render your
+
'''Cada''' petición irá a través del '''OnRequest''' del módulo primero, sin importar la acción solicitada.  Sólo si '''no se pone''' Handled en true, la acción web OnRequest se ejecuta.
action useless because it's always your data module that handles the request. How could that be? Going
 
back a few chapters, remember the '''Handled := true''' that we always did before? Now this is where
 
'''Handled''' parameter comes into play.
 
  
'''Every''' requests will go through module's '''OnRequest''' first, regardless the requested action. Only if it does
+
En general, el flujo de peticiones es:
'''not''' set Handled to true, web action's OnRequest is executed.
 
  
In general, the request flow is:
+
[[File:fpwebtutorial_request_flow.png|Flujo de peticiones fpWeb]]
  
[[File:fpwebtutorial_request_flow.png|fpWeb request flow]]
+
Note el recuadro "Our Concern", es eso a lo que le pondremos nuestra atención.
  
Notice the "Our Concern" box, that's what we're going to put our attention at.
 
  
===== Add Actions to Web Modules =====
+
===== Agregue Actions a los Módulos Web =====
  
To add an action, select the web module then go to object inspector. In the properties tab, select Actions
+
Para agregar un Action, seleccione el módulo web luego vaya al Inspector de Objetos. En la pestaña de propiedades, seleccione Actions y clique el botón en la segunda columna.
and click the button on the second column.
 
  
[[File:fpwebtutorial_manage_actions.png|Manage actions button in object inspector]]
+
[[File:fpwebtutorial_manage_actions.png|Botón de manejo de acciones en el Inspector de Objetos]]
  
A popup window shall appear where you can add, delete and reorder actions.
+
Una ventana popup deberá aparecer donde puede agregar, eliminar y cambiar el orden de las acciones.
  
[[File:fpwebtutorial_edit_actions.png|Manage actions button in popup window]]
+
[[File:fpwebtutorial_edit_actions.png|Botón de manejo de acciones en la ventana popup]]
  
Press Add, a new action shall appear in the list. Select it then go to object inspector. It will currently show
+
Presione Agregar, una nueva acción aparecerá en la lista. Selecciónela y luego vaya al Inspector de Objetos. Mostrará propiedades y eventos para esa nueva acción. Renombre la propiedad Name (este será el nombre que escribirá en la URL, así que dele un nombre corto, simple, pero descriptivo) como prefiera, escogeremos "Hola". Muévase a la pestaña de eventos, haga lo mismo que OnRequest para el módulo, clique el botón a la derecha de la fila OnRequest para crear el manejador de la petición.
properties and events of that newly created action. Rename the Name property (this will be the name you
 
write in the URL, so give it a short, simple but descriptive name) as you wish, I will choose "Hello". Move on
 
to events tab, do the same as OnRequest for module, click button on the right of OnRequest row to create
 
the request handler.
 
  
[[File:fpwebtutorial_action_onrequest.png|Creating web action's OnRequest handler in the object inspector]]
+
[[File:fpwebtutorial_action_onrequest.png|Creando un manejador para la acción web OnRequest en el Inspector de Objetos]]
  
You will be presented in the same OnRequest interface, but this one handles web action instead of web
+
Se le presentará en la misma interfaz OnRquest, pero esto es una acción de manejo web en vez de un módulo web. Cualquier cosa que haga en el módulo web OnRequest puede hacerse aquí también. Copie el cuerpo del método desde la sección "Hola, Mundo!".
module. Whatever you can do in web module's OnRequest can be done here as well. Copy the method body
 
from the "Hello, World!" section.
 
  
Remember to remove '''Handled := true''' from the previous web module's '''OnRequest''' body (or remove the
+
Recuerde remover '''Handled := true''' del cuerpo previo del módulo web '''OnRequest''' (o remueva el evento completamente) para que la acción esté pendiente del manejo de la petición.
event completely) for the action to take care of the request handling.
 
  
Run your project, and fire up your browser. Now, since the request handling is delegated to web action, you
+
Ejecute su proyecto, y encienda su navegador. Ahora, mientras se delega al manejo de petición la acción web, no podrá sólo pedir /, sino que necesita /<nombre de la acción> o propiedad ActionVar del Módulo>=<nombre de la acción>. Note que <propiedad del Módulo de ActionVar> tiene un valor por defecto de cadena vacía, a diferencia de '''Application.ModuleVariable''' el cual tiene "module" como el valor por defecto. Así que, por defecto, puede sólo usarse el formulario /<nombre de la acción>.
can no longer just request /, but you need /<action name> or <Module's ActionVar property>=<action
 
name>. Note that <Module's ActionVar property> has a default value of empty string, unlike
 
'''Application.ModuleVariable''' which has "module" as the default value. So, by default, you can only use
 
the /<action name> form.
 
  
If you have multiple modules, then you have a variety of options:
+
Si tiene múltiples módulos, entonces tiene una variedad de opciones:
  
* /<module name>/<action name>
+
* /<nombre del módulo>/<nombre de la acción>
* /<module name>?action=<action name>
+
* /<nombre del módulo>?action=<nombre de la acción>
* /<action name>?module=<module name>
+
* /<nombre de la acción>?module=<nombre del módulo>
* /?module=<module name>&action=<action name>
+
* /?module=<nombre del módulo>&action=<nombre de la acción>
  
Note that as soon as a module have at least one action, /<module or action name> alone will by default
+
Note que tan pronto como un módulo tenga al menos una acción, /<nombre de la acción o del módulo> en solitario se dirigirá por defecto a /<nombre de la acción>. Para cambiar el comportamiento de tal forma que se dirija a /<nombre del módulo> por defecto, ponga '''Application.PreferModuleName''' en '''true'''. En el caso de múltiples módulos, si no se ha dado nombre de módulo, entonces el módulo por defecto manejará la acción dada. Para cambiar el comportamiento de ese módulo, el nombre debe darse de forma explícita, ponga  '''Application.AllowDefaultModule''' en '''false'''.
map to /<action name>. To change the behavior such that it maps to /<module name> by default, set
 
'''Application.PreferModuleName''' to '''true'''. In case of multiple modules, if no module name given, then the
 
default module will handle the given action. To change the behavior such that module name must be
 
explicitly given, set '''Application.AllowDefaultModule''' to '''false'''.
 
  
The following tables summarize what will happen based on the two properties:
+
Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
  
 
{| class="wikitable"
 
{| class="wikitable"
!colspan="2" rowspan="2"|/<module or action name>
+
!colspan="2" rowspan="2"|/<nombre de la acción o del módulo>
 
!colspan="2"|Application.PreferModuleName
 
!colspan="2"|Application.PreferModuleName
 
|-
 
|-
Line 477: Line 401:
 
!rowspan="2"|Application.AllowDefaultModule
 
!rowspan="2"|Application.AllowDefaultModule
 
|true
 
|true
|/<module name>
+
|/<nombre del módulo>
|/<default module>/<action name>
+
|/<módulo por defecto>/<nombre de la acción>
 
|-
 
|-
 
|false
 
|false
|/<module name>
+
|/<nombre del módulo>
 
|ERROR
 
|ERROR
 
|}
 
|}
  
===== Default Action =====
+
===== Acción por Defecto =====
  
Remember the previous diagram, the "Delegate request handling to actions" is actually not so simple, but if
+
Recuerde el diagrama previo, el "Delegar el manejo de peticiones a las acciones" actualmente no es tan simple, pero si expandimos el diagrama, la imagen será demasiado grande para que quepa. Así que aquí está el diagrama de esa parte:
we expand that diagram, the image will be too big to fit. So, here's the diagram of that part:
 
  
[[File:fpwebtutorial_request_delegation_flow.png|Request delegation to action flow]]
+
[[File:fpwebtutorial_request_delegation_flow.png|Delegación de la petición al flujo de la acción]]
  
Two important things from the flow: DefActionWhenUnknown and a default action. The former is a web
+
Dos cosas importantes del flujo: DefActionWhenUnknown y una acción por defecto. El formador es una propiedad del módulo web mientras que el último corresponde a la propiedad  Default de una acción. En el último caso, en el que haya más de dos acciones que tengan la propiedad Default en true, el orden de las acciones (tal como se muestra en la ventana popup de manejo de acciones) será considerada a decidir cuál es la acción por defecto. Las dos propiedades forman lo que la aplicación debería hacer si no se encuentra una acción correspondiente para una petición dada.
module's property while the latter corresponds to Default property of an action. In the latter case, in case
 
there are more than two actions having Default property set to true, the the action order (as how it's
 
shown in the manage actions popup window) will be considered to decide which is the default action. The
 
two properties forms what the application should do if no matching action found for a given request.
 
  
The following tables summarize what will happen based on the two properties:
+
Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
  
 
{| class="wikitable"
 
{| class="wikitable"
!colspan="2" rowspan="2"|Request with invalid action name
+
!colspan="2" rowspan="2"|Petición con un nombre de acción no-válido
 
!colspan="2"|DefActionWhenUnknown
 
!colspan="2"|DefActionWhenUnknown
 
|-
 
|-
Line 509: Line 428:
 
!rowspan="2"|Action.Default
 
!rowspan="2"|Action.Default
 
|true
 
|true
|Request handled by default action
+
|Petición manejada por una acción por defecto
|Error: No action found for action: <action name>
+
|Error: No se encontró acción para la acción: <nombre de la acción>
 
|-
 
|-
 
|false
 
|false
|Error: Invalid action name and no default action
+
|Error: Nombre de acción no-válido y/o no acción por defecto
|Error: No action found for action: <action name>
+
|Error: No se encontró acción para la acción: <nombre de la acción>
 
|}
 
|}
  
  
 
{| class="wikitable"
 
{| class="wikitable"
!colspan="2" rowspan="2"|Request without action name, i.e.: /
+
!colspan="2" rowspan="2"|Petición sin nombre de acción, p.e.: /
 
!colspan="2"|DefActionWhenUnknown
 
!colspan="2"|DefActionWhenUnknown
 
|-
 
|-
Line 527: Line 446:
 
!rowspan="2"|Action.Default
 
!rowspan="2"|Action.Default
 
|true
 
|true
|Request handled by default action
+
|Petición manejada por la acción por defecto
|Request handled by default action
+
|Petición manejada por la acción por defecto
 
|-
 
|-
 
|false
 
|false
|Error: No action name and no default action
+
|Error: Sin Nombre de acción y sin acción por defecto
|Error: No action name and no default action
+
|Error: Sin Nombre de acción y sin acción por defecto
 
|}
 
|}
  
In case of error response above, a stack trace shall follow, complete with line number information if you
+
En caso de la respuesta de error arriba, una traza de pila deberá aparecer, completa con la información del número de línea si construyó su aplicación con la opción -gl. Veremos luego cómo crear un manejador personalizado para esta (las trazas de pila no son buenas para la producción). Pero por ahora, asegúrese que entiende el concepto del módulo de web y las acciones web, especialmente el flujo de peticiones. Juegue con el código hasta que crea estar listo para la siguiente sección.
build your app with -gl. We'll see later on how to create custom handler for this (stacktrace are no good for
 
production). But for now, make sure you understand the concept of web module and web action, especially
 
the request flow. Play around until you think you're ready for next section.
 
  
=== New Mechanism ===
+
=== Nuevo Mecanismo ===
  
The new mechanism is extremely flexible and works even without data modules (old mechanism only works
+
El nuevo mecanismo es extremadamente flexible y funciona incluso sin módulos de datos (el viejo mecanismo sólo funciona con módulos de datos).
with data modules).
 
  
A dedicated unit for this routing is provided as '''httproute''' (add this to uses clause of program / unit where
+
Una unidad dedicada para este enrolamiento se provee como '''httproute''' (agregue esto a la cláusula uses del programa o unidad donde quiera registrar las rutas). La unidad contiene una función '''HTTPRouter''' que retornará un objeto solitario responsable por la administración de las rutas de la aplicación y tiene un método '''RegisterRoute''' para registrar su ruta.
you want to register routes). The unit contains a function '''HTTPRouter''' that will return singleton object
 
responsible for application's route management and has '''RegisterRoute''' method to register your route.
 
  
==== Route Syntax ====
+
==== Sintáxis de la Ruta ====
  
The first parameter of '''HTTPRouter.RegisterRoute''' is the route that will be matched against incoming
+
El primer parámetro de '''HTTPRouter.RegisterRoute''' es la ruta que se emparejará con la petición entrante. Puede ser tan simple como un asterisco '''*''', el cual significa 0 ó más rutas o simplemente cualquier ruta, tan complejo como '''/api/v1/:resource/*''' el cual significa '''REQUEST_URI''' el encabezado debería iniciar con '''/api/v1/''' seguido por algo que será unido a la variable del recurso nombrada y finalmente termina con 0 o más rutas. Será ajustable con:
request. It can be as simple as '''*''' above, which means 0 or more paths or simply any paths, up to as
 
complex as '''/api/v1/:resource/*''' which means '''REQUEST_URI''' header should start
 
with '''/api/v1/''' followed by something else that will be bound to variable named  resource and finally
 
ended with 0 or more paths. It will match:
 
  
 
* /api/v1/products
 
* /api/v1/products
Line 562: Line 471:
 
* /api/v1/products/something/else/that/is/really/long/and/silly
 
* /api/v1/products/something/else/that/is/really/long/and/silly
  
but not:
+
pero no con:
  
 
* /api/v1
 
* /api/v1
 
* /excuse/me/api/v1/products
 
* /excuse/me/api/v1/products
  
Basically there are only 3 special characters:
+
Básicamente hay sólo 3 caracteres especiales:
* *  denoting 0 or more paths
+
* *  que denota 0 o más rutas
* :param  denoting a part
+
* :param  denota una parte
* /  denoting part separator
+
* /  separador de partes
 +
 
 +
su ruta se compondrá de estos 3 caracteres, además cualquier cosa que conforme una ruta.
  
your route will be composed of these 3 characters, plus everything else that made up a route. 
 
  
==== Registering a Route ====
+
==== Registrar una Ruta ====
  
The 2nd, 3rd or 4th parameter (depending on whether you want to handle specific HTTP method and/or
+
El 2º, 3º o 4º parámetro (dependiendo en lo que desee, manejar un método HTTP específico y/o pasar datos adicionales en él) de '''HTTPRouter.RegisterRoute''' está sobrecargado con varias posibilidades:
pass additional data to it) of '''HTTPRouter.RegisterRoute''' is overloaded with several possibilities:
 
  
* Callback procedure
+
* Procedimiento Callback
 
<syntaxhighlight lang=pascal>TRouteCallback = Procedure(ARequest: TRequest; AResponse);</syntaxhighlight>
 
<syntaxhighlight lang=pascal>TRouteCallback = Procedure(ARequest: TRequest; AResponse);</syntaxhighlight>
  
* Callback event
+
* Evento Callback
 
<syntaxhighlight lang=pascal>TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;</syntaxhighlight>
 
<syntaxhighlight lang=pascal>TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;</syntaxhighlight>
  
* Object satisfying a (CORBA) interface
+
* Objeto que satisface una interfaz (CORBA)
 
<syntaxhighlight lang=pascal>IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
 
<syntaxhighlight lang=pascal>IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
 
   Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
 
   Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
* Object extending abstract router class
+
* Objeto que extiende una clase de enrutador abstracto
 
<syntaxhighlight lang=pascal>TRouteObject = Class(TObject, IRouteInterface)
 
<syntaxhighlight lang=pascal>TRouteObject = Class(TObject, IRouteInterface)
 
Public
 
Public
Line 598: Line 507:
 
TRouteObjectClass = Class of TRouteObject;</syntaxhighlight>
 
TRouteObjectClass = Class of TRouteObject;</syntaxhighlight>
  
By default, if 2nd parameter is not a '''TRouteMethod''', then all HTTP methods will match. Use one
+
Por defecto, si el 2º parámetro no es '''TRouteMethod''', entonces todos los métodos HTTP concordarán. Use alguno de estos '''rmUnknown, rmAll, rmGet, rmPost, rmPut, rmDelete, rmOptions, rmHead, rmTrace''' para concordar sólo con un método HTTP específico.
of  '''rmUnknown, rmAll, rmGet, rmPost, rmPut, rmDelete, rmOptions, rmHead, rmTrace''' to match only a
 
specific HTTP method.
 
  
Registration order matters. If there are two or more routes matching the current request, the earlier
+
El orden de registro importa. Si hay dos o más rutas que concuerdan con la petición actual, el que se registró antes se manejará.
registered one will handle it.
 
  
In this new mechanism, standalone Hello, World! program can be as simple as:
+
En este nuevo mecanismo, un simple programa Hola, Mundo! puede ser tan simple como:
  
 
<syntaxhighlight lang=pascal>uses
 
<syntaxhighlight lang=pascal>uses
 
   fphttpapp, httpdefs, httproute;
 
   fphttpapp, httpdefs, httproute;
procedure DoHello(ARequest:TRequest; AResponse : TResponse);
+
procedure DigaHola(ARequest:TRequest; AResponse : TResponse);
 
begin
 
begin
   AResponse.Content:='<html><body><h1>Hello,World!</h1></body></html>'
+
   AResponse.Content:='<html><body><h1>Hola, Mundo!</h1></body></html>';
 
end;
 
end;
  
 
begin
 
begin
   HTTPRouter.RegisterRoute('*', @DoHello);
+
   HTTPRouter.RegisterRoute('*', @DigaHola);
 
   Application.Port := 9000;
 
   Application.Port := 9000;
 
   Application.Initialize;
 
   Application.Initialize;
Line 621: Line 527:
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
==== Webserver example ====
+
==== Ejemplo Webserver ====
  
This is an example of a simple, cross-platform, multi-threaded web server.
+
Este es un ejemplo de un servidor simple, multi-plataforma, multi-hilos.  
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 638: Line 544:
 
procedure route1(aReq: TRequest; aResp: TResponse);
 
procedure route1(aReq: TRequest; aResp: TResponse);
 
begin
 
begin
   aResp.content:='<html><body><h1>Route 1 The Default</h1></body></html>'
+
   aResp.content:='<html><body><h1>Ruta 1 Por Defecto</h1></body></html>'
 
end;
 
end;
 
   
 
   
 
procedure route2(aReq: TRequest; aResp: TResponse);
 
procedure route2(aReq: TRequest; aResp: TResponse);
 
begin
 
begin
   aResp.content:='<html><body><h1>Route 2</h1></body></html>'
+
   aResp.content:='<html><body><h1>Ruta 2</h1></body></html>'
 
end;
 
end;
 
   
 
   
Line 655: Line 561:
 
end.
 
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
To open "Route 1" type into your browser the following URL: http://127.0.0.1:8080
+
Para abrir la "Ruta 1" teclee en su navegador la URL: http://localhost:8080
  
To open "Route 2" type into your browser the following URL: http://127.0.0.1:8080/route2
+
Para abrir la "Ruta 2" teclee en su navegador la URL: http://localhost:8080/route2
  
== Using Templates ==
+
Puede cambiar localhost por 127.0.0.1
  
fpWeb has integrated support for FPTemplate, Free Pascal's generic templating engine. It doesn't have to be
+
== Usando Plantillas ==
used from web application context, but with integrated support things will be easier a bit. At least the
 
memory management can be ignored as the module will take care of it.
 
  
There are two levels where one can use templates at: action and module. The full RAD support is
+
fpWeb ha integrado soporte para FPTemplate, el motor de plantillas genérico de Free Pascal. No tiene que ser usado desde el contexto de la aplicación web, pero con soporte integrado las cosas serán un poco más fáciles.  Al menos el administrador de memoria puede ser ignorado mientras el módulo se encarga de eso.
incomplete, so you need to go down to hand coding at some points.
 
  
There are two modes of operation: non-parameterized and parameterized. The active mode is controlled by
+
Hay dos niveles donde se puede usar plantillas: action y módulo. El soporte completo del RAD está incompleto, así que necesitará codificar por su cuenta en algunos puntos.
'''AllowTagParams''' property, which should be obvious enough what value refers to what mode.
 
  
Template string can be given from a file through '''FileName''' property or a direct string through '''Template'''
+
Hay dos modos de operación: parametrizados y no parametrizados.  El modo activo está controlado por la propiedad '''AllowTagParams''', la cual deberá ser suficientemente obvia  con los valores que se refieren a los modos.
property. Template.Template, I know it sounds weird :) If both are filled then FileName will take
 
precedence.
 
  
The two properties: '''StartDelimiter''' and '''EndDelimiter''' define how the engine should recognize a
+
La cadena de plantilla puede obtenerse desde un archivo a través de la propiedad '''FileName''' o una cadena directa a través de la propiedad de plantilla '''Template'''.  Si ambas propiedades contienen valores, entonces tendrá preferencia FileName.
template tag. For instance, if you have:
+
 
 +
Las dos propiedades: '''StartDelimiter''' y '''EndDelimiter''' definen cómo el motor debería reconocer una etiqueta de plantilla. Por ejemplo, si se tiene:
  
 
* StartDelimiter = '{+'
 
* StartDelimiter = '{+'
 
* EndDelimiter = '+}'
 
* EndDelimiter = '+}'
  
then a string '{+title+}' defines a template tag named 'title'. Note that spaces are significant, so '{+ title +}'
+
entonces una cadena '{+titulo+}' define una plantilla llamada 'titulo'. Nótese que los espacios son importantes, así que '{+ titulo +}' define una plantilla llamada ' titulo ' en vez de sólo 'titulo'.
defines a template tag named ' title ' instead of just 'title'.
 
  
Special for parameterized mode, additional three properties: '''ParamStartDelimiter''', '''ParamEndDelimiter'''
+
En especial para el modo parametrizado, tres propiedades adicionales: '''ParamStartDelimiter''', '''ParamEndDelimiter''' y '''ParamValueSeparator''' definen cómo el motor debe reconocer un parámetro de etiqueta de plantilla. Por ejemplo, si tiene:
and '''ParamValueSeparator''' defines how the engine should recognize a template tag parameter. For
 
instance, if you have:
 
  
 
* ParamStartDelimiter = '[-'
 
* ParamStartDelimiter = '[-'
Line 692: Line 590:
 
* ParamValueSeparator = '='
 
* ParamValueSeparator = '='
  
then a string '{+data [-p=v-][-a=b-] +}' defines a template tag named 'data' with parameter 'p' of value 'v' and
+
entonces una cadena '{+data [-p=v-][-a=b-] +}' define una etiqueta de plantilla llamada 'data' con el parámetro 'p' de valor 'v' y el parámetro 'a' de valor 'b'. Puede usarse para pasar el parámetro de nivel de plantilla tal como se espera, en formato de fecha, encabezado-fila-pieDePágina para una presentación personalizada, nombre de archivo, etc. usted decide.
parameter 'a' of value 'b'. This can be used to pass template level parameter such as expected date format,
+
 
header-row-footer for customizable output presentation, name of file, etc. you decide.
+
Como consecuencia de distintas formas de operar, el evento central donde la etiqueta de plantilla trabaja es diferente también.
  
As a consequence of different way of operation, the core event where the template works is different, too.
+
El no-parametrizado usará '''OnGetParam''' mientras que el parametrizado usará '''OnReplaceTag'''. Los dos tienen por supuesto, diferentes interfaces:
Non-parameterized will use '''OnGetParam''' while the parameterized will use '''OnReplaceTag'''. The two has of
 
course different interface:
 
  
 
<syntaxhighlight lang=pascal>Type
 
<syntaxhighlight lang=pascal>Type
   // OnGetParam: for simple template tag support only (ex: {Name})
+
   // OnGetParam: para simple soporte exclusivo de etiquetas de plantilla (ej: {Nombre})
 
   TGetParamEvent = Procedure(
 
   TGetParamEvent = Procedure(
 
     Sender: TObject;
 
     Sender: TObject;
Line 707: Line 603:
 
     Out AValue: String
 
     Out AValue: String
 
   ) Of Object;
 
   ) Of Object;
   // OnReplaceTag: for tags with parameters support
+
   // OnReplaceTag: para etiquetas con soporte de parámetros
 
   TReplaceTagEvent = Procedure(
 
   TReplaceTagEvent = Procedure(
 
     Sender: TObject;
 
     Sender: TObject;
Line 715: Line 611:
 
   ) Of Object;</syntaxhighlight>
 
   ) Of Object;</syntaxhighlight>
  
In '''OnGetParam''', you check for '''ParamName''', then assign '''AValue''' accordingly. i.e., if you want tag 'title' to be
+
En '''OnGetParam''', compruebe '''ParamName''', luego asigne '''AValue''' respectivamente. p.e., si quiere etiquetar 'titulo' para ser reemplazado por 'Mi App', entonces llene el método con:
replaced by 'My App', then fill in the method with:
 
  
<syntaxhighlight lang=pascal>// use Trim() if you want the spaces around tag to be insignificant
+
<syntaxhighlight lang=pascal>// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
 
case Trim(ParamName) of
 
case Trim(ParamName) of
   'title': AValue := 'My App';
+
   'titulo': AValue := 'Mi App';
 
else
 
else
   AValue := 'UNKNOWN';
+
   AValue := 'DESCONOCIDO';
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
In '''OnReplaceTag''', you check for '''TagString''' and optionally '''TagParams''', then assign '''ReplaceText'''
+
En '''OnReplaceTag''', compruebe '''TagString''' y opcionalmente '''TagParams''', luego asigne '''ReplaceText'''
accordingly. i.e., if you want tag 'datetime' to be replaced by current time with parameter 'datetimeformat'
+
respectivamente. p.e., si quiere que la etiqueta 'FechaHora' se reemplace con el tiempo actual con el parámetro 'datetimeformat' para especificar cómo la fecha y hora deberían formatearse, luego llene en el método así:
to specify how the date and time should be formatted, then fill in the method like this:
 
  
<syntaxhighlight lang=pascal>// use Trim() if you want the spaces around tag to be insignificant
+
<syntaxhighlight lang=pascal>// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
 
case Trim(TagString) of
 
case Trim(TagString) of
   'datetime': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
+
   'FechaHora': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
 
else
 
else
   AValue := 'UNKNOWN';
+
   AValue := 'DESCONOCIDO';
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
=== At Action Level ===
+
=== Al Nivel de la Acción ===
  
Create/select an action, then go to object inspector. You will see a subcomponent property named
+
Cree/seleccione una acción, luego vaya al inspector de objetos. Verá una propiedad de subcomponente llamada '''Template'''. Esta plantilla es una instancia normal de '''TFPTemplate'''. Expándalo y llene en las propiedades como se explicó arriba. Ahora vaya a la pestaña Eventos, nuevamente expanda '''Template''', verá los dos eventos. Llene el que está basado en su valor de propiedad de '''AllowTagParams'''.
'''Template'''. This template is a normal '''TFPTemplate''' instance. Expand it and fill in the properties as
 
explained above. Now go to Events tab, again expand '''Template''', you will see the two events. Fill the one
 
based on your value of '''AllowTagParams''' property.
 
  
NOTE: If your Lazarus cannot autocomplete the event, try writing the name manually in the edit box then
+
NOTA: Si su Lazarus no puede autocompletar el evento, intente escribir el nombre manualmente en el cuadro de edición y luego clique el botón ···. Este es un bug en el Lazarus actual que deberá ser arreglado en el futuro.
click the ... button. This is a bug in present Lazarus which may be fixed in the future.
 
  
At this level, template with a content is not automatically set as request handler. It might change in the
+
A este nivel, la plantilla con contenido no se ajusta automáticamente como el manejador de petición. Podría cambiar en el futuro, pero trabajemos con su estado actual. Llene el evento de la acción '''OnRequest''' con:
future, but let's deal with its current state. Fill in '''OnRequest''' event of the action, then fill it with:
 
  
 
<syntaxhighlight lang=pascal>with Actions.CurrentAction as TFPWebAction do
 
<syntaxhighlight lang=pascal>with Actions.CurrentAction as TFPWebAction do
Line 755: Line 644:
 
Handled := true;</syntaxhighlight>
 
Handled := true;</syntaxhighlight>
  
The cast is required since '''CurrentAction''' is of type '''TCustomWebAction''' instead of '''TFPWebAction'''.
+
El cast es requerido desde que '''CurrentAction''' es de tipo '''TCustomWebAction''' en vez de '''TFPWebAction'''.
Without it, we can't access the '''Template''' property.
+
Sin eso, no podemos acceder a la propiedad '''Template'''.
  
=== At Module Level ===
+
=== A Nivel de Módulo ===
  
At module level, you currently have to do it by hand coding since no RAD support is implemented. The
+
A nivel del módulo, actualmente tiene que codificarlo porque no se ha implementado soporte para RAD. La propiedad a vincular es ModuleTemplate. Esto es sin embargo una instancia irregular de '''TFPTemplate''', pero una clase especial '''TFPWebTemplate''' que es descendiente de ella.
linked property is ModuleTemplate . This is however not a regular '''TFPTemplate''' instance, but special
 
'''TFPWebTemplate''' class that's a descendant of it.
 
  
The idea here is to have module provide a layout while the actions provide content, with the ability to
+
La idea aquí es que el módulo proporcione un diseño mientras que las acciones proporcionan contenido, con la capacidad de proporcionar variables adicionales. Por tanto, es buena idea mantener '''AllowTagParams''' como está y asignar el evento '''OnGetParam''' **del módulo** solamente. NO asigne '''OnGetParam''' del '''ModuleTemplate''' ya que '''nunca''' será llamado.
provide additional variables. It's therefore a good idea to keep '''AllowTagParams''' as is and assign
 
'''OnGetParam''' event **of the module** only. Do NOT assign '''OnGetParam''' of '''ModuleTemplate''' as it will
 
'''never''' be called.
 
  
A template tag named 'content' will be replaced automatically by what content produced by action,
+
Una etiqueta de plantilla llamada 'contenido' se reemplazará automáticamente por el contendido producido por la acción, todo lo demás es reemplazado desde las variables internas de plantilla o desde '''OnGetParam'''.
everything else is either replaced from internal template variables or from '''OnGetParam'''.
 
  
=== Using Separated Template ===
+
=== Usando Plantillas por Separado ===
  
Despite the somewhat incomplete integration, nothing stops you from using fpTemplate (or any other
+
A pesar de la integración algo incompleta, nada le impide usar fpTemplate (o cualquier otra solución de plantillas) manualmente, fuera del soporte integrado. Esto podría incluso ser mejor en algunos casos ya que es modular.
templating solutions) manually, outside from the integrated support. This could even be better in some
 
cases since it's modular.
 
  
  
  
== Using an html Producer object ==
+
== Usando un objeto html Producer ==
  
  

Revision as of 18:16, 17 March 2023

العربية (ar) English (en) español (es) русский (ru)

Originalmente basado en: tutorial de fcl-web (en PDF) por un usuario del foro Leledumbo.

Introducción

fpWeb es un esquema de aplicación web que viene con FPC por defecto como parte del paquete fcl-web. El esquema en sí mismo está construido encima de las características de fcl-web. El esquema está construido con mentalidad de RAD para hacer buen uso de la componentización en producir contenido dinámico. Un paquete Lazarus es provisto que pueda usar el esquema en forma de arrastrar y soltar para su administración de sesión y producción de contenido.

Este tutorial intentará cubrir la funcionalidad básica de fpWeb, así que uno puede construir una aplicación común para web con él. Note que este tutorial NO intenta enseñar ningún protocolo HTTP, HTML, CSS, JavaScript o manipulación de base de datos como los lenguajes de protocolo y cliente debieran ser los prerequisitos para cada programador de aplicaciones web y la manipulación de base de datos no difiere de la implementación de escritorio.

Arquitectura (lea POR FAVOR)

Antes de iniciar, es necesario conocer la arquitectura y flujo de aplicación para evitar confusiones cuando ciertas cosas no funcionen o lo hagan de forma inesperada. Así que por favor pase algo de tiempo leyendo esta sección.

Aplicación

La aplicación aquí se refiere al protocolo que su aplicación implementará. fpWeb felizmente se cambia de módulos CGI, FCGI, Apache a un servidor embebido, y más si fcl-web implementa otro en el futuro. Cada aplicación se implementa en su propia unidad, así que para cambiar de una aplicación a otra, con la excepción del módulo Apache, sólo se necesita cambiar el respectivo identificador en la cláusula uses. Para 3.0.0 en adelante son:

  • fpCGI -> CGI
  • fpFCGI -> FastCGI
  • fpApache (requiere httpd también) -> módulo Apache
  • fpHttpApp -> servidor embebido
  • microhttpapp -> servidor embebido usando la biblioteca GNU libmicrohttp.
  • fphttpsys -> soporte de sistema Windows para el protocolo HTTP.

A través de este tutorial, usaremos el servidor embebido en beneficio de simplicidad porque no tiene que tratar con configurar el servidor virtual ni enredarse con el manejo complicado de archivos y servicios. Su aplicación será una aplicación portátil binaria para web! Otra razón sería que hay más de una aplicación de servidor web y que cada una tiene una forma distinta de configurar. Sería exagerado cubrir todos mientras que su documentación hace el trabajo. El módulo Apache se implementa como una biblioteca (dinámica), mientras que otros protocolos son aplicaciones normales. Cada aplicación puede que tenga propiedades específicas (como puertos) disponibles y que sólo competen a esa aplicación. Eso es el porqué de ver los ejemplos fcl-web, pares de .lpi / .lpr para cada protocolo están puestos en sus propios directorios, sólo los módulos web se comparten.


Módulos Web

fpWeb-overview.png

Las aplicaciones fpWeb constan de módulos web los que hacen la producción actual de contenido. Un módulo web puede contener acciones web que pueden dividir la funcionalidad incluso más. Por ejemplo, un módulo web auténtico puede que tenga acciones web de login y logout. Mientras que un módulo web de acerca de, puede no necesitar una acción específica y sirve sólo para un contenido. El módulo Web se integra con fpTemplate que puede usarse para producir contenido dinámico con una plantilla. Esto es bastante similar a lo que hace PHP, sólo el espacio entre la lógica y la presentación se impone en vez de lo que se sugiere. Algunos dicen que fpTemplate implementa una vista pasiva mientras que PHP por defecto implementa un patrón de diseño de vista activa.


Instalación

El paquete fpWeb para Lazarus no se instala por defecto (pero viene con él), para activar fpWeb:

  1. Abra Lazarus y escoja Paquetes->Instalar/Desinstalar Paquetes’’’
  2. En el listbox disponible para la instalación, busque weblaz y presione Instala selección. Presione Guardar y reconstruir IDE y confirme con Continuar
  3. Deje que el IDE se reconstruya y reincide solo. Si todo va bien, deberá ahora tener una pestaña ftweb en la paleta de componentes, como se muestra:
Installed weblaz package

Módulos Especializados

La clase *TFPWebModule* (usada abajo) es un simple ejemplo del módulo fpWEB que puede usarse para toda clase de peticiones HTTP.

Sin embargo, fpWEB viene con algunos módulos especializados que tienen soporte extra para tareas especializadas:

  • La clase TSimpleFileModule en la unidad fpwebfile.pp puede usarse para enviar archivos. Sólo apunte a un directorio, y él hace el resto.
  • La clase TFPHTMLModule en la unidad fphtml.pp puede usarse para producir HTML.
  • La clase TProxyWebModule en la unidad fpwebproxy.pp es un proxy de redirección listo para usar.
  • La clase TFPWebProviderDataModule en la unidad fpwebdata.pp sirve de datos en formato JSON que puede guardar ExtJS.
  • La clase TSQLDBRestModule en la unidad sqldbrestmodule.pp implementa un servidor REST completo respaldado por SQLDB. Vea más información en SQLDBRestBridge.
  • La clase TJSONRPCModule en la unidad webjsonrpc.pp implementa un servicio JSON-RPC.
  • La clase TExtDirectModule en la unidad fpextdirect.pp implementa una variante Ext.Direct de un servicio JSON-RPC.

Hola, Mundo!

Creemos una aplicación Web sencilla. Como se enseñó comúnmente cuando se aprendía programación, "Hola, Mundo!" será nuestra primera app.

1. Abra Lazarus y escoja Proyecto->Nuevo Proyecto luego HTTP server Application

Create new HTTP server application

2. Otro diálogo deberá aparecer para servir archivos estáticos, selección de puerto y multihilo. Sólo use por defecto el puerto 8080.

Puede que omita servir archivos estáticos (vaya a la sección de tips and tricks si quiere saber más).
Static files, port selection and multithreading options


IMPORTANTE!:
Si escogió usar hilos en *nix, no olvide agregar cthreads como la primera unidad en la cláusula uses del archivo .lpr, de otra forma se generará un error RTE 232. Cuando se ejecuta desde una consola, un mensaje deberá aparecer:
Este binario no se compiló con soporte de hilos.  Recompile la aplicación con un manejado de hilasen la cláusula uses del programa antes de cualquier unidad que use hilos.


3. Desde Enero 14 de 2017 (o FPC 3.0.4), pueda que necesite abrir el archivo .lpr y agregar la siguiente línea en el cuerpo principal si no está puesto:

Application.LegacyRouting := true;
la razón estará explicada en el capítulo #Enrutamiento.


4. Cualquier cosa que escoja, clique "OK" y se le presentará en el módulo de aplicación fpWeb por defecto.

5. Enfoque el módulo y muévase al Inspector de Objetos. Siéntase libre de renombre el módulo si así lo prefiere.

6. Escoja la pestaña Events y clique el botón a la derecha de la segunda columna de la fila OnRequest para crear el manejador del evento.

Creating web module's OnRequest handler in the object inspector
Será dirigido al editor de código fuente con el código:
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
begin
  |
end;
Escriba:
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
begin
  AResponse.Content := 'Hola, Mundo!';
  Handled := true;
end;

7. Luego ejecute su aplicación (o presione F9).

8. Abra su navegador y escriba:

http://localhost:8080/

9. Deberá ver "Hola, Mundo!'.

En caso contrario, revise:

  • El marco de trabajo hace mucho de manejo de excepciones y el depurador del IDE podrá atraparlos e interrumpe su aplicación. Está bien agregar muchas de las excepciones para la lista del olvido así que se pueda concentrar más en su flujo de aplicación. Manténgase evitando y continuando hasta que no aparezcan más diálogos y el navegador muestre el contenido.
  • Handled := true es la forma que le decimos que la solicitud ha sido manejada. Si no se coloca (o se pone en false) mostrará la página de error. Por ahora, esto no afecta el flujo de peticiones, pero lo hará después. Así que déjelo así hasta que el momento en que se le de buen uso.
  • otra pista: pruebe sin un cortafuegos cargado en RAM (como una aplicación, como un servicio o daemon, o ambos).


Leyendo los datos GET & POST

Es probable que un contenido dinámico se active a partir de la entrada del usuario, tanto a través de formularios, proveyendo valores en el URL, etc. Esos datos se envían a través de la petición, la cual se representa en el método como ARequest parámetro de tipo TRequest.

Leyendo GET

Los datos GET se proveen como ARequest.QueryFields , el cual desciende de TStrings. Resumiendo, cualquier cosa que haga con TStrings, se aplica aquí como acceder a los datos en un estilo de mapa a través de la propiedad Values.

Reusando el código anterior, reemplace el cuerpo del método con:

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
var
  LName: String;
begin
  LName := ARequest.QueryFields.Values['Name']; // QueryFields para GET
  if LName = EmptyStr then
    with AResponse.Contents do begin       // En el formulario <form> el
      Add('<form action="%s" method="GET"', [aRequest.URI]);// solicitante
      Add('<label for="name">Por favor dame tu nombre:</label>');// pondrá
      Add('<input type="text" name="name" id="name" />');// name="name"
      Add('<input type="submit" value="Send" />');
      Add('</form>');
    end
  else
    AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
  Handled := true;  // <— Se dio buen manejo!
end;

ARequest.URI es sólo una conveniencia referirse al URI actual, así que cuando cambie su módulo registrado, este código permanece igual.

Note que como en Pascal, nos referimos a los datos se hace sin importar las mayúsculas/minúsculas.

Como es una petición GET, en la dirección de la página aparece la sintaxis de lo solicitado. Ahora puede intentar la solicitud /, la cual mostrará

 Por favor dame tu nombre:

y al final de la dirección coloque /?name=<escriba algo aquí, p.e.: Fernando>, lo que mostrará

 Hola, Fernando!

Leyendo POST

POST no difiere mucho de GET, sólo en la propiedad que accede. Si GET se acceda a través de ARequest.QueryFields, POST se accede a través de ARequest.ContentFields. El estilo de POST del código previo es:

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
var
  LName: String;
begin
  LName := ARequest.ContentFields.Values['Name']; // ContentFields para POST
  if LName = EmptyStr then
    with AResponse.Contents do
    begin
      Add('<form action="%d" method="POST"', [ARequest.URI]);
      Add('<label for="name">Por favor dame tu nombre:</label>');
      Add('<input type="text" name="name" id="name" />');
      Add('<input type="submit" value="Send" />');
      Add('</form>');
    end
  else
    AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
  Handled := true;
end;

Leyendo Subir Archivos

Una excepción es para los archivos de lectura multipart/form-data, p.e. los archivos. Eso está disponible en ARequest.Files como una instancia de TUploadedFiles, la cual desciende de TCollection. Lo siguiente es la interfaz pública TUploadedFiles la cual puede usarse para acceder a los archivos:

TUploadedFiles = Class(TCollection)
    ·
    ·
    ·
public
  Function First: TUploadedFile;
  Function Last: TUploadedFile;
  Function IndexOfFile(AName: String) : Integer;
  Function FileByName(AName: String) : TUploadedFile;
  Function FindFile(AName: String) : TUploadedFile;
  Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
end;

// Cada TUploadedFile por sí mismo tiene varias propiedades:

TUploadedFile = Class(TCollectionItem)
    ·
    ·
    ·
Public
  Destructor Destroy; override;
  Property FieldName: String Read FFieldName Write FFieldName;
  Property FileName: String Read FFileName Write FFileName;
  Property Stream: TStream Read GetStream;
  Property Size: Int64 Read FSize Write FSize;
  Property ContentType: String Read FContentType Write FContentType;
  Property Disposition: String Read FDisposition Write FDisposition;
  Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
  Property Description: String Read FDescription Write FDescription;
end;

Debería ser lo suficientemente descriptivo, con la excepción de FileName y LocalFileName. FileName es el archivo original name como se subió desde el cliente, LocalFileName es la ruta del archivo en el servidor donde el archivo se guarda de forma temporal. Note la diferencia en los términos en negrita antes descritos.

Nuevamente, reusemos el mismo manejador de petición, nótese que se puede optimizar el código usando una constante Req:

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
const
  Req = '<form id="form" action="%d" method=POST enctype="multipart' + 
	'/form-data"><label for="name">Drag n drop or click to' +
	' add file:</label><input type="file" name="input" />' +
	'<input type="submit" value="Send" /></form>';
var
  n, i: Integer;
  f: TUploadedFile;
begin
  n := ARequest.Files.Count;
  if n = 0 then
    with AResponse.Contents do 
      Add(Req, [ARequest.URI])
  else begin
    f := ARequest.Files[0];
    AResponse.Contents.LoadFromStream(f.Stream);
  end;
  Handled := true;
end;

arrastrar y soltar un archivo (preferiblemente texto, tal si fuese presentado como texto) en el campo de entrada de archivo (o clique el botón respectivo) entonces clique en el botón Enviar. El contenido de archivo deberá mostrarse.

Cookies

Configuración

El concepto de "cookie" (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes.

El guardar y mantener las cookies son responsabilidad del navegador, por tanto el servidor necesita enviarlo como parte de la respuesta para mantener una. AResponse.Cookies contiene una lista de cookies a enviarse. Es descendiente de TCollection, respectivamente el TCookie contenido es descendiente de TCollectionItem. Por lo tanto, puede usar TCollection en la forma de manejar items para manipularlo.

Aquí un ejemplo:

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
var
  C: TCookie;
begin
  C := AResponse.Cookies.Add;
  C.Name := 'miCookie';
  C.Value := 'algunValor';
  Handled := true;
end;

No verá ninguna salida en su navegador. Pero si usa alguna herramienta de navegador (Chrome y Brave tienen una incorporada), puede ver el encabezado de la respuesta:

Encabezado de respuesta puesta de una Cookie response en las herramientas de desarrollador de Chrome's

Note que la cookie tiene atributos, así que Name y Value no son las únicas cosas que pueda poner. Navegue por la interfaz de TCookie para ver qué propiedades soporta.

Obteniéndola

Una vez se da el encabezado Set-Cookie arriba, la subsecuente petición a su sitio contendrá un encabezado adicional que contiene el valor que solicitó previamente poner:


Encabezado de petición de una Cookie en las herramientas de desarrollo de Chrome's

Afortunadamente, la forma de leerla no es distinta de los datos GET o POST. La propiedad relacionada es ARequest.CookieFields. Para leer una cookie previamente puesta:

procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
begin
  AResponse.Contents.Add('<p>Cookie get: ' + ARequest.CookieFields.Values['mycookie'] + '</p>');
  Handled := true;
end;

Sesiones

TFPWebModule es un descendiente de TSessionHTTPModule, así que tiene capacidad de manejo de sesiones. La sesión está basada en módulos, así que cada módulo puede escoger usar o no el manejo de sesiones.

La sesión se implementa de forma abstracta. Por defecto, no se provee implementación. Un ejemplo de implementación usando archivos .ini está en la unidad iniwebsession. Debe tener esta unidad en su proyecto o implementar una para que el manejo de sesión funcione. Si decide implementar uno, básicamente necesita extender e implementar métodos abstractos en las clases TCustomSession y TSessionFactory.

Activando la Sesión

Para activar el manejo de sesión, ponga la propiedad CreateSession en true. La sesión será iniciada antes del manejo de la petición. En caso de una nueva sesión, OnNewSession será invocado. Inicialice sus variables de sesión aquí.

Manipulación de las Variables de Sesión

Las variables de sesión se proveen como Session.Variables (nb: el objeto Session es el equivalente al arreglo $_SESSION que se usa en PHP). Esto es una estructura parecida a mapa de cadenas, así que puede leer / escribir así:

Session.Variables['miVar'] := miVar; // escritura
...
miVar := Session.Variables['miVar']; // lectura

Poner una variable como cadena vacía NO la remueve. En vez de eso, si realmente quiere remover una variable, llame Session.RemoveVariable.

Terminando la Sesión

Llame Session.Terminate cuando quiera terminar una sesión (p.e.: logout del usuario). La sesión también expirará automáticamente si la siguiente petición viene después de Session.TimeOutMinutes desde la última petición. Cuando termina la sesión, OnSessionExpired será invocado. Haga toda la limpieza que necesite aquí.

Enrutamiento

Desde la versión FPC 3.0.4, un nuevo mecanismo se ha implementado. En vez de mantener compatibilidad con lo anterior, se ha decidido que el nuevo enrolamiento será por defecto. Así cualquier código anterior (o nuevo código dependiendo en el enrolamiento anterior) debe actualizarse agregando:

Application.LegacyRouting := true;

en el archivo .lpr.

Mecanismo Anterior

usando Múltiples Módulos

Puede tener múltiples módulos en su aplicación. Clique el menú "Archivo", luego clique "Nuevo…". Un cuadro de diálogo deberá aparecer, seleccione "Web Module".

Add new web module

luego clique en Aceptar.


Dado que existen múltiples módulos en su aplicación, ya no puede solicitar sólo con /. El marco de trabajo no podrá seleccionar mágicamente qué módulo debe servir la respuesta, por lo que hay dos formas de indicar a qué módulo le gustaría llamar:

  • /<nombre del módulo>
  • /?modulo=<nombre del módulo>

En el segundo formato, puede cambiar "módulo" (que es el valor por defecto) a cualquier clave de cadena de consulta válida modificando Application.ModuleVariable


Usando Actions

Hasta ahora, sólo hemos usado módulos web con un solo manejador de peticiones. Esto no escala mucho a medida que su aplicación web se vuelve más y más compleja. Además, algunas funciones pueden tener propiedades compartidas y es mejor agruparlas de forma lógica, por ejemplo:


  • Módulo Contable
    • Action para Ingreso
    • Action para Salir
    • Action para Registro
  • Módulo de Productos
    • Action para Crear
    • Action para Actualizar
    • Action para Eliminar
    • Action para Detalles


Flujo de Manejo de Peticiones

Antes de usar un Action, es importante que conozca el manejo de peticiones de fpWeb. Si no lo hace podría inutilizar su Action porque es siempre su módulo de datos que maneja la petición. Cómo podría pasar eso? Retroceda algunos capítulos, recuerda el Handled := true que siempre hicimos al final de cada procedimiento? Aquí es donde el parámetro Handled entra en juego.

Cada petición irá a través del OnRequest del módulo primero, sin importar la acción solicitada. Sólo si no se pone Handled en true, la acción web OnRequest se ejecuta.

En general, el flujo de peticiones es:

Flujo de peticiones fpWeb

Note el recuadro "Our Concern", es eso a lo que le pondremos nuestra atención.


Agregue Actions a los Módulos Web

Para agregar un Action, seleccione el módulo web luego vaya al Inspector de Objetos. En la pestaña de propiedades, seleccione Actions y clique el botón en la segunda columna.

Botón de manejo de acciones en el Inspector de Objetos

Una ventana popup deberá aparecer donde puede agregar, eliminar y cambiar el orden de las acciones.

Botón de manejo de acciones en la ventana popup

Presione Agregar, una nueva acción aparecerá en la lista. Selecciónela y luego vaya al Inspector de Objetos. Mostrará propiedades y eventos para esa nueva acción. Renombre la propiedad Name (este será el nombre que escribirá en la URL, así que dele un nombre corto, simple, pero descriptivo) como prefiera, escogeremos "Hola". Muévase a la pestaña de eventos, haga lo mismo que OnRequest para el módulo, clique el botón a la derecha de la fila OnRequest para crear el manejador de la petición.

Creando un manejador para la acción web OnRequest en el Inspector de Objetos

Se le presentará en la misma interfaz OnRquest, pero esto es una acción de manejo web en vez de un módulo web. Cualquier cosa que haga en el módulo web OnRequest puede hacerse aquí también. Copie el cuerpo del método desde la sección "Hola, Mundo!".

Recuerde remover Handled := true del cuerpo previo del módulo web OnRequest (o remueva el evento completamente) para que la acción esté pendiente del manejo de la petición.

Ejecute su proyecto, y encienda su navegador. Ahora, mientras se delega al manejo de petición la acción web, no podrá sólo pedir /, sino que necesita /<nombre de la acción> o propiedad ActionVar del Módulo>=<nombre de la acción>. Note que <propiedad del Módulo de ActionVar> tiene un valor por defecto de cadena vacía, a diferencia de Application.ModuleVariable el cual tiene "module" como el valor por defecto. Así que, por defecto, puede sólo usarse el formulario /<nombre de la acción>.

Si tiene múltiples módulos, entonces tiene una variedad de opciones:

  • /<nombre del módulo>/<nombre de la acción>
  • /<nombre del módulo>?action=<nombre de la acción>
  • /<nombre de la acción>?module=<nombre del módulo>
  • /?module=<nombre del módulo>&action=<nombre de la acción>

Note que tan pronto como un módulo tenga al menos una acción, /<nombre de la acción o del módulo> en solitario se dirigirá por defecto a /<nombre de la acción>. Para cambiar el comportamiento de tal forma que se dirija a /<nombre del módulo> por defecto, ponga Application.PreferModuleName en true. En el caso de múltiples módulos, si no se ha dado nombre de módulo, entonces el módulo por defecto manejará la acción dada. Para cambiar el comportamiento de ese módulo, el nombre debe darse de forma explícita, ponga Application.AllowDefaultModule en false.

Las siguientes tablas resumen lo que pasará basado en las dos propiedades:

/<nombre de la acción o del módulo> Application.PreferModuleName
true false
Application.AllowDefaultModule true /<nombre del módulo> /<módulo por defecto>/<nombre de la acción>
false /<nombre del módulo> ERROR
Acción por Defecto

Recuerde el diagrama previo, el "Delegar el manejo de peticiones a las acciones" actualmente no es tan simple, pero si expandimos el diagrama, la imagen será demasiado grande para que quepa. Así que aquí está el diagrama de esa parte:

Delegación de la petición al flujo de la acción

Dos cosas importantes del flujo: DefActionWhenUnknown y una acción por defecto. El formador es una propiedad del módulo web mientras que el último corresponde a la propiedad Default de una acción. En el último caso, en el que haya más de dos acciones que tengan la propiedad Default en true, el orden de las acciones (tal como se muestra en la ventana popup de manejo de acciones) será considerada a decidir cuál es la acción por defecto. Las dos propiedades forman lo que la aplicación debería hacer si no se encuentra una acción correspondiente para una petición dada.

Las siguientes tablas resumen lo que pasará basado en las dos propiedades:

Petición con un nombre de acción no-válido DefActionWhenUnknown
true false
Action.Default true Petición manejada por una acción por defecto Error: No se encontró acción para la acción: <nombre de la acción>
false Error: Nombre de acción no-válido y/o no acción por defecto Error: No se encontró acción para la acción: <nombre de la acción>


Petición sin nombre de acción, p.e.: / DefActionWhenUnknown
true false
Action.Default true Petición manejada por la acción por defecto Petición manejada por la acción por defecto
false Error: Sin Nombre de acción y sin acción por defecto Error: Sin Nombre de acción y sin acción por defecto

En caso de la respuesta de error arriba, una traza de pila deberá aparecer, completa con la información del número de línea si construyó su aplicación con la opción -gl. Veremos luego cómo crear un manejador personalizado para esta (las trazas de pila no son buenas para la producción). Pero por ahora, asegúrese que entiende el concepto del módulo de web y las acciones web, especialmente el flujo de peticiones. Juegue con el código hasta que crea estar listo para la siguiente sección.

Nuevo Mecanismo

El nuevo mecanismo es extremadamente flexible y funciona incluso sin módulos de datos (el viejo mecanismo sólo funciona con módulos de datos).

Una unidad dedicada para este enrolamiento se provee como httproute (agregue esto a la cláusula uses del programa o unidad donde quiera registrar las rutas). La unidad contiene una función HTTPRouter que retornará un objeto solitario responsable por la administración de las rutas de la aplicación y tiene un método RegisterRoute para registrar su ruta.

Sintáxis de la Ruta

El primer parámetro de HTTPRouter.RegisterRoute es la ruta que se emparejará con la petición entrante. Puede ser tan simple como un asterisco *, el cual significa 0 ó más rutas o simplemente cualquier ruta, tan complejo como /api/v1/:resource/* el cual significa REQUEST_URI el encabezado debería iniciar con /api/v1/ seguido por algo que será unido a la variable del recurso nombrada y finalmente termina con 0 o más rutas. Será ajustable con:

  • /api/v1/products
  • /api/v1/products/1
  • /api/v1/products/1/clone
  • /api/v1/products/something/else/that/is/really/long/and/silly

pero no con:

  • /api/v1
  • /excuse/me/api/v1/products

Básicamente hay sólo 3 caracteres especiales:

  • *  que denota 0 o más rutas
  • :param  denota una parte
  • /  separador de partes

su ruta se compondrá de estos 3 caracteres, además cualquier cosa que conforme una ruta.


Registrar una Ruta

El 2º, 3º o 4º parámetro (dependiendo en lo que desee, manejar un método HTTP específico y/o pasar datos adicionales en él) de HTTPRouter.RegisterRoute está sobrecargado con varias posibilidades:

  • Procedimiento Callback
TRouteCallback = Procedure(ARequest: TRequest; AResponse);
  • Evento Callback
TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
  • Objeto que satisface una interfaz (CORBA)
IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
  Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
end;
  • Objeto que extiende una clase de enrutador abstracto
TRouteObject = Class(TObject, IRouteInterface)
Public
  Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
end;

TRouteObjectClass = Class of TRouteObject;

Por defecto, si el 2º parámetro no es TRouteMethod, entonces todos los métodos HTTP concordarán. Use alguno de estos rmUnknown, rmAll, rmGet, rmPost, rmPut, rmDelete, rmOptions, rmHead, rmTrace para concordar sólo con un método HTTP específico.

El orden de registro importa. Si hay dos o más rutas que concuerdan con la petición actual, el que se registró antes se manejará.

En este nuevo mecanismo, un simple programa Hola, Mundo! puede ser tan simple como:

uses
  fphttpapp, httpdefs, httproute;
procedure DigaHola(ARequest:TRequest; AResponse : TResponse);
begin
  AResponse.Content:='<html><body><h1>Hola, Mundo!</h1></body></html>';
end;

begin
  HTTPRouter.RegisterRoute('*', @DigaHola);
  Application.Port := 9000;
  Application.Initialize;
  Application.Run;
end.

Ejemplo Webserver

Este es un ejemplo de un servidor simple, multi-plataforma, multi-hilos.

program webserver;
 
{$mode objfpc}{$H+}
 
uses
  {$ifdef UNIX}
    cthreads, cmem,
  {$endif} 
  fphttpapp, httpdefs, httproute;
 
procedure route1(aReq: TRequest; aResp: TResponse);
begin
  aResp.content:='<html><body><h1>Ruta 1 Por Defecto</h1></body></html>'
end;
 
procedure route2(aReq: TRequest; aResp: TResponse);
begin
  aResp.content:='<html><body><h1>Ruta 2</h1></body></html>'
end;
 
begin
  HTTPRouter.registerRoute('/', @route1, true);
  HTTPRouter.registerRoute('/route2', @route2);
  Application.port := 8080;
  Application.threaded := true;
  Application.initialize;
  Application.run;
end.

Para abrir la "Ruta 1" teclee en su navegador la URL: http://localhost:8080

Para abrir la "Ruta 2" teclee en su navegador la URL: http://localhost:8080/route2

Puede cambiar localhost por 127.0.0.1

Usando Plantillas

fpWeb ha integrado soporte para FPTemplate, el motor de plantillas genérico de Free Pascal. No tiene que ser usado desde el contexto de la aplicación web, pero con soporte integrado las cosas serán un poco más fáciles. Al menos el administrador de memoria puede ser ignorado mientras el módulo se encarga de eso.

Hay dos niveles donde se puede usar plantillas: action y módulo. El soporte completo del RAD está incompleto, así que necesitará codificar por su cuenta en algunos puntos.

Hay dos modos de operación: parametrizados y no parametrizados. El modo activo está controlado por la propiedad AllowTagParams, la cual deberá ser suficientemente obvia con los valores que se refieren a los modos.

La cadena de plantilla puede obtenerse desde un archivo a través de la propiedad FileName o una cadena directa a través de la propiedad de plantilla Template. Si ambas propiedades contienen valores, entonces tendrá preferencia FileName.

Las dos propiedades: StartDelimiter y EndDelimiter definen cómo el motor debería reconocer una etiqueta de plantilla. Por ejemplo, si se tiene:

  • StartDelimiter = '{+'
  • EndDelimiter = '+}'

entonces una cadena '{+titulo+}' define una plantilla llamada 'titulo'. Nótese que los espacios son importantes, así que '{+ titulo +}' define una plantilla llamada ' titulo ' en vez de sólo 'titulo'.

En especial para el modo parametrizado, tres propiedades adicionales: ParamStartDelimiter, ParamEndDelimiter y ParamValueSeparator definen cómo el motor debe reconocer un parámetro de etiqueta de plantilla. Por ejemplo, si tiene:

  • ParamStartDelimiter = '[-'
  • ParamEndDelimiter = '-]'
  • ParamValueSeparator = '='

entonces una cadena '{+data [-p=v-][-a=b-] +}' define una etiqueta de plantilla llamada 'data' con el parámetro 'p' de valor 'v' y el parámetro 'a' de valor 'b'. Puede usarse para pasar el parámetro de nivel de plantilla tal como se espera, en formato de fecha, encabezado-fila-pieDePágina para una presentación personalizada, nombre de archivo, etc. usted decide.

Como consecuencia de distintas formas de operar, el evento central donde la etiqueta de plantilla trabaja es diferente también.

El no-parametrizado usará OnGetParam mientras que el parametrizado usará OnReplaceTag. Los dos tienen por supuesto, diferentes interfaces:

Type
  // OnGetParam: para simple soporte exclusivo de etiquetas de plantilla (ej: {Nombre})
  TGetParamEvent = Procedure(
    Sender: TObject;
    Const ParamName: String;
    Out AValue: String
  ) Of Object;
  // OnReplaceTag: para etiquetas con soporte de parámetros
  TReplaceTagEvent = Procedure(
    Sender: TObject;
    Const TagString: String;
    TagParams: TStringList;
    Out ReplaceText: String
  ) Of Object;

En OnGetParam, compruebe ParamName, luego asigne AValue respectivamente. p.e., si quiere etiquetar 'titulo' para ser reemplazado por 'Mi App', entonces llene el método con:

// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
case Trim(ParamName) of
  'titulo': AValue := 'Mi App';
else
  AValue := 'DESCONOCIDO';
end;

En OnReplaceTag, compruebe TagString y opcionalmente TagParams, luego asigne ReplaceText respectivamente. p.e., si quiere que la etiqueta 'FechaHora' se reemplace con el tiempo actual con el parámetro 'datetimeformat' para especificar cómo la fecha y hora deberían formatearse, luego llene en el método así:

// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
case Trim(TagString) of
  'FechaHora': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
else
  AValue := 'DESCONOCIDO';
end;

Al Nivel de la Acción

Cree/seleccione una acción, luego vaya al inspector de objetos. Verá una propiedad de subcomponente llamada Template. Esta plantilla es una instancia normal de TFPTemplate. Expándalo y llene en las propiedades como se explicó arriba. Ahora vaya a la pestaña Eventos, nuevamente expanda Template, verá los dos eventos. Llene el que está basado en su valor de propiedad de AllowTagParams.

NOTA: Si su Lazarus no puede autocompletar el evento, intente escribir el nombre manualmente en el cuadro de edición y luego clique el botón ···. Este es un bug en el Lazarus actual que deberá ser arreglado en el futuro.

A este nivel, la plantilla con contenido no se ajusta automáticamente como el manejador de petición. Podría cambiar en el futuro, pero trabajemos con su estado actual. Llene el evento de la acción OnRequest con:

with Actions.CurrentAction as TFPWebAction do
begin
  AResponse.Content := Template.GetContent;
end;
Handled := true;

El cast es requerido desde que CurrentAction es de tipo TCustomWebAction en vez de TFPWebAction. Sin eso, no podemos acceder a la propiedad Template.

A Nivel de Módulo

A nivel del módulo, actualmente tiene que codificarlo porque no se ha implementado soporte para RAD. La propiedad a vincular es ModuleTemplate. Esto es sin embargo una instancia irregular de TFPTemplate, pero una clase especial TFPWebTemplate que es descendiente de ella.

La idea aquí es que el módulo proporcione un diseño mientras que las acciones proporcionan contenido, con la capacidad de proporcionar variables adicionales. Por tanto, es buena idea mantener AllowTagParams como está y asignar el evento OnGetParam **del módulo** solamente. NO asigne OnGetParam del ModuleTemplate ya que nunca será llamado.

Una etiqueta de plantilla llamada 'contenido' se reemplazará automáticamente por el contendido producido por la acción, todo lo demás es reemplazado desde las variables internas de plantilla o desde OnGetParam.

Usando Plantillas por Separado

A pesar de la integración algo incompleta, nada le impide usar fpTemplate (o cualquier otra solución de plantillas) manualmente, fuera del soporte integrado. Esto podría incluso ser mejor en algunos casos ya que es modular.


Usando un objeto html Producer

Assuming that there is a TWebAction named act_get_my_html, you must ask to write in AResponse, the return of an internal bytes stream: when answering with html Producer objects solution, we must use the methods of the writer. It manages a memory stream for a purpose of rapidity. No string type here, like with the above fpTemplate solution which uses text \ string processing as in the regular Php way. The Producer writes in AResponse with a recursive " foreach " iteration, that traverses the polymorphic objects HTML Dom tree, composed with the hierarchy THtmlCustomElement = Class(TDOMElement) Classes (see unit htmlelements.pp). So, the AResponse is written by the parameter aWriter, with the following call:

procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
begin
  (* ask HTMLEntityProducer1 for the content of its html DOM elements: *)
  HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
  Handled := True;
end;

Said again, it's the job of the Producer to convert its internal memory stream into text in the response object: that's how this pattern was designed, although there is a text-html rendering method named ProduceContent for debugging purposes only. You can override this method if you are writing or debugging a Producer component.

Here is an example, always with an html Producer object, allowing to create a web application in the "RAD way", i.e. with the drag-drop of the HTMLEntityProducer component from the pallet:

procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
begin

  aWriter.startHeader;
    aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
    aWriter.title('My web page');
    aWriter.link('stylesheet','stylesheet.css','text/css','screen');
  aWriter.EndHeader;

  aWriter.Startbody;
    aWriter.Startparagraph;
      aWriter.heading2('Hello, world from inside §1:');
      aWriter.Text('Here is text written inside the current paragraph.');
    aWriter.Endparagraph;
    aWriter.paragraph('This is another text written inside a self "started and ended" paragraph.');
    aWriter.Startparagraph;
      aWriter.heading2('Hello, world from inside §2:');
      aWriter.Text('Here is the final text.');
      aWriter.Image.src := 'logo.png';
      AnotherProducer.WriteContent(aWriter);
    aWriter.Endparagraph;
  aWriter.Endbody;

end;

The TWebAction.ContentProducer property allows to couple a THTTPContentProducer to its web action exposed via a URI on the net.

[ToDo: there is no official documentation about the components "RAD way" of fpWeb (html producer, html provider, html adapter, html formatter, etc)]

Tips and Tricks

Returning Different HTTP Response Code

By default, fpWeb will return HTTP 200 OK to indicate successful request handling. This surely is not always the case, as user input might not be as what we expected. To do so, set AResponse.Code in your request handler to the code you want to return.

Redirect Request to Different URL

A common flow after a successful login is to redirect user to his account page. This can be done by calling AResponse.SendRedirect in your request handler, supplying the URL to redirect request to.

Serving Static Files (Embedded Web Server)

Remember the dialog in the #Hello, World! section after you select HTTP server Application? If you tick "Register location to serve files from" you can fill "Location" (the URI segment, must not contain any slashes) and "Directory" (physical directory in your computer, must exist at runtime) and the wizard will simply add:

RegisterFileLocation('<Location>','<Directory>');

to the beginning of your .lpr and add the unit fpwebfile to the uses clause. You can actually do this by hand anytime and also register multiple times for different locations / directories. After this you can request /<Location>/<any filename under Directory> and it will be served automatically. Note that the mimetype of the file is determined by fpmimetypes. Call MimeTypes.LoadFromFile with your mime.types file in order to give correct mimetype based on its extension. Otherwise, the file will always be served as application/octet-stream which means the browser will download it instead of interpreting it (especially important for JavaScript and CSS files).

You can grab a complete mime.types here http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co

In Lazarus 2.0.6 or newer you must add at the top of your program the full path of the mime.types file

begin
  MimeTypesFile := Application.Location + 'mime.txt';

Take into consideration that the default path coming with the project is lib\$(TargetCPU)-$(TargetOS)

For example httpproject\lib\i386-win32\mime.txt

Centralize Management of Configuration and Modules

By default, the program file (.lpr) is the one that contains protocol unit. This limits the ability to use Application object from other contexts such as from web modules. Fortunately, it's not difficult to refactor to have what we want. We remove RegisterHTTPModule calls from web modules' units and left out the .lpr to empty main block with single unit identifier in the uses clause, we name it: brokers. The unit contains:

unit Brokers;

{$mode objfpc}{$H+}

interface

{ $define cgi}
{ $define fcgi}
{$define httpapp}

uses
  CustWeb;

function GetApp: TCustomWebApplication; inline;

implementation

uses
  {$ifdef cgi}fpcgi{$endif}
  {$ifdef fcgi}fpfcgi{$endif}
  {$ifdef httpapp}fphttpapp{$endif}
  ,webmodule1
  ,webmodule2
  ;

function GetApp: TCustomWebApplication;
begin
  Result := Application;
end;

initialization
  RegisterHTTPModule('wm1', TWebModule1);
  RegisterHTTPModule('wm2', TWebModule2);
  {$ifndef cgi}
  Application.Port := 2015;
  {$endif}
  Application.Initialize;
  Application.Run;
end.

This way, we can control over web module registration and also provide an API to get Application object (casted as TCustomWebApplication), while still easily switch between protocol implementations, in a single place.

Terminating Gracefully (FastCGI / Embedded Web Server)

Instead of Ctrl+C-ing your app, there is a way for your app to terminate gracefully, doing whatever cleanup it needs, by calling Application.Terminate. You might need to use previous trick to easily access the Application object. A common implementation is to provide a specific password protected module / action that calls the Terminate method. You may choose whatever way you want, though.

Custom Exception Handler

[edit the May 1, 2020 => moved from a method pointer to a simple procedure.]

To override the default exception handler, which prints stacktrace whenever an exception is raised (i.e.: on HTTP 404 or 500), and thus not good for production, you must assign Application.OnShowRequestException.

This is a method so you will need to provide your procedure that implements the method and assign it by using the object. i.e.: if you have MyExceptionHandler as an object of TMyExceptionHandler which has MyShowRequestException method, you can assign it by:

Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;

don't forget to .Create() MyExceptionHandler BEFORE assigning above or you will get an EAccessViolation!

You must provide your global procedure that implements your own exception handler (in production, it is advisable to replace the call stack by an HTTP status code and its explanation). Then, you can override the default exception handler, by assigning it like this:

Application.OnShowRequestException := @MyShowRequestException;

Pure Hand Coding (No Form Designer Required)

It's not a must to use Lazarus' form designer to write an fpWeb application. You can use pure hand coding technique to write it. The secret lies in the 3rd parameter of RegisterHTTPModule : SkipStreaming. When this parameter is set to true, fpWeb will not search for .lfm resource. Therefore everything must be manually handled: property settings, event handlers, action registration, etc.

Note that it's logical to do what's usually done through object inspector in an overriden constructor. Inside it, call the inherited constructor supplying both AOwner and CreateMode as parameters. After that you can set properties, assign event handlers, etc. Example:

type
  THelloWorldModule = class(TFPWebModule)
    constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
    procedure Request(Sender: TObject; ARequest: TRequest;
      AResponse: TResponse; var Handled: Boolean);
  end;

constructor THelloWorldModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
begin
  inherited CreateNew(AOwner,CreateMode);
  OnRequest := @Request;
end;

procedure THelloWorldModule.Request(Sender: TObject; ARequest: TRequest;
 AResponse: TResponse; var Handled: Boolean);
begin
  AResponse.Content := 'Hello, World!';
  Handled := true;
end;


See also

Screenshot copy of one of the examples

  • XML or JSON, on ExtJS: if you want to use a more or less complex engine that is very "accomplished" regarding the rendering aspect of Javascript objects on the browser side, in addition to their functionalities (like ExtJS, for example, to display a db-aware grid, dynamic graphics, etc), this kind of solution very very often expects an XML or a JSON file. There are Adapter and Formatter classes (e.g. the classes TExtJSXMLWebdataInputAdaptor, TExtJSJSONDataFormatter).

Overall, the Adapter classes adapt the nodes of an incoming jSon or XML to a mapping of their TField database fields. And Formatter classes are mappers of each TField of a record to its node in jSon or XML format, before sending it out towards the browser.