Sign in Join
Wiki copytemporary

fpWeb Tutorial/es: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11Originalmente basado en el tutorial de fcl-web (en PDF) por un usuario del foro [Leledumbo](https://forum.lazarus.freepascal.org/index.php?action=profile;u=7523).
22
33## Introducción
44
55fpWeb 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.
66
77Este 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.
88
99## Arquitectura (lea POR FAVOR)
1010
1111Antes 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.
1212
1313### Aplicación
1414
1515La 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:
1616
1717- fpCGI -\> CGI
1818- fpFCGI -\> FastCGI
1919- fpApache (requiere httpd también) -\> módulo Apache
2020- fpHttpApp -\> servidor embebido
2121- microhttpapp -\> servidor embebido usando la biblioteca GNU libmicrohttp.
2222- fphttpsys -\> soporte de sistema Windows para el protocolo HTTP.
2323
2424A 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.
2525
2626### Módulos Web
2727
2828![](/wiki-images/5/50/FpWeb-overview.png)
2929
3030Las 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.
3131
3232## Instalación
3333
3434El paquete fpWeb para Lazarus no se instala por defecto (pero viene con él), para activar fpWeb:
3535
36361. Abra Lazarus y escoja '''Paquetes-\>Instalar/Desinstalar Paquetes’’’
37372. En el listbox disponible para la instalación, busque **weblaz** y presione **Instala selección**. Presione **Guardar y reconstruir IDE** y confirme con **Continuar**
38383. 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:
3939
4040
4141
4242![](/wiki-images/5/55/Fpwebtutorial_inst.png)
4343
4444## Módulos Especializados
4545
4646La clase *TFPWebModule* (usada abajo) es un simple ejemplo del módulo fpWEB que puede usarse para toda clase de peticiones HTTP.
4747
4848Sin embargo, fpWEB viene con algunos módulos especializados que tienen soporte extra para tareas especializadas:
4949
5050- La clase **TSimpleFileModule** en la unidad *fpwebfile.pp* puede usarse para enviar archivos. Sólo apunte a un directorio, y él hace el resto.
5151- La clase **TFPHTMLModule** en la unidad *fphtml.pp* puede usarse para producir HTML.
5252- La clase **TProxyWebModule** en la unidad *fpwebproxy.pp* es un proxy de redirección listo para usar.
5353- La clase **TFPWebProviderDataModule** en la unidad *fpwebdata.pp* sirve de datos en formato JSON que puede guardar ExtJS.
5454- La clase **TSQLDBRestModule** en la unidad *sqldbrestmodule.pp* implementa un servidor REST completo respaldado por SQLDB. Vea más información en [[SQLDBRestBridge]].
5555- La clase **TJSONRPCModule** en la unidad *webjsonrpc.pp* implementa un servicio JSON-RPC.
5656- La clase **TExtDirectModule** en la unidad *fpextdirect.pp* implementa una variante Ext.Direct de un servicio JSON-RPC.
5757
5858## Hola, Mundo!
5959
6060Creemos una aplicación Web sencilla. Como se enseñó comúnmente cuando se aprendía programación, "Hola, Mundo!" será nuestra primera app.\
6161
62621\. Abra Lazarus y escoja **Proyecto-\>Nuevo Proyecto** luego **HTTP server Application**
6363
6464
6565![Create new HTTP server application](/wiki-images/5/57/Fpwebtutorial_create_app.png)\
6666
67672\. Otro diálogo deberá aparecer para servir archivos estáticos, selección de puerto y multihilo. Sólo use por defecto el puerto 8080.
6868
6969
7070``` pascal
7171Puede que omita servir archivos estáticos (vaya a la sección de trucos y consejos si quiere saber más).
7272```
7373
7474![](/wiki-images/e/e6/Fpwebtutorial_port_selection.PNG)
7575
7676:**IMPORTANTE!:**
7777
7878
7979Si 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:
8080
8181``` pascal
8282Este 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.
8383```
8484
85853\. 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:
8686
8787
8888``` pascal
8989Application.LegacyRouting := true;
9090```
9191
9292la razón estará explicada en el capítulo #Enrutamiento.
9393
94944\. Cualquier cosa que escoja, clique "OK" y se le presentará en el módulo de aplicación fpWeb por defecto.\
95955\. Enfoque el módulo y muévase al **Inspector de Objetos**. Siéntase libre de renombre el módulo si así lo prefiere.\
96966\. 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.
9797
9898
9999![Creating web module's OnRequest handler in the object inspector](/wiki-images/8/8d/Fpwebtutorial_on_request.png)\
100100
101101
102102Será dirigido al editor de código fuente con el código:
103103
104104:
105105
106106``` pascal
107107procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
108108AResponse: TResponse; var Handled: Boolean);
109109begin
110110 |
111111end;
112112```
113113
114114
115115Escriba:
116116
117117:
118118
119119``` pascal
120120procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
121121AResponse: TResponse; var Handled: Boolean);
122122begin
123123 AResponse.Content := 'Hola, Mundo!';
124124 Handled := true;
125125end;
126126```
127127
1281287\. Luego ejecute su aplicación (o presione F9).\
1291298\. Abra su navegador y escriba:
130130
131131
132132<http://localhost:8080/>\
133133
1341349\. Deberá ver "Hola, Mundo!'.\
135135
136136En caso contrario, revise:
137137
138138- 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.
139139
140140- **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.
141141
142142- otra pista: pruebe sin un cortafuegos cargado en RAM (como una aplicación, como un servicio o daemon, o ambos).
143143
144144## Leyendo los datos GET & POST
145145
146146Es 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**.
147147
148148### Leyendo GET
149149
150150Los 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**.
151151
152152Reusando el código anterior, reemplace el cuerpo del método con:
153153
154154``` pascal
155155procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
156156 AResponse: TResponse; var Handled: Boolean);
157157var
158158 LName: String;
159159begin
160160 LName := ARequest.QueryFields.Values['Name']; // QueryFields para GET
161161 if LName = EmptyStr then
162162 with AResponse.Contents do begin // En el formulario <form> el
163163 Add('<form action="%s" method="GET"', [aRequest.URI]);// solicitante
164164 Add('<label for="name">Por favor dame tu nombre:</label>');// pondrá
165165 Add('<input type="text" name="name" id="name" />');// name="name"
166166 Add('<input type="submit" value="Send" />');
167167 Add('</form>');
168168 end
169169 else
170170 AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
171171 Handled := true; // <— Se dio buen manejo!
172172end;
173173```
174174
175175**ARequest.URI** es sólo una conveniencia referirse al URI actual, así que cuando cambie su módulo registrado, este código permanece igual.
176176
177177Note que como en Pascal, nos referimos a los datos se hace sin importar las mayúsculas/minúsculas.
178178
179179Como 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á
180180
181181` Por favor dame tu nombre:`
182182
183183y al final de la dirección coloque /?name=\<escriba algo aquí, p.e.: Fernando\>, lo que mostrará
184184
185185` Hola, Fernando!`
186186
187187### Leyendo POST
188188
189189POST 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:
190190
191191``` pascal
192192procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
193193 AResponse: TResponse; var Handled: Boolean);
194194var
195195 LName: String;
196196begin
197197 LName := ARequest.ContentFields.Values['Name']; // ContentFields para POST
198198 if LName = EmptyStr then
199199 with AResponse.Contents do
200200 begin
201201 Add('<form action="%d" method="POST"', [ARequest.URI]);
202202 Add('<label for="name">Por favor dame tu nombre:</label>');
203203 Add('<input type="text" name="name" id="name" />');
204204 Add('<input type="submit" value="Send" />');
205205 Add('</form>');
206206 end
207207 else
208208 AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
209209 Handled := true;
210210end;
211211```
212212
213213### Leyendo Subir Archivos
214214
215215Una 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:
216216
217217``` pascal
218218TUploadedFiles = Class(TCollection)
219219 ·
220220 ·
221221 ·
222222public
223223 Function First: TUploadedFile;
224224 Function Last: TUploadedFile;
225225 Function IndexOfFile(AName: String) : Integer;
226226 Function FileByName(AName: String) : TUploadedFile;
227227 Function FindFile(AName: String) : TUploadedFile;
228228 Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
229229end;
230230```
231231
232232// Cada **TUploadedFile** por sí mismo tiene varias propiedades:
233233
234234``` pascal
235235TUploadedFile = Class(TCollectionItem)
236236 ·
237237 ·
238238 ·
239239Public
240240 Destructor Destroy; override;
241241 Property FieldName: String Read FFieldName Write FFieldName;
242242 Property FileName: String Read FFileName Write FFileName;
243243 Property Stream: TStream Read GetStream;
244244 Property Size: Int64 Read FSize Write FSize;
245245 Property ContentType: String Read FContentType Write FContentType;
246246 Property Disposition: String Read FDisposition Write FDisposition;
247247 Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
248248 Property Description: String Read FDescription Write FDescription;
249249end;
250250```
251251
252252Deberí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.
253253
254254Nuevamente, reusemos el mismo manejador de petición, nótese que se puede optimizar el código usando una constante Req:
255255
256256``` pascal
257257procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
258258 AResponse: TResponse; var Handled: Boolean);
259259const
260260 Req = '<form id="form" action="%d" method=POST enctype="multipart' +
261261 '/form-data"><label for="name">Drag n drop or click to' +
262262 ' add file:</label><input type="file" name="input" />' +
263263 '<input type="submit" value="Send" /></form>';
264264var
265265 n, i: Integer;
266266 f: TUploadedFile;
267267begin
268268 n := ARequest.Files.Count;
269269 if n = 0 then
270270 with AResponse.Contents do
271271 Add(Req, [ARequest.URI])
272272 else begin
273273 f := ARequest.Files[0];
274274 AResponse.Contents.LoadFromStream(f.Stream);
275275 end;
276276 Handled := true;
277277end;
278278```
279279
280280arrastrar 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.
281281
282282## Cookies
283283
284284### Configuración
285285
286-<img src="cookie_session_Object_Pascal.png" title="El concepto de &quot;cookie&quot; (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes." width="450" alt="El concepto de &quot;cookie&quot; (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes." />\
286+![El concepto de "cookie" (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes.](cookie_session_Object_Pascal.png "El concepto de "cookie" (por el monstruo comegalletas?), inventado por Netscape en 1994, para permitir al servidor HTTP identificar a todos sus clientes.")\
287287El 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.
288288
289289Aquí un ejemplo:
290290
291291``` pascal
292292procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
293293 AResponse: TResponse; var Handled: Boolean);
294294var
295295 C: TCookie;
296296begin
297297 C := AResponse.Cookies.Add;
298298 C.Name := 'miCookie';
299299 C.Value := 'algunValor';
300300 Handled := true;
301301end;
302302```
303303
304304No 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:
305305
306306![](/wiki-images/6/69/Fpwebtutorial_cookie_set.png)
307307
308308Note 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.
309309
310310### Obteniéndola
311311
312312Una 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:
313313
314314![](/wiki-images/9/93/Fpwebtutorial_cookie_get.png)
315315
316316Afortunadamente, 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:
317317
318318``` pascal
319319procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
320320 AResponse: TResponse; var Handled: Boolean);
321321begin
322322 AResponse.Contents.Add('<p>La Cookie tiene:%d</p>', [ARequest.CookieFields.Values['miCookie']);
323323 Handled := true;
324324end;
325325```
326326
327327## Sesiones
328328
329329TFPWebModule 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.
330330
331331La 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**.
332332
333333### Activando la Sesión
334334
335335Para 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í.
336336
337337### Manipulación de las Variables de Sesión
338338
339339Las 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í:
340340
341341``` pascal
342342Session.Variables['miVar'] := miVar; // escritura
343343...
344344miVar := Session.Variables['miVar']; // lectura
345345```
346346
347347Poner una variable como cadena vacía **NO** la remueve. En vez de eso, si realmente quiere remover una variable, llame **Session.RemoveVariable**.
348348
349349### Terminando la Sesión
350350
351351Llame **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í.
352352
353353## Enrutamiento
354354
355355Desde 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:
356356
357357``` pascal
358358Application.LegacyRouting := true;
359359```
360360
361361en el archivo .lpr.
362362
363363### Mecanismo Anterior
364364
365365#### usando Múltiples Módulos
366366
367367Puede 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".
368368
369369![](/wiki-images/e/e5/Fpwebtutorial_new_module.png)
370370
371371luego clique en Aceptar.
372372
373373Dado 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:
374374
375375- /\<nombre del módulo\>
376376- /?modulo=\<nombre del módulo\>
377377
378378En 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**
379379
380380#### Usando Actions
381381
382382Hasta 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:
383383
384384- Módulo Contable
385385 - Action para Ingreso
386386 - Action para Salir
387387 - Action para Registro
388388- Módulo de Productos
389389 - Action para Crear
390390 - Action para Actualizar
391391 - Action para Eliminar
392392 - Action para Detalles
393393
394394##### Flujo de Manejo de Peticiones
395395
396396Antes 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.
397397
398398**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.
399399
400400En general, el flujo de peticiones es:
401401
402402![](/wiki-images/e/ea/Fpwebtutorial_request_flow.png)
403403
404404Note el recuadro "Our Concern", es eso a lo que le pondremos nuestra atención.
405405
406406##### Agregue Actions a los Módulos Web
407407
408408Para 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.
409409
410410![](/wiki-images/9/95/Fpwebtutorial_manage_actions.png)
411411
412412Una ventana popup deberá aparecer donde puede agregar, eliminar y cambiar el orden de las acciones.
413413
414414![](/wiki-images/d/db/Fpwebtutorial_edit_actions.png)
415415
416416Presione 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.
417417
418418![](/wiki-images/2/2e/Fpwebtutorial_action_onrequest.png)
419419
420420Se 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!".
421421
422422Recuerde 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.
423423
424424Ejecute 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\>.
425425
426426Si tiene múltiples módulos, entonces tiene una variedad de opciones:
427427
428428- /\<nombre del módulo\>/\<nombre de la acción\>
429429- /\<nombre del módulo\>?action=\<nombre de la acción\>
430430- /\<nombre de la acción\>?module=\<nombre del módulo\>
431431- /?module=\<nombre del módulo\>&action=\<nombre de la acción\>
432432
433433Note 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**.
434434
435435Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
436436
437437<thead>
438438
439439</thead>
440440<tbody>
441441
442442</tbody>
443443
444444##### Acción por Defecto
445445
446446Recuerde 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:
447447
448448![](/wiki-images/4/44/Fpwebtutorial_request_delegation_flow.png)
449449
450450Dos 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.
451451
452452Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
453453
454454<thead>
455455
456456</thead>
457457<tbody>
458458
459459</tbody>
460460
461461<thead>
462462
463463</thead>
464464<tbody>
465465
466466</tbody>
467467
468468En 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.
469469
470470### Nuevo Mecanismo
471471
472472El nuevo mecanismo es extremadamente flexible y funciona incluso sin módulos de datos (el viejo mecanismo sólo funciona con módulos de datos).
473473
474474Una 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.
475475
476476#### Sintáxis de la Ruta
477477
478478El 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:
479479
480480- /api/v1/products
481481- /api/v1/products/1
482482- /api/v1/products/1/clone
483483- /api/v1/products/something/else/that/is/really/long/and/silly
484484
485485pero no con:
486486
487487- /api/v1
488488- /excuse/me/api/v1/products
489489
490490Básicamente hay sólo 3 caracteres especiales:
491491
492492- *  que denota 0 o más rutas
493493
494494-
495495 param  denota una parte
496496
497497- /  separador de partes
498498
499499su ruta se compondrá de estos 3 caracteres, además cualquier cosa que conforme una ruta.
500500
501501#### Registrar una Ruta
502502
503503El 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:
504504
505505- Procedimiento Callback
506506
507507``` pascal
508508TRouteCallback = Procedure(ARequest: TRequest; AResponse);
509509```
510510
511511- Evento Callback
512512
513513``` pascal
514514TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
515515```
516516
517517- Objeto que satisface una interfaz (CORBA)
518518
519519``` pascal
520520IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
521521 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
522522end;
523523```
524524
525525- Objeto que extiende una clase de enrutador abstracto
526526
527527``` pascal
528528TRouteObject = Class(TObject, IRouteInterface)
529529Public
530530 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
531531end;
532532
533533TRouteObjectClass = Class of TRouteObject;
534534```
535535
536536Por 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.
537537
538538El 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á.
539539
540540En este nuevo mecanismo, un simple programa Hola, Mundo! puede ser tan simple como:
541541
542542``` pascal
543543uses
544544 fphttpapp, httpdefs, httproute;
545545procedure DigaHola(ARequest:TRequest; AResponse : TResponse);
546546begin
547547 AResponse.Content:='<html><body><h1>Hola, Mundo!</h1></body></html>';
548548end;
549549
550550begin
551551 HTTPRouter.RegisterRoute('*', @DigaHola);
552552 Application.Port := 9000;
553553 Application.Initialize;
554554 Application.Run;
555555end.
556556```
557557
558558#### Ejemplo Webserver
559559
560560Este es un ejemplo de un servidor simple, multi-plataforma, multi-hilos.
561561
562562``` pascal
563563program webserver;
564564
565565{$mode objfpc}{$H+}
566566
567567uses
568568 {$ifdef UNIX}
569569 cthreads, cmem,
570570 {$endif}
571571 fphttpapp, httpdefs, httproute;
572572
573573procedure route1(aReq: TRequest; aResp: TResponse);
574574begin
575575 aResp.content:='<html><body><h1>Ruta 1 Por Defecto</h1></body></html>'
576576end;
577577
578578procedure route2(aReq: TRequest; aResp: TResponse);
579579begin
580580 aResp.content:='<html><body><h1>Ruta 2</h1></body></html>'
581581end;
582582
583583begin
584584 HTTPRouter.registerRoute('/', @route1, true);
585585 HTTPRouter.registerRoute('/route2', @route2);
586586 Application.port := 8080;
587587 Application.threaded := true;
588588 Application.initialize;
589589 Application.run;
590590end.
591591```
592592
593593Para abrir la "Ruta 1" teclee en su navegador la URL: <http://localhost:8080>
594594
595595Para abrir la "Ruta 2" teclee en su navegador la URL: <http://localhost:8080/route2>
596596
597597Puede cambiar localhost por 127.0.0.1
598598
599599## Usando Plantillas
600600
601601fpWeb 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.
602602
603603Hay 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.
604604
605605Hay 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.
606606
607607La 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.
608608
609609Las dos propiedades: **StartDelimiter** y **EndDelimiter** definen cómo el motor debería reconocer una etiqueta de plantilla. Por ejemplo, si se tiene:
610610
611611- StartDelimiter = '{+'
612612- EndDelimiter = '+}'
613613
614614entonces 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'.
615615
616616En 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:
617617
618618- ParamStartDelimiter = '\[-'
619619- ParamEndDelimiter = '-\]'
620620- ParamValueSeparator = '='
621621
622622entonces 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.
623623
624624Como consecuencia de distintas formas de operar, el evento central donde la etiqueta de plantilla trabaja es diferente también.
625625
626626El no-parametrizado usará **OnGetParam** mientras que el parametrizado usará **OnReplaceTag**. Los dos tienen por supuesto, diferentes interfaces:
627627
628628``` pascal
629629Type
630630 // OnGetParam: para simple soporte exclusivo de etiquetas de plantilla (ej: {Nombre})
631631 TGetParamEvent = Procedure(
632632 Sender: TObject;
633633 Const ParamName: String;
634634 Out AValue: String
635635 ) Of Object;
636636 // OnReplaceTag: para etiquetas con soporte de parámetros
637637 TReplaceTagEvent = Procedure(
638638 Sender: TObject;
639639 Const TagString: String;
640640 TagParams: TStringList;
641641 Out ReplaceText: String
642642 ) Of Object;
643643```
644644
645645En **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:
646646
647647``` pascal
648648// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
649649case Trim(ParamName) of
650650 'titulo': AValue := 'Mi App';
651651else
652652 AValue := 'DESCONOCIDO';
653653end;
654654```
655655
656656En **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í:
657657
658658``` pascal
659659// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
660660case Trim(TagString) of
661661 'FechaHora': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
662662else
663663 AValue := 'DESCONOCIDO';
664664end;
665665```
666666
667667### Al Nivel de la Acción
668668
669669Cree/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**.
670670
671671NOTA: 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.
672672
673673A 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:
674674
675675``` pascal
676676with Actions.CurrentAction as TFPWebAction do
677677begin
678678 AResponse.Content := Template.GetContent;
679679end;
680680Handled := true;
681681```
682682
683683El cast es requerido desde que **CurrentAction** es de tipo **TCustomWebAction** en vez de **TFPWebAction**. Sin eso, no podemos acceder a la propiedad **Template**.
684684
685685### A Nivel de Módulo
686686
687687A 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.
688688
689689La 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.
690690
691691Una 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**.
692692
693693### Usando Plantillas por Separado
694694
695695A 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.
696696
697697## Usando un objeto html Producer
698698
699699Suponiendo que hay una TWebAction llamada act_get_my_html, debe solicitar escribir en AResponse, el retorno de una corriente de bytes interna: al responder con la solución de objetos html Producer, debemos usar los métodos del escritor. Maneja una corriente de memoria con el propósito de mayor rapidez. No hay tipo de cadena aquí, como con la solución fpTemplate anterior que utiliza el procesamiento de texto \\ cadena como en la forma normal de PHP. El Productor escribe en AResponse con una iteración recursiva " para cada elemento ", que atraviesa el árbol HTML DOM de objetos polimórficos, compuesto por la jerarquía de clases THtmlCustomElement = Class(TDOMElement) (vea la unidad htmlelements.pp). Entonces, AResponse se escribe mediante el parámetro aWriter, con la siguiente llamada:
700700
701701``` pascal
702702procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
703703begin
704704 (* pide a HTMLEntityProducer1 el contenido de sus elementos html DOM: *)
705705 HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
706706 Handled := True;
707707end;
708708```
709709
710710Dicho de nuevo, es trabajo del Productor convertir su corriente interna de memoria a texto en el objeto de la respuesta: así es como se diseñó este patrón, aunque hay un método de representación de texto-html llamado ProduceContent solo con fines de depuración. Puede sobrecargar este método si está escribiendo o depurando un componente Producer.
711711
712712Aquí hay un ejemplo, con un objeto Producer html, que permite crear una aplicación web al "modo RAD", es decir, con el arrastrar y soltar del componente HTMLEntityProducer desde la paleta de componentes:
713713
714714``` pascal
715715procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
716716begin // HTMLEntityProducer has Entity := heHtml
717717 aWriter.StartHead; // .startHeader;
718718 aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
719719 aWriter.title('My web page');
720720 aWriter.link('stylesheet','stylesheet.css','text/css','screen');
721721 aWriter.EndHead; // .endHeader;
722722
723723 aWriter.Startbody;
724724 aWriter.StartParagraph;
725725 aWriter.heading2('Hola, mundo desde adentro §1:');
726726 aWriter.Text('Esto es texto escrito dentro del párrafo actual.');
727727 aWriter.EndParagraph;
728728 aWriter.paragraph('Otro texto dentro de un párrafo "iniciado y terminado" por sí mismo.');
729729 aWriter.StartP; // .StartParagraph;
730730 aWriter.tagH2('Hola, mundo desde adentro §2:'); // .heading2;
731731 aWriter.Text('Aquí está el texto final.');
732732 aWriter.Image.src := 'logo.png';
733733 AnotherProducer.WriteContent(aWriter);
734734 aWriter.EndP; // .EndParagraph;
735735 // you can use tags and other THtmlCustomElements
736736 aWriter.tagP([aWriter.bold('Hola'), aWriter.tagQ('Mundo!')]);
737737 aWriter.Text('<u>Hola,</u><i>mundo!</i>');
738738 aWriter.Endbody;
739739
740740end;
741741```
742742
743743La propiedad TWebAction.ContentProducer permite acoplar un THTTPContentProducer a su acción web expuesta a través de una URI en la red.
744744
745745\[PorHacer: no hay documentación oficial acerca de los componentes "RAD way" de ftweb (html producer, html provider, html adapter, html formatter, etc)\]
746746
747747## Trucos y Consejos
748748
749749### Retornando Distintos Códigos de Respuesta HTTP
750750
751751Por defecto, fpWeb retornará HTTP 200 OK para indicar un manejo de respuesta exitoso. Esto de seguro no siempre es el caso, como las entradas del usuario no puedan ser lo que esperamos. Para hacerlo, modifique **AResponse.Code** en su manejador de peticiones en el código que desee retornar.
752752
753753### Redirigir Peticiones a Diferentes URLs
754754
755755Un flujo común después de un login es redirigir al usuario a su página de cuenta. Esto se puede hacer llamando AResponse.SendRedirect en su manejador de petición, ordenando a la URL que redirija la petición.
756756
757757### Sirviendo Archivos Estáticos (Servidor Web Embebido)
758758
759759Recuerda el diálogo en la sección #Hola, Mundo! después que seleccione la Aplicación servidora HTTP? Si escogió "Registre ubicación de dónde servir archivos" puede llenar "Ubicación" (el segmento URI, no debe contener ningún slash) y "Directorio" (directorio físico en su computador, debe existir en tiempo de ejecución) y el asistente simplemente agrega:
760760
761761``` pascal
762762RegisterFileLocation('<Location>','<Directory>');
763763```
764764
765765al comienzo de su archivo .lpr y agrega la unidad **fpwebfile** a su cláusula uses. Puede actualmente hacer esto a mano en cualquier momento y también registrar múltiples veces para diferentes ubicaciones / directorios. Después de esto puede pedir **/\<Ubicación\>/\<cualquier nombre de archivo dentro del Directorio\>** y será servido automáticamente. Note que el mimetype del archivo se determina por fpmimetypes. Llame **MimeTypes.LoadFromFile** con su archivo mime.types para dar un mimetype correcto basado en su extensión. De otra forma, el archivo siempre se servirá como **application/octet-stream** el cual significa que el navegador lo descargará en vez de interpretarlo (especialmente importante para JavaScript y archivos CSS).
766766
767767Puede aprovechar los mime.types completos aquí <http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co>
768768
769769En Lazarus 2.0.6 o más reciente versión debe agregar en el comienzo del programa la ruta completa de los archivos mime.type
770770
771771``` pascal
772772begin
773773 MimeTypesFile := Application.Location + 'mime.txt';
774774```
775775
776776Tenga en cuenta que la ruta por defecto que viene con el proyecto es lib\\\$(TargetCPU)-\$(TargetOS)
777777
778778por ejemplo httpproject\lib\i386-win32\mime.txt
779779
780780### Centralice la Administración de la Configuración y los Módulos
781781
782782Por defecto, el archivo (.lpr) es el único que contiene la unidad de protocolo. Esto limita la habilidad de usar el objeto **Application** de otros contextos como los módulos web. Afortunadamente, esto no dificulta refactorizar lo que queremos tener. Removemos las llamadas **RegisterHTTPModule** desde las unidades de los módulos web dejamos al .lpr que extraiga para vaciar el bloque principal con un identificador simple de unidad en la cláusula uses, lo nombramos: agentes (Brokers). La unidad contiene:
783783
784784``` pascal
785785unit Brokers;
786786
787787{$mode objfpc}{$H+}
788788
789789interface
790790
791791{ $define cgi}
792792{ $define fcgi}
793793{$define httpapp}
794794
795795uses
796796 CustWeb;
797797
798798function GetApp: TCustomWebApplication; inline;
799799
800800implementation
801801
802802uses
803803 {$ifdef cgi}fpcgi{$endif}
804804 {$ifdef fcgi}fpfcgi{$endif}
805805 {$ifdef httpapp}fphttpapp{$endif}
806806 ,webmodule1
807807 ,webmodule2
808808 ;
809809
810810function GetApp: TCustomWebApplication;
811811begin
812812 Result := Application;
813813end;
814814
815815initialization
816816 RegisterHTTPModule('wm1', TWebModule1);
817817 RegisterHTTPModule('wm2', TWebModule2);
818818 {$ifndef cgi}
819819 Application.Port := 2015;
820820 {$endif}
821821 Application.Initialize;
822822 Application.Run;
823823end.
824824```
825825
826826De esta forma, podemos controlar el registro del módulo web y también proveer un API para obtener el objeto **Application** (enmarcado como **TCustomWebApplication**), mientras todavía de forma fácil cambie entre los protocolos de implementación, en un sólo lugar.
827827
828828### Terminando Adecuadamente (FastCGI / Servidor Web Embebido)
829829
830830En vez de terminar la aplicación mediante Ctrl+C, hay una forma para que su aplicación termine adecuadamente, haciendo todo lo necesario para limpiar lo que necesite, llamando **Application.Terminate**. Podría necesitar usar el truco previo para acceder fácilmente al objeto **Application**. Una implementación común es proveer un módulo / acción específico protegido con contraseña que llame el método Terminate. Puede escoger la forma que prefiera.
831831
832832### Manejador de Excepción Personalizada
833833
834834\[edición de Mayo 1 de 2020 =\> movido desde un puntero de método a un simple de procedimiento.\]
835835
836836Para pasar por delante del manejador de excepción por defecto, el cual imprime stacktrace cada vez que una excepción se genera (p.e.: HTTP 404 ó 500), y así no es bueno para la producción, debe asignar un **Application.OnShowRequestException**.
837837
838838Este es un método que necesitará proveer su procedimiento que implemente el método y lo asigne usando el objeto. p.e.: si tiene **MyExceptionHandler** como un objeto **TMyExceptionHandler** el cual tiene un método **MyShowRequestException**, puede asignarlo mediante:
839839
840840``` pascal
841841Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;
842842```
843843
844844no olvide **.Create()** **MyExceptionHandler** ANTES de asignar arriba u obtendrá un **EAccessViolation**!
845845
846846Debe proveer un procedimiento global que implemente su propio manejador de excepción (en producción, es You must provide your global procedure that implements your own exception handler (in production, es recomendable reemplazar la pila de llamadas por un código de estado HTTP y su explicación). Luego puede pasar por delante el manejador de excepción por defecto, asignándolo así:
847847
848848``` pascal
849849Application.OnShowRequestException := @MyShowRequestException;
850850```
851851
852852### Codificando Sólo a Mano (Sin Diseñador de Formularios)
853853
854854No es una obligación usar el diseñador de formularios para escribir una aplicación fpWeb. Puede usar técnicas de codificación a mano para escribirla. El secreto radica en el 3er parámetro de **RegisterHTTPModule** : **SkipStreaming**. Cuando este parámetro se pone en verdadero, fpWeb no buscará el recurso .lfm. Por tanto todo debe manejarse de forma manual: las opciones de propiedades, los maneadores de eventos, registro de acciones, etc.
855855
856856Note que es lógico hacer lo que usualmente funciona a través del inspector de objetos en un constructor solapado. Dentro de él, llame al constructor heredado supliendo los parámetros AOwner y CreateMode. Después de eso puede poner propiedades, asignar maneadores de eventos, etc. Por ejemplo:
857857
858858``` pascal
859859type
860860 TModuloHolaMundo = class(TFPWebModule)
861861 constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
862862 procedure Request(Sender: TObject; ARequest: TRequest;
863863 AResponse: TResponse; var Handled: Boolean);
864864 end;
865865
866866constructor TModuloHolaMundo.CreateNew(AOwner: TComponent; CreateMode: Integer);
867867begin
868868 inherited CreateNew(AOwner, CreateMode);
869869 OnRequest := @Request;
870870end;
871871
872872procedure TModuloHolaMundo.Request(Sender: TObject; ARequest: TRequest;
873873 AResponse: TResponse; var Handled: Boolean);
874874begin
875875 AResponse.Content := 'Hola, Mundo!';
876876 Handled := true;
877877end;
878878```
879879
880880## Vea También
881881
882882- [Posiblemente el mismo contenido](https://github.com/leledumbo/fpWebTutorial)
883883- [Lazarus para la web (tutorial)](https://www.freepascal.org/~michael/articles/#web1)
884884- [Lazarus para la web: Sesiones y Plantillas (tutorial)](https://www.freepascal.org/~michael/articles/#web2) Este artículo explica una Clase TSQLDBSession que maneja una conexión SQL, que se hereda de una sesión web (para manejar el historial de las variables de sesión web, dentro de una sesión de conexión a una base de datos teniendo capacidades de bloqueo de fila).
885885- los ejemplos - lea los archivos de texto - en su directorio ...\fpc\x.x.x\source\packages\fcl-web\examples.\
886886
887-<img src="cgi-listrecords.png" title="Screenshot copia de uno de los ejemplos" width="50" />
887+![](/wiki-images/c/c0/Cgi-listrecords.png)
888888
889889- XML o JSON, en ExtJS: si quiere usar un motor más o menos complejo que sea bastante "cumplidor" sin importar el aspecto que muestran los objetos Javascript en el lado del navegador, además de sus funcionalidades (como ExtJS, por ejemplo, para mostrar una grilla accesible a la base de datos, gráficos dinámicos, etc), esta clase de solución muchas veces espera por un archivo XML o JSON. Hay clases adaptadoras y formateadoras (p.e. las clases TExtJSXMLWebdataInputAdaptor, TExtJSJSONDataFormatter).
890890
891891Sobre todo, las clases adaptadoras "adaptan" los nodos de un JSON o XML entrante a un mapeo de sus campos de base de datos TField. Y las clases formateadoras son mapeadores de cada Field de un registro en su nodo en formato JSON o XML, antes de enviarlo hacia el navegador.\
892892* [Formatos de datos Web en Lazarus/FPC](https://www.freepascal.org/~michael/articles/#webdata)
893893
894894- [Construcción GUI práctica en ExtJS](https://www.freepascal.org/~michael/articles/#extjs1)
895895- [Combinando aplicaciones web ExtJS y Pascal: ExtPascal](https://www.freepascal.org/~michael/articles/#extpascal)
896896
897897traducido por edgarrod71@gmail.com
898898
899899---
900900*From the Free Pascal wiki, page "fpWeb Tutorial/es", as it stood on 2023-03-19 after a change by Edgardager, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history