Sign in Join
Wiki copytemporary

fpWeb Tutorial/es: what changed

Revision 2 to 3. 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
181-` Por favor dame tu nombre:`
181+```text
182+ Por favor dame tu nombre:
183+```
182184
183185y al final de la dirección coloque /?name=\<escriba algo aquí, p.e.: Fernando\>, lo que mostrará
184186
185-` Hola, Fernando!`
187+```text
188+ Hola, Fernando!
189+```
186190
187191### Leyendo POST
188192
189193POST 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:
190194
191195``` pascal
192196procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
193197 AResponse: TResponse; var Handled: Boolean);
194198var
195199 LName: String;
196200begin
197201 LName := ARequest.ContentFields.Values['Name']; // ContentFields para POST
198202 if LName = EmptyStr then
199203 with AResponse.Contents do
200204 begin
201205 Add('<form action="%d" method="POST"', [ARequest.URI]);
202206 Add('<label for="name">Por favor dame tu nombre:</label>');
203207 Add('<input type="text" name="name" id="name" />');
204208 Add('<input type="submit" value="Send" />');
205209 Add('</form>');
206210 end
207211 else
208212 AResponse.Content := Format('<h1>Hola, %s!</h1>', [LName]);
209213 Handled := true;
210214end;
211215```
212216
213217### Leyendo Subir Archivos
214218
215219Una 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:
216220
217221``` pascal
218222TUploadedFiles = Class(TCollection)
219223 ·
220224 ·
221225 ·
222226public
223227 Function First: TUploadedFile;
224228 Function Last: TUploadedFile;
225229 Function IndexOfFile(AName: String) : Integer;
226230 Function FileByName(AName: String) : TUploadedFile;
227231 Function FindFile(AName: String) : TUploadedFile;
228232 Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
229233end;
230234```
231235
232236// Cada **TUploadedFile** por sí mismo tiene varias propiedades:
233237
234238``` pascal
235239TUploadedFile = Class(TCollectionItem)
236240 ·
237241 ·
238242 ·
239243Public
240244 Destructor Destroy; override;
241245 Property FieldName: String Read FFieldName Write FFieldName;
242246 Property FileName: String Read FFileName Write FFileName;
243247 Property Stream: TStream Read GetStream;
244248 Property Size: Int64 Read FSize Write FSize;
245249 Property ContentType: String Read FContentType Write FContentType;
246250 Property Disposition: String Read FDisposition Write FDisposition;
247251 Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
248252 Property Description: String Read FDescription Write FDescription;
249253end;
250254```
251255
252256Deberí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.
253257
254258Nuevamente, reusemos el mismo manejador de petición, nótese que se puede optimizar el código usando una constante Req:
255259
256260``` pascal
257261procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
258262 AResponse: TResponse; var Handled: Boolean);
259263const
260264 Req = '<form id="form" action="%d" method=POST enctype="multipart' +
261265 '/form-data"><label for="name">Drag n drop or click to' +
262266 ' add file:</label><input type="file" name="input" />' +
263267 '<input type="submit" value="Send" /></form>';
264268var
265269 n, i: Integer;
266270 f: TUploadedFile;
267271begin
268272 n := ARequest.Files.Count;
269273 if n = 0 then
270274 with AResponse.Contents do
271275 Add(Req, [ARequest.URI])
272276 else begin
273277 f := ARequest.Files[0];
274278 AResponse.Contents.LoadFromStream(f.Stream);
275279 end;
276280 Handled := true;
277281end;
278282```
279283
280284arrastrar 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.
281285
282286## Cookies
283287
284288### Configuración
285289
286290![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.")\
287291El 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.
288292
289293Aquí un ejemplo:
290294
291295``` pascal
292296procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
293297 AResponse: TResponse; var Handled: Boolean);
294298var
295299 C: TCookie;
296300begin
297301 C := AResponse.Cookies.Add;
298302 C.Name := 'miCookie';
299303 C.Value := 'algunValor';
300304 Handled := true;
301305end;
302306```
303307
304308No 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:
305309
306310![](/wiki-images/6/69/Fpwebtutorial_cookie_set.png)
307311
308312Note 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.
309313
310314### Obteniéndola
311315
312316Una 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:
313317
314318![](/wiki-images/9/93/Fpwebtutorial_cookie_get.png)
315319
316320Afortunadamente, 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:
317321
318322``` pascal
319323procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
320324 AResponse: TResponse; var Handled: Boolean);
321325begin
322326 AResponse.Contents.Add('<p>La Cookie tiene:%d</p>', [ARequest.CookieFields.Values['miCookie']);
323327 Handled := true;
324328end;
325329```
326330
327331## Sesiones
328332
329333TFPWebModule 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.
330334
331335La 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**.
332336
333337### Activando la Sesión
334338
335339Para 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í.
336340
337341### Manipulación de las Variables de Sesión
338342
339343Las 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í:
340344
341345``` pascal
342346Session.Variables['miVar'] := miVar; // escritura
343347...
344348miVar := Session.Variables['miVar']; // lectura
345349```
346350
347351Poner una variable como cadena vacía **NO** la remueve. En vez de eso, si realmente quiere remover una variable, llame **Session.RemoveVariable**.
348352
349353### Terminando la Sesión
350354
351355Llame **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í.
352356
353357## Enrutamiento
354358
355359Desde 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:
356360
357361``` pascal
358362Application.LegacyRouting := true;
359363```
360364
361365en el archivo .lpr.
362366
363367### Mecanismo Anterior
364368
365369#### usando Múltiples Módulos
366370
367371Puede 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".
368372
369373![](/wiki-images/e/e5/Fpwebtutorial_new_module.png)
370374
371375luego clique en Aceptar.
372376
373377Dado 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:
374378
375379- /\<nombre del módulo\>
376380- /?modulo=\<nombre del módulo\>
377381
378382En 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**
379383
380384#### Usando Actions
381385
382386Hasta 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:
383387
384388- Módulo Contable
385389 - Action para Ingreso
386390 - Action para Salir
387391 - Action para Registro
388392- Módulo de Productos
389393 - Action para Crear
390394 - Action para Actualizar
391395 - Action para Eliminar
392396 - Action para Detalles
393397
394398##### Flujo de Manejo de Peticiones
395399
396400Antes 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.
397401
398402**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.
399403
400404En general, el flujo de peticiones es:
401405
402406![](/wiki-images/e/ea/Fpwebtutorial_request_flow.png)
403407
404408Note el recuadro "Our Concern", es eso a lo que le pondremos nuestra atención.
405409
406410##### Agregue Actions a los Módulos Web
407411
408412Para 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.
409413
410414![](/wiki-images/9/95/Fpwebtutorial_manage_actions.png)
411415
412416Una ventana popup deberá aparecer donde puede agregar, eliminar y cambiar el orden de las acciones.
413417
414418![](/wiki-images/d/db/Fpwebtutorial_edit_actions.png)
415419
416420Presione 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.
417421
418422![](/wiki-images/2/2e/Fpwebtutorial_action_onrequest.png)
419423
420424Se 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!".
421425
422426Recuerde 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.
423427
424428Ejecute 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\>.
425429
426430Si tiene múltiples módulos, entonces tiene una variedad de opciones:
427431
428432- /\<nombre del módulo\>/\<nombre de la acción\>
429433- /\<nombre del módulo\>?action=\<nombre de la acción\>
430434- /\<nombre de la acción\>?module=\<nombre del módulo\>
431435- /?module=\<nombre del módulo\>&action=\<nombre de la acción\>
432436
433437Note 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**.
434438
435439Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
436440
437441<thead>
438442
439443</thead>
440444<tbody>
441445
442446</tbody>
443447
444448##### Acción por Defecto
445449
446450Recuerde 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:
447451
448452![](/wiki-images/4/44/Fpwebtutorial_request_delegation_flow.png)
449453
450454Dos 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.
451455
452456Las siguientes tablas resumen lo que pasará basado en las dos propiedades:
453457
454458<thead>
455459
456460</thead>
457461<tbody>
458462
459463</tbody>
460464
461465<thead>
462466
463467</thead>
464468<tbody>
465469
466470</tbody>
467471
468472En 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.
469473
470474### Nuevo Mecanismo
471475
472476El nuevo mecanismo es extremadamente flexible y funciona incluso sin módulos de datos (el viejo mecanismo sólo funciona con módulos de datos).
473477
474478Una 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.
475479
476480#### Sintáxis de la Ruta
477481
478482El 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:
479483
480484- /api/v1/products
481485- /api/v1/products/1
482486- /api/v1/products/1/clone
483487- /api/v1/products/something/else/that/is/really/long/and/silly
484488
485489pero no con:
486490
487491- /api/v1
488492- /excuse/me/api/v1/products
489493
490494Básicamente hay sólo 3 caracteres especiales:
491495
492496- *  que denota 0 o más rutas
493497
494498-
495499 param  denota una parte
496500
497501- /  separador de partes
498502
499503su ruta se compondrá de estos 3 caracteres, además cualquier cosa que conforme una ruta.
500504
501505#### Registrar una Ruta
502506
503507El 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:
504508
505509- Procedimiento Callback
506510
507511``` pascal
508512TRouteCallback = Procedure(ARequest: TRequest; AResponse);
509513```
510514
511515- Evento Callback
512516
513517``` pascal
514518TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
515519```
516520
517521- Objeto que satisface una interfaz (CORBA)
518522
519523``` pascal
520524IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
521525 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
522526end;
523527```
524528
525529- Objeto que extiende una clase de enrutador abstracto
526530
527531``` pascal
528532TRouteObject = Class(TObject, IRouteInterface)
529533Public
530534 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
531535end;
532536
533537TRouteObjectClass = Class of TRouteObject;
534538```
535539
536540Por 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.
537541
538542El 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á.
539543
540544En este nuevo mecanismo, un simple programa Hola, Mundo! puede ser tan simple como:
541545
542546``` pascal
543547uses
544548 fphttpapp, httpdefs, httproute;
545549procedure DigaHola(ARequest:TRequest; AResponse : TResponse);
546550begin
547551 AResponse.Content:='<html><body><h1>Hola, Mundo!</h1></body></html>';
548552end;
549553
550554begin
551555 HTTPRouter.RegisterRoute('*', @DigaHola);
552556 Application.Port := 9000;
553557 Application.Initialize;
554558 Application.Run;
555559end.
556560```
557561
558562#### Ejemplo Webserver
559563
560564Este es un ejemplo de un servidor simple, multi-plataforma, multi-hilos.
561565
562566``` pascal
563567program webserver;
564568
565569{$mode objfpc}{$H+}
566570
567571uses
568572 {$ifdef UNIX}
569573 cthreads, cmem,
570574 {$endif}
571575 fphttpapp, httpdefs, httproute;
572576
573577procedure route1(aReq: TRequest; aResp: TResponse);
574578begin
575579 aResp.content:='<html><body><h1>Ruta 1 Por Defecto</h1></body></html>'
576580end;
577581
578582procedure route2(aReq: TRequest; aResp: TResponse);
579583begin
580584 aResp.content:='<html><body><h1>Ruta 2</h1></body></html>'
581585end;
582586
583587begin
584588 HTTPRouter.registerRoute('/', @route1, true);
585589 HTTPRouter.registerRoute('/route2', @route2);
586590 Application.port := 8080;
587591 Application.threaded := true;
588592 Application.initialize;
589593 Application.run;
590594end.
591595```
592596
593597Para abrir la "Ruta 1" teclee en su navegador la URL: <http://localhost:8080>
594598
595599Para abrir la "Ruta 2" teclee en su navegador la URL: <http://localhost:8080/route2>
596600
597601Puede cambiar localhost por 127.0.0.1
598602
599603## Usando Plantillas
600604
601605fpWeb 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.
602606
603607Hay 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.
604608
605609Hay 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.
606610
607611La 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.
608612
609613Las dos propiedades: **StartDelimiter** y **EndDelimiter** definen cómo el motor debería reconocer una etiqueta de plantilla. Por ejemplo, si se tiene:
610614
611615- StartDelimiter = '{+'
612616- EndDelimiter = '+}'
613617
614618entonces 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'.
615619
616620En 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:
617621
618622- ParamStartDelimiter = '\[-'
619623- ParamEndDelimiter = '-\]'
620624- ParamValueSeparator = '='
621625
622626entonces 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.
623627
624628Como consecuencia de distintas formas de operar, el evento central donde la etiqueta de plantilla trabaja es diferente también.
625629
626630El no-parametrizado usará **OnGetParam** mientras que el parametrizado usará **OnReplaceTag**. Los dos tienen por supuesto, diferentes interfaces:
627631
628632``` pascal
629633Type
630634 // OnGetParam: para simple soporte exclusivo de etiquetas de plantilla (ej: {Nombre})
631635 TGetParamEvent = Procedure(
632636 Sender: TObject;
633637 Const ParamName: String;
634638 Out AValue: String
635639 ) Of Object;
636640 // OnReplaceTag: para etiquetas con soporte de parámetros
637641 TReplaceTagEvent = Procedure(
638642 Sender: TObject;
639643 Const TagString: String;
640644 TagParams: TStringList;
641645 Out ReplaceText: String
642646 ) Of Object;
643647```
644648
645649En **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:
646650
647651``` pascal
648652// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
649653case Trim(ParamName) of
650654 'titulo': AValue := 'Mi App';
651655else
652656 AValue := 'DESCONOCIDO';
653657end;
654658```
655659
656660En **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í:
657661
658662``` pascal
659663// use Trim() si quiere que los espacios alrededor de la etiqueta no importen
660664case Trim(TagString) of
661665 'FechaHora': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
662666else
663667 AValue := 'DESCONOCIDO';
664668end;
665669```
666670
667671### Al Nivel de la Acción
668672
669673Cree/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**.
670674
671675NOTA: 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.
672676
673677A 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:
674678
675679``` pascal
676680with Actions.CurrentAction as TFPWebAction do
677681begin
678682 AResponse.Content := Template.GetContent;
679683end;
680684Handled := true;
681685```
682686
683687El cast es requerido desde que **CurrentAction** es de tipo **TCustomWebAction** en vez de **TFPWebAction**. Sin eso, no podemos acceder a la propiedad **Template**.
684688
685689### A Nivel de Módulo
686690
687691A 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.
688692
689693La 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.
690694
691695Una 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**.
692696
693697### Usando Plantillas por Separado
694698
695699A 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.
696700
697701## Usando un objeto html Producer
698702
699703Suponiendo 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:
700704
701705``` pascal
702706procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
703707begin
704708 (* pide a HTMLEntityProducer1 el contenido de sus elementos html DOM: *)
705709 HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
706710 Handled := True;
707711end;
708712```
709713
710714Dicho 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.
711715
712716Aquí 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:
713717
714718``` pascal
715719procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
716720begin // HTMLEntityProducer has Entity := heHtml
717721 aWriter.StartHead; // .startHeader;
718722 aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
719723 aWriter.title('My web page');
720724 aWriter.link('stylesheet','stylesheet.css','text/css','screen');
721725 aWriter.EndHead; // .endHeader;
722726
723727 aWriter.Startbody;
724728 aWriter.StartParagraph;
725729 aWriter.heading2('Hola, mundo desde adentro §1:');
726730 aWriter.Text('Esto es texto escrito dentro del párrafo actual.');
727731 aWriter.EndParagraph;
728732 aWriter.paragraph('Otro texto dentro de un párrafo "iniciado y terminado" por sí mismo.');
729733 aWriter.StartP; // .StartParagraph;
730734 aWriter.tagH2('Hola, mundo desde adentro §2:'); // .heading2;
731735 aWriter.Text('Aquí está el texto final.');
732736 aWriter.Image.src := 'logo.png';
733737 AnotherProducer.WriteContent(aWriter);
734738 aWriter.EndP; // .EndParagraph;
735739 // you can use tags and other THtmlCustomElements
736740 aWriter.tagP([aWriter.bold('Hola'), aWriter.tagQ('Mundo!')]);
737741 aWriter.Text('<u>Hola,</u><i>mundo!</i>');
738742 aWriter.Endbody;
739743
740744end;
741745```
742746
743747La propiedad TWebAction.ContentProducer permite acoplar un THTTPContentProducer a su acción web expuesta a través de una URI en la red.
744748
745749\[PorHacer: no hay documentación oficial acerca de los componentes "RAD way" de ftweb (html producer, html provider, html adapter, html formatter, etc)\]
746750
747751## Trucos y Consejos
748752
749753### Retornando Distintos Códigos de Respuesta HTTP
750754
751755Por 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.
752756
753757### Redirigir Peticiones a Diferentes URLs
754758
755759Un 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.
756760
757761### Sirviendo Archivos Estáticos (Servidor Web Embebido)
758762
759763Recuerda 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:
760764
761765``` pascal
762766RegisterFileLocation('<Location>','<Directory>');
763767```
764768
765769al 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).
766770
767771Puede aprovechar los mime.types completos aquí <http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co>
768772
769773En 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
770774
771775``` pascal
772776begin
773777 MimeTypesFile := Application.Location + 'mime.txt';
774778```
775779
776780Tenga en cuenta que la ruta por defecto que viene con el proyecto es lib\\\$(TargetCPU)-\$(TargetOS)
777781
778782por ejemplo httpproject\lib\i386-win32\mime.txt
779783
780784### Centralice la Administración de la Configuración y los Módulos
781785
782786Por 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:
783787
784788``` pascal
785789unit Brokers;
786790
787791{$mode objfpc}{$H+}
788792
789793interface
790794
791795{ $define cgi}
792796{ $define fcgi}
793797{$define httpapp}
794798
795799uses
796800 CustWeb;
797801
798802function GetApp: TCustomWebApplication; inline;
799803
800804implementation
801805
802806uses
803807 {$ifdef cgi}fpcgi{$endif}
804808 {$ifdef fcgi}fpfcgi{$endif}
805809 {$ifdef httpapp}fphttpapp{$endif}
806810 ,webmodule1
807811 ,webmodule2
808812 ;
809813
810814function GetApp: TCustomWebApplication;
811815begin
812816 Result := Application;
813817end;
814818
815819initialization
816820 RegisterHTTPModule('wm1', TWebModule1);
817821 RegisterHTTPModule('wm2', TWebModule2);
818822 {$ifndef cgi}
819823 Application.Port := 2015;
820824 {$endif}
821825 Application.Initialize;
822826 Application.Run;
823827end.
824828```
825829
826830De 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.
827831
828832### Terminando Adecuadamente (FastCGI / Servidor Web Embebido)
829833
830834En 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.
831835
832836### Manejador de Excepción Personalizada
833837
834838\[edición de Mayo 1 de 2020 =\> movido desde un puntero de método a un simple de procedimiento.\]
835839
836840Para 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**.
837841
838842Este 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:
839843
840844``` pascal
841845Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;
842846```
843847
844848no olvide **.Create()** **MyExceptionHandler** ANTES de asignar arriba u obtendrá un **EAccessViolation**!
845849
846850Debe 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í:
847851
848852``` pascal
849853Application.OnShowRequestException := @MyShowRequestException;
850854```
851855
852856### Codificando Sólo a Mano (Sin Diseñador de Formularios)
853857
854858No 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.
855859
856860Note 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:
857861
858862``` pascal
859863type
860864 TModuloHolaMundo = class(TFPWebModule)
861865 constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
862866 procedure Request(Sender: TObject; ARequest: TRequest;
863867 AResponse: TResponse; var Handled: Boolean);
864868 end;
865869
866870constructor TModuloHolaMundo.CreateNew(AOwner: TComponent; CreateMode: Integer);
867871begin
868872 inherited CreateNew(AOwner, CreateMode);
869873 OnRequest := @Request;
870874end;
871875
872876procedure TModuloHolaMundo.Request(Sender: TObject; ARequest: TRequest;
873877 AResponse: TResponse; var Handled: Boolean);
874878begin
875879 AResponse.Content := 'Hola, Mundo!';
876880 Handled := true;
877881end;
878882```
879883
880884## Vea También
881885
882886- [Posiblemente el mismo contenido](https://github.com/leledumbo/fpWebTutorial)
883887- [Lazarus para la web (tutorial)](https://www.freepascal.org/~michael/articles/#web1)
884888- [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).
885889- los ejemplos - lea los archivos de texto - en su directorio ...\fpc\x.x.x\source\packages\fcl-web\examples.\
886890
887891![](/wiki-images/c/c0/Cgi-listrecords.png)
888892
889893- 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).
890894
891895Sobre 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.\
892896* [Formatos de datos Web en Lazarus/FPC](https://www.freepascal.org/~michael/articles/#webdata)
893897
894898- [Construcción GUI práctica en ExtJS](https://www.freepascal.org/~michael/articles/#extjs1)
895899- [Combinando aplicaciones web ExtJS y Pascal: ExtPascal](https://www.freepascal.org/~michael/articles/#extpascal)
896900
897901traducido por edgarrod71@gmail.com
898902
899903---
900904*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