Sign in Join
Wiki copytemporary

fpWeb Tutorial/es: what changed

Revision 3 to 4. 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```text
182182 Por favor dame tu nombre:
183183```
184184
185185y al final de la dirección coloque /?name=\<escriba algo aquí, p.e.: Fernando\>, lo que mostrará
186186
187187```text
188188 Hola, Fernando!
189189```
190190
191191### Leyendo POST
192192
193193POST 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:
194194
195195``` pascal
196196procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
197197 AResponse: TResponse; var Handled: Boolean);
198198var
199199 LName: String;
200200begin
201201 LName := ARequest.ContentFields.Values['Name']; // ContentFields para POST
202202 if LName = EmptyStr then
203203 with AResponse.Contents do
204204 begin
205205 Add('<form action="%d" method="POST"', [ARequest.URI]);
206206 Add('<label for="name">Por favor dame tu nombre:</label>');
207207 Add('<input type="text" name="name" id="name" />');
208208 Add('<input type="submit" value="Send" />');
209209 Add('</form>');
210210 end
211211 else
212212 AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
213213 Handled := true;
214214end;
215215```
216216
217217### Leyendo Subir Archivos
218218
219219Una 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:
220220
221221``` pascal
222222TUploadedFiles = Class(TCollection)
223223 ·
224224 ·
225225 ·
226226public
227227 Function First: TUploadedFile;
228228 Function Last: TUploadedFile;
229229 Function IndexOfFile(AName: String) : Integer;
230230 Function FileByName(AName: String) : TUploadedFile;
231231 Function FindFile(AName: String) : TUploadedFile;
232232 Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
233233end;
234234```
235235
236236// Cada **TUploadedFile** por sí mismo tiene varias propiedades:
237237
238238``` pascal
239239TUploadedFile = Class(TCollectionItem)
240240 ·
241241 ·
242242 ·
243243Public
244244 Destructor Destroy; override;
245245 Property FieldName: String Read FFieldName Write FFieldName;
246246 Property FileName: String Read FFileName Write FFileName;
247247 Property Stream: TStream Read GetStream;
248248 Property Size: Int64 Read FSize Write FSize;
249249 Property ContentType: String Read FContentType Write FContentType;
250250 Property Disposition: String Read FDisposition Write FDisposition;
251251 Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
252252 Property Description: String Read FDescription Write FDescription;
253253end;
254254```
255255
256256Deberí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.
257257
258258Nuevamente, reusemos el mismo manejador de petición, nótese que se puede optimizar el código usando una constante Req:
259259
260260``` pascal
261261procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
262262 AResponse: TResponse; var Handled: Boolean);
263263const
264264 Req = '<form id="form" action="%d" method=POST enctype="multipart' +
265265 '/form-data"><label for="name">Drag n drop or click to' +
266266 ' add file:</label><input type="file" name="input" />' +
267267 '<input type="submit" value="Send" /></form>';
268268var
269269 n, i: Integer;
270270 f: TUploadedFile;
271271begin
272272 n := ARequest.Files.Count;
273273 if n = 0 then
274274 with AResponse.Contents do
275275 Add(Req, [ARequest.URI])
276276 else begin
277277 f := ARequest.Files[0];
278278 AResponse.Contents.LoadFromStream(f.Stream);
279279 end;
280280 Handled := true;
281281end;
282282```
283283
284284arrastrar 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.
285285
286286## Cookies
287287
288288### Configuración
289289
290290![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.")\
291291El 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.
292292
293293Aquí un ejemplo:
294294
295295``` pascal
296296procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
297297 AResponse: TResponse; var Handled: Boolean);
298298var
299299 C: TCookie;
300300begin
301301 C := AResponse.Cookies.Add;
302302 C.Name := 'miCookie';
303303 C.Value := 'algunValor';
304304 Handled := true;
305305end;
306306```
307307
308308No 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:
309309
310310![](/wiki-images/6/69/Fpwebtutorial_cookie_set.png)
311311
312312Note 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.
313313
314314### Obteniéndola
315315
316316Una 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:
317317
318318![](/wiki-images/9/93/Fpwebtutorial_cookie_get.png)
319319
320320Afortunadamente, 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:
321321
322322``` pascal
323323procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
324324 AResponse: TResponse; var Handled: Boolean);
325325begin
326326 AResponse.Contents.Add('<p>La Cookie tiene:%d</p>', [ARequest.CookieFields.Values['miCookie']);
327327 Handled := true;
328328end;
329329```
330330
331331## Sesiones
332332
333333TFPWebModule 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.
334334
335335La 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**.
336336
337337### Activando la Sesión
338338
339339Para 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í.
340340
341341### Manipulación de las Variables de Sesión
342342
343343Las 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í:
344344
345345``` pascal
346346Session.Variables['miVar'] := miVar; // escritura
347347...
348348miVar := Session.Variables['miVar']; // lectura
349349```
350350
351351Poner una variable como cadena vacía **NO** la remueve. En vez de eso, si realmente quiere remover una variable, llame **Session.RemoveVariable**.
352352
353353### Terminando la Sesión
354354
355355Llame **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í.
356356
357357## Enrutamiento
358358
359359Desde 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:
360360
361361``` pascal
362362Application.LegacyRouting := true;
363363```
364364
365365en el archivo .lpr.
366366
367367### Mecanismo Anterior
368368
369369#### usando Múltiples Módulos
370370
371371Puede 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".
372372
373373![](/wiki-images/e/e5/Fpwebtutorial_new_module.png)
374374
375375luego clique en Aceptar.
376376
377377Dado 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:
378378
379379- /\<nombre del módulo\>
380380- /?modulo=\<nombre del módulo\>
381381
382382En 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**
383383
384384#### Usando Actions
385385
386386Hasta 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:
387387
388388- Módulo Contable
389389 - Action para Ingreso
390390 - Action para Salir
391391 - Action para Registro
392392- Módulo de Productos
393393 - Action para Crear
394394 - Action para Actualizar
395395 - Action para Eliminar
396396 - Action para Detalles
397397
398398##### Flujo de Manejo de Peticiones
399399
400400Antes 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.
401401
402402**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.
403403
404404En general, el flujo de peticiones es:
405405
406406![](/wiki-images/e/ea/Fpwebtutorial_request_flow.png)
407407
408408Note el recuadro "Our Concern", es eso a lo que le pondremos nuestra atención.
409409
410410##### Agregue Actions a los Módulos Web
411411
412412Para 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.
413413
414414![](/wiki-images/9/95/Fpwebtutorial_manage_actions.png)
415415
416416Una ventana popup deberá aparecer donde puede agregar, eliminar y cambiar el orden de las acciones.
417417
418418![](/wiki-images/d/db/Fpwebtutorial_edit_actions.png)
419419
420420Presione 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.
421421
422422![](/wiki-images/2/2e/Fpwebtutorial_action_onrequest.png)
423423
424424Se 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!".
425425
426426Recuerde 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.
427427
428428Ejecute 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\>.
429429
430430Si tiene múltiples módulos, entonces tiene una variedad de opciones:
431431
432432- /\<nombre del módulo\>/\<nombre de la acción\>
433433- /\<nombre del módulo\>?action=\<nombre de la acción\>
434434- /\<nombre de la acción\>?module=\<nombre del módulo\>
435435- /?module=\<nombre del módulo\>&action=\<nombre de la acción\>
436436
437437Note 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**.
438438
439439Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
440440
441-<thead>
442-
443-</thead>
444-<tbody>
445-
446-</tbody>
441+| /<nombre de la acción o del módulo> | | Application.PreferModuleName | |
442+|---|---|---|---|
443+| true | false | | |
444+| Application.AllowDefaultModule | true | /<nombre del módulo> | /<módulo por defecto>/<nombre de la acción> |
445+| | false | /<nombre del módulo> | ERROR |
447446
448447##### Acción por Defecto
449448
450449Recuerde 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:
451450
452451![](/wiki-images/4/44/Fpwebtutorial_request_delegation_flow.png)
453452
454453Dos 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.
455454
456455Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
457456
458-<thead>
459-
460-</thead>
461-<tbody>
462-
463-</tbody>
464-
465-<thead>
466-
467-</thead>
468-<tbody>
457+| Petición con un nombre de acción no-válido | | DefActionWhenUnknown | |
458+|---|---|---|---|
459+| true | false | | |
460+| 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> |
461+| | 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> |
469462
470-</tbody>
463+| Petición sin nombre de acción, p.e.: / | | DefActionWhenUnknown | |
464+|---|---|---|---|
465+| true | false | | |
466+| Action.Default | true | Petición manejada por la acción por defecto | Petición manejada por la acción por defecto |
467+| | 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 |
471468
472469En 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.
473470
474471### Nuevo Mecanismo
475472
476473El nuevo mecanismo es extremadamente flexible y funciona incluso sin módulos de datos (el viejo mecanismo sólo funciona con módulos de datos).
477474
478475Una 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.
479476
480477#### Sintáxis de la Ruta
481478
482479El 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:
483480
484481- /api/v1/products
485482- /api/v1/products/1
486483- /api/v1/products/1/clone
487484- /api/v1/products/something/else/that/is/really/long/and/silly
488485
489486pero no con:
490487
491488- /api/v1
492489- /excuse/me/api/v1/products
493490
494491Básicamente hay sólo 3 caracteres especiales:
495492
496493- *  que denota 0 o más rutas
497494
498495-
499496 param  denota una parte
500497
501498- /  separador de partes
502499
503500su ruta se compondrá de estos 3 caracteres, además cualquier cosa que conforme una ruta.
504501
505502#### Registrar una Ruta
506503
507504El 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:
508505
509506- Procedimiento Callback
510507
511508``` pascal
512509TRouteCallback = Procedure(ARequest: TRequest; AResponse);
513510```
514511
515512- Evento Callback
516513
517514``` pascal
518515TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
519516```
520517
521518- Objeto que satisface una interfaz (CORBA)
522519
523520``` pascal
524521IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
525522 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
526523end;
527524```
528525
529526- Objeto que extiende una clase de enrutador abstracto
530527
531528``` pascal
532529TRouteObject = Class(TObject, IRouteInterface)
533530Public
534531 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
535532end;
536533
537534TRouteObjectClass = Class of TRouteObject;
538535```
539536
540537Por 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.
541538
542539El 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á.
543540
544541En este nuevo mecanismo, un simple programa Hola, Mundo! puede ser tan simple como:
545542
546543``` pascal
547544uses
548545 fphttpapp, httpdefs, httproute;
549546procedure DigaHola(ARequest:TRequest; AResponse : TResponse);
550547begin
551548 AResponse.Content:='<html><body><h1>Hola, Mundo!</h1></body></html>';
552549end;
553550
554551begin
555552 HTTPRouter.RegisterRoute('*', @DigaHola);
556553 Application.Port := 9000;
557554 Application.Initialize;
558555 Application.Run;
559556end.
560557```
561558
562559#### Ejemplo Webserver
563560
564561Este es un ejemplo de un servidor simple, multi-plataforma, multi-hilos.
565562
566563``` pascal
567564program webserver;
568565
569566{$mode objfpc}{$H+}
570567
571568uses
572569 {$ifdef UNIX}
573570 cthreads, cmem,
574571 {$endif}
575572 fphttpapp, httpdefs, httproute;
576573
577574procedure route1(aReq: TRequest; aResp: TResponse);
578575begin
579576 aResp.content:='<html><body><h1>Ruta 1 Por Defecto</h1></body></html>'
580577end;
581578
582579procedure route2(aReq: TRequest; aResp: TResponse);
583580begin
584581 aResp.content:='<html><body><h1>Ruta 2</h1></body></html>'
585582end;
586583
587584begin
588585 HTTPRouter.registerRoute('/', @route1, true);
589586 HTTPRouter.registerRoute('/route2', @route2);
590587 Application.port := 8080;
591588 Application.threaded := true;
592589 Application.initialize;
593590 Application.run;
594591end.
595592```
596593
597594Para abrir la "Ruta 1" teclee en su navegador la URL: <http://localhost:8080>
598595
599596Para abrir la "Ruta 2" teclee en su navegador la URL: <http://localhost:8080/route2>
600597
601598Puede cambiar localhost por 127.0.0.1
602599
603600## Usando Plantillas
604601
605602fpWeb 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.
606603
607604Hay 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.
608605
609606Hay 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.
610607
611608La 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.
612609
613610Las dos propiedades: **StartDelimiter** y **EndDelimiter** definen cómo el motor debería reconocer una etiqueta de plantilla. Por ejemplo, si se tiene:
614611
615612- StartDelimiter = '{+'
616613- EndDelimiter = '+}'
617614
618615entonces 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'.
619616
620617En 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:
621618
622619- ParamStartDelimiter = '\[-'
623620- ParamEndDelimiter = '-\]'
624621- ParamValueSeparator = '='
625622
626623entonces 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.
627624
628625Como consecuencia de distintas formas de operar, el evento central donde la etiqueta de plantilla trabaja es diferente también.
629626
630627El no-parametrizado usará **OnGetParam** mientras que el parametrizado usará **OnReplaceTag**. Los dos tienen por supuesto, diferentes interfaces:
631628
632629``` pascal
633630Type
634631 // OnGetParam: para simple soporte exclusivo de etiquetas de plantilla (ej: {Nombre})
635632 TGetParamEvent = Procedure(
636633 Sender: TObject;
637634 Const ParamName: String;
638635 Out AValue: String
639636 ) Of Object;
640637 // OnReplaceTag: para etiquetas con soporte de parámetros
641638 TReplaceTagEvent = Procedure(
642639 Sender: TObject;
643640 Const TagString: String;
644641 TagParams: TStringList;
645642 Out ReplaceText: String
646643 ) Of Object;
647644```
648645
649646En **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:
650647
651648``` pascal
652649// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
653650case Trim(ParamName) of
654651 'titulo': AValue := 'Mi App';
655652else
656653 AValue := 'DESCONOCIDO';
657654end;
658655```
659656
660657En **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í:
661658
662659``` pascal
663660// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
664661case Trim(TagString) of
665662 'FechaHora': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
666663else
667664 AValue := 'DESCONOCIDO';
668665end;
669666```
670667
671668### Al Nivel de la Acción
672669
673670Cree/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**.
674671
675672NOTA: 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.
676673
677674A 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:
678675
679676``` pascal
680677with Actions.CurrentAction as TFPWebAction do
681678begin
682679 AResponse.Content := Template.GetContent;
683680end;
684681Handled := true;
685682```
686683
687684El cast es requerido desde que **CurrentAction** es de tipo **TCustomWebAction** en vez de **TFPWebAction**. Sin eso, no podemos acceder a la propiedad **Template**.
688685
689686### A Nivel de Módulo
690687
691688A 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.
692689
693690La 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.
694691
695692Una 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**.
696693
697694### Usando Plantillas por Separado
698695
699696A 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.
700697
701698## Usando un objeto html Producer
702699
703700Suponiendo 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:
704701
705702``` pascal
706703procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
707704begin
708705 (* pide a HTMLEntityProducer1 el contenido de sus elementos html DOM: *)
709706 HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
710707 Handled := True;
711708end;
712709```
713710
714711Dicho 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.
715712
716713Aquí 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:
717714
718715``` pascal
719716procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
720717begin // HTMLEntityProducer has Entity := heHtml
721718 aWriter.StartHead; // .startHeader;
722719 aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
723720 aWriter.title('My web page');
724721 aWriter.link('stylesheet','stylesheet.css','text/css','screen');
725722 aWriter.EndHead; // .endHeader;
726723
727724 aWriter.Startbody;
728725 aWriter.StartParagraph;
729726 aWriter.heading2('Hola, mundo desde adentro §1:');
730727 aWriter.Text('Esto es texto escrito dentro del párrafo actual.');
731728 aWriter.EndParagraph;
732729 aWriter.paragraph('Otro texto dentro de un párrafo "iniciado y terminado" por sí mismo.');
733730 aWriter.StartP; // .StartParagraph;
734731 aWriter.tagH2('Hola, mundo desde adentro §2:'); // .heading2;
735732 aWriter.Text('Aquí está el texto final.');
736733 aWriter.Image.src := 'logo.png';
737734 AnotherProducer.WriteContent(aWriter);
738735 aWriter.EndP; // .EndParagraph;
739736 // you can use tags and other THtmlCustomElements
740737 aWriter.tagP([aWriter.bold('Hola'), aWriter.tagQ('Mundo!')]);
741738 aWriter.Text('<u>Hola,</u><i>mundo!</i>');
742739 aWriter.Endbody;
743740
744741end;
745742```
746743
747744La propiedad TWebAction.ContentProducer permite acoplar un THTTPContentProducer a su acción web expuesta a través de una URI en la red.
748745
749746\[PorHacer: no hay documentación oficial acerca de los componentes "RAD way" de ftweb (html producer, html provider, html adapter, html formatter, etc)\]
750747
751748## Trucos y Consejos
752749
753750### Retornando Distintos Códigos de Respuesta HTTP
754751
755752Por 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.
756753
757754### Redirigir Peticiones a Diferentes URLs
758755
759756Un 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.
760757
761758### Sirviendo Archivos Estáticos (Servidor Web Embebido)
762759
763760Recuerda 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:
764761
765762``` pascal
766763RegisterFileLocation('<Location>','<Directory>');
767764```
768765
769766al 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).
770767
771768Puede aprovechar los mime.types completos aquí <http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co>
772769
773770En 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
774771
775772``` pascal
776773begin
777774 MimeTypesFile := Application.Location + 'mime.txt';
778775```
779776
780777Tenga en cuenta que la ruta por defecto que viene con el proyecto es lib\\\$(TargetCPU)-\$(TargetOS)
781778
782779por ejemplo httpproject\lib\i386-win32\mime.txt
783780
784781### Centralice la Administración de la Configuración y los Módulos
785782
786783Por 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:
787784
788785``` pascal
789786unit Brokers;
790787
791788{$mode objfpc}{$H+}
792789
793790interface
794791
795792{ $define cgi}
796793{ $define fcgi}
797794{$define httpapp}
798795
799796uses
800797 CustWeb;
801798
802799function GetApp: TCustomWebApplication; inline;
803800
804801implementation
805802
806803uses
807804 {$ifdef cgi}fpcgi{$endif}
808805 {$ifdef fcgi}fpfcgi{$endif}
809806 {$ifdef httpapp}fphttpapp{$endif}
810807 ,webmodule1
811808 ,webmodule2
812809 ;
813810
814811function GetApp: TCustomWebApplication;
815812begin
816813 Result := Application;
817814end;
818815
819816initialization
820817 RegisterHTTPModule('wm1', TWebModule1);
821818 RegisterHTTPModule('wm2', TWebModule2);
822819 {$ifndef cgi}
823820 Application.Port := 2015;
824821 {$endif}
825822 Application.Initialize;
826823 Application.Run;
827824end.
828825```
829826
830827De 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.
831828
832829### Terminando Adecuadamente (FastCGI / Servidor Web Embebido)
833830
834831En 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.
835832
836833### Manejador de Excepción Personalizada
837834
838835\[edición de Mayo 1 de 2020 =\> movido desde un puntero de método a un simple de procedimiento.\]
839836
840837Para 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**.
841838
842839Este 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:
843840
844841``` pascal
845842Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;
846843```
847844
848845no olvide **.Create()** **MyExceptionHandler** ANTES de asignar arriba u obtendrá un **EAccessViolation**!
849846
850847Debe 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í:
851848
852849``` pascal
853850Application.OnShowRequestException := @MyShowRequestException;
854851```
855852
856853### Codificando Sólo a Mano (Sin Diseñador de Formularios)
857854
858855No 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.
859856
860857Note 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:
861858
862859``` pascal
863860type
864861 TModuloHolaMundo = class(TFPWebModule)
865862 constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
866863 procedure Request(Sender: TObject; ARequest: TRequest;
867864 AResponse: TResponse; var Handled: Boolean);
868865 end;
869866
870867constructor TModuloHolaMundo.CreateNew(AOwner: TComponent; CreateMode: Integer);
871868begin
872869 inherited CreateNew(AOwner, CreateMode);
873870 OnRequest := @Request;
874871end;
875872
876873procedure TModuloHolaMundo.Request(Sender: TObject; ARequest: TRequest;
877874 AResponse: TResponse; var Handled: Boolean);
878875begin
879876 AResponse.Content := 'Hola, Mundo!';
880877 Handled := true;
881878end;
882879```
883880
884881## Vea También
885882
886883- [Posiblemente el mismo contenido](https://github.com/leledumbo/fpWebTutorial)
887884- [Lazarus para la web (tutorial)](https://www.freepascal.org/~michael/articles/#web1)
888885- [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).
889886- los ejemplos - lea los archivos de texto - en su directorio ...\fpc\x.x.x\source\packages\fcl-web\examples.\
890887
891888![](/wiki-images/c/c0/Cgi-listrecords.png)
892889
893890- 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).
894891
895892Sobre 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.\
896893* [Formatos de datos Web en Lazarus/FPC](https://www.freepascal.org/~michael/articles/#webdata)
897894
898895- [Construcción GUI práctica en ExtJS](https://www.freepascal.org/~michael/articles/#extjs1)
899896- [Combinando aplicaciones web ExtJS y Pascal: ExtPascal](https://www.freepascal.org/~michael/articles/#extpascal)
900897
901898traducido por edgarrod71@gmail.com
902899
903900---
904901*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