Sign in Join
Wiki copytemporary

fpWeb Tutorial: what changed

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

OldNewLine
11Originally based on: fcl-web tutorial (in PDF) by forum user [Leledumbo](https://forum.lazarus.freepascal.org/index.php?action=profile;u=7523).
22
33## Introduction
44
55fpWeb is a web application framework shipped by FPC in its default distribution as a part of fcl-web package. The framework itself is built on top of fcl-web features. The framework is built with RAD mind to make a good use of componentization in producing dynamic content. A Lazarus package is provided that can use the framework in a drag n drop manner for its session management and content production. This tutorial will attempt to cover basic functionality of fpWeb, so that one can build common web application with it. Note that this tutorial does **NOT** attempt to teach HTTP protocol, HTML, CSS, JavaScript or database manipulation as the protocol and client languages should be the prerequisites for every web application programmer and database manipulation does not differ from desktop implementation.
66
77## Architecture (PLEASE read)
88
99Before starting, it's necessary to know the architecture and application flow to avoid confusion when certain things don't work or work unexpectedly. So please spend some time reading this section.
1010
1111### Application
1212
1313Application here refers to the protocol that your app will implement. fpWeb will happily switch from CGI, FCGI, Apache module to embedded server, and more if fcl-web implements another one in the future. Each application is implemented in its own unit, so to switch from one application to another, with the exception of Apache module, one just needs to change the respective identifier in the uses clause. Currently (as of 3.0.0 / 3.1.1), they are:
1414
1515- fpCGI -\> CGI
1616- fpFCGI -\> FastCGI
1717- fpApache (requires httpd as well) -\> Apache module
1818- fpHttpApp -\> embedded server
1919- microhttpapp -\> embedded server using GNU libmicrohttp library.
2020- fphttpsys -\> Windows system support for HTTP protocol.
2121
2222Throughout this tutorial, we will use embedded server for the sake of simplicity because you don't have to deal with setting up virtual server and messing up with complicated configuration file and service management. Your app will be a single binary portable web application! Another reason would be there are more than just one web server application out there and each has different way to configure. It would be overkill to cover all of them while their documentation already does the job. Apache module is implemented as a (dynamic) library, while other protocols are normal application. Each application may have specific properties (such as port) available and meaningful only to that application. That's why if you look at fcl-web examples, .lpi / .lpr pairs for each protocol are put in their own directories, only the web modules are shared.
2323
2424### Web Modules
2525
2626![](/wiki-images/5/50/FpWeb-overview.png)
2727
2828fpWeb applications consist of web modules which do the actual content production. A web module can contain web actions that can divide the functionality even more. For instance, an auth web module might have login and logout web actions. While an about web module might not need an action at all and serves just one content. Web module is integrated with fpTemplate that can be used for producing dynamic content from a template file. This is roughly similar to what PHP does, only the gap between logic and presentation is forced rather than suggested. Some say that fpTemplate implements passive view while PHP by default implements an active view design pattern.
2929
3030## Installation
3131
3232The fpWeb package for Lazarus is not installed by default (but it's shipped), to enable fpWeb:
3333
34341. Open Lazarus and choose **Package-\>Install/Uninstall Package**
35352. In the Available for installation listbox, look for **weblaz** and press **Install selection**. Press **Save and rebuild IDE** and confirm with **Continue**
36363. Let the IDE rebuild and restart itself. If everything goes well, you should now have fpWeb tab in the component palette, as shown below:
3737
3838
3939
4040![](/wiki-images/5/55/Fpwebtutorial_inst.png)
4141
4242## Specialized modules
4343
4444The *TFPWebModule* class (used below) is a simple example of a fpWEB module that can be used for all kinds of HTTP requests.
4545
4646However, fpWEB comes with some specialized modules, that have extra support for specialized tasks:
4747
4848- The **TSimpleFileModule** class in unit *fpwebfile.pp* can be used to send files. You point it to a directory, and it does the rest.
4949- The **TFPHTMLModule** class in unit *fphtml.pp* can be used to produce HTML.
5050- The **TProxyWebModule** class in unit *fpwebproxy.pp* is a ready-made forwarding proxy.
5151- The **TFPWebProviderDataModule** class in unit *fpwebdata.pp* serves data in JSON format that can be consumed by ExtJS stores.
5252- The **TSQLDBRestModule** class in unit *sqldbrestmodule.pp* implements a full REST server backed by SQLDB. See more info in [[SQLDBRestBridge]].
5353- The **TJSONRPCModule** class in unit *webjsonrpc.pp* implements a JSON-RPC service.
5454- The **TExtDirectModule** class in unit *fpextdirect.pp* implements a Ext.Direct variant of a JSON-RPC service.
5555
5656## Hello, World!
5757
5858Let's create a simple Web Application. As commonly taught when learning programming, "Hello, World!" will be our first app.\
5959
60601\. Open up Lazarus and choose **Project-\>New Project** then pick **HTTP server Application**
6161
6262
6363![Create new HTTP server application](/wiki-images/5/57/Fpwebtutorial_create_app.png)\
6464
65652\. Another dialog shall appear for serving static files, port selection and multithreading. Just use default port 8080.
6666
6767
6868``` pascal
6969You may skip the static files serving (go to tips and tricks section if you want to know it more).
7070```
7171
7272![](/wiki-images/e/e6/Fpwebtutorial_port_selection.PNG)
7373
7474:**IMPORTANT!:**
7575
7676
7777If you choose to use threads on *nix, don't forget to add cthreads as the first unit in the .lpr's uses clause, otherwise a RTE 232 will be generated. When running from console, a message shall appear:
7878
7979``` pascal
8080This binary has no thread support compiled in. Recompile the application with a thread-driver in the program uses clause before other units using thread.
8181```
8282
83833\. Since Jan 14, 2017 (or FPC 3.0.4), you may need to open the .lpr and add the following line in the main body if it's not already there:
8484
8585
8686``` pascal
8787Application.LegacyRouting := true;
8888```
8989
9090reason will be explained in chapter #Routing.
9191
92924\. Whatever you choose, pick "OK" and you'll be presented in the default one module fpWeb app.\
93935\. Focus the module and move to **Object Inspector**. Feel free to rename the module if you wish.\
94946\. Choose **Events** tab and click the button at the right of the second column of the **OnRequest** row to create the event handler.
9595
9696
9797![Creating web module's OnRequest handler in the object inspector](/wiki-images/8/8d/Fpwebtutorial_on_request.png)\
9898
9999
100100You will be redirected to the source editor with the following code:
101101
102102:
103103
104104``` pascal
105105procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
106106AResponse: TResponse; var Handled: Boolean);
107107begin
108108 |
109109end;
110110```
111111
112112
113113Fill in the event with:
114114
115115:
116116
117117``` pascal
118118procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
119119AResponse: TResponse; var Handled: Boolean);
120120begin
121121 AResponse.Content := 'Hello, World!';
122122 Handled := true;
123123end;
124124```
125125
1261267\. Then run your app (or press F9).\
1271278\. Open your browser and type:
128128
129129
130130<http://localhost:8080/>\
131131
1321329\. You should see "Hello, World!" displayed.\
133133
134134If it doesn't, check below:
135135
136136- The framework does a lot of exception handling and the IDE's debugger might catch them and interrupts your app. It's OK to add most of the exceptions to the ignore list so you can concentrate more on your app flow. Keep skipping and continue until no more dialog appears and the browser shows the output.
137137
138138- **Handled := true** is the way we tell the framework that the request has been handled. Not setting it (or setting it to **false**) will show error page instead. For now, this doesn't affect the request flow yet, but it will be later on. So keep it that way until the time comes to further make a good use of it.
139139
140140- other track: test without a firewall loaded in RAM (as an application, as a service or deamon, or as both).
141141
142142## Reading GET & POST data
143143
144144A dynamic content is likely to be triggered from user input, either through forms, providing values in the URL, etc. Those data are sent along the request, which is represented in the method as **ARequest** parameter of type **TRequest**.
145145
146146### Reading GET
147147
148148GET data is provided as **ARequest.QueryFields** , which is a **TStrings** descendant. In short, whatever you usually do with TStrings, is applicable here such as accessing the data in a map style through the **Values** property.
149149
150150Reusing above code, replace the method body with:
151151
152152``` pascal
153153procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
154154 AResponse: TResponse; var Handled: Boolean);
155155var
156156 LName: String;
157157begin
158158 LName := ARequest.QueryFields.Values['Name'];
159159 if LName = EmptyStr then
160160 with AResponse.Contents do
161161 begin
162162 Add('<form action="' + ARequest.URI + '" method="GET"');
163163 Add('<label for="name">Please tell me your name:</label>');
164164 Add('<input type="text" name="name" id="name" />');
165165 Add('<input type="submit" value="Send" />');
166166 Add('</form>');
167167 end
168168 else
169169 AResponse.Content := 'Hello, ' + LName + '!';
170170 Handled := true;
171171end;
172172```
173173
174174**ARequest.URI** is just a convenience to refer to the current URI, so even when you change your registered module or action name, this code stays the same.
175175
176176Note that as in Pascal, referring to the data is done case insensitively.
177177
178178Now you can try requesting /, which will display
179179
180-` Please tell me your name`
180+```text
181+ Please tell me your name
182+```
181183
182184and /?name=\<write anything here, e.g.: Bob\>, which will display
183185
184-` Hello, Bob!`
186+```text
187+ Hello, Bob!
188+```
185189
186190### Reading POST
187191
188192POST is actually doesn't differ much from GET, only differs in which property to access. If GET is accessed through **ARequest.QueryFields** , POST is accessed through **ARequest.ContentFields**. POST style of previous code is:
189193
190194``` pascal
191195procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
192196 AResponse: TResponse; var Handled: Boolean);
193197var
194198 LName: String;
195199begin
196200 LName := ARequest.ContentFields.Values['Name'];
197201 if LName = EmptyStr then
198202 with AResponse.Contents do
199203 begin
200204 Add('<form action="' + ARequest.URI + '" method="POST"');
201205 Add('<label for="name">Please tell me your name:</label>');
202206 Add('<input type="text" name="name" id="name" />');
203207 Add('<input type="submit" value="Send" />');
204208 Add('</form>');
205209 end
206210 else
207211 AResponse.Content := 'Hello, ' + LName + '!';
208212 Handled := true;
209213end;
210214```
211215
212216### Reading File Uploads
213217
214218One exception is for reading **multipart/form-data** fields, i.e. files. That one is available in **ARequest.Files** as a **TUploadedFiles** instance, which is a **TCollection** descendant. The following is TUploadedFiles public interface which you can use to access the files:
215219
216220``` pascal
217221TUploadedFiles = Class(TCollection)
218222...
219223public
220224 Function First: TUploadedFile;
221225 Function Last: TUploadedFile;
222226 Function IndexOfFile(AName: String) : Integer;
223227 Function FileByName(AName: String) : TUploadedFile;
224228 Function FindFile(AName: String) : TUploadedFile;
225229 Property Files[Index: Integer] : TUploadedFile read GetFile Write SetFile; default;
226230end;
227231```
228232
229233Each **TUploadedFile** itself has several properties:
230234
231235``` pascal
232236TUploadedFile = Class(TCollectionItem)
233237...
234238Public
235239 Destructor Destroy; override;
236240 Property FieldName: String Read FFieldName Write FFieldName;
237241 Property FileName: String Read FFileName Write FFileName;
238242 Property Stream: TStream Read GetStream;
239243 Property Size: Int64 Read FSize Write FSize;
240244 Property ContentType: String Read FContentType Write FContentType;
241245 Property Disposition: String Read FDisposition Write FDisposition;
242246 Property LocalFileName: String Read FLocalFileName Write FLocalFileName;
243247 Property Description: String Read FDescription Write FDescription;
244248end;
245249```
246250
247251They should be descriptive enough, with the exception of **FileName** and **LocalFileName**. **FileName** is the original file **name** as uploaded from client, **LocalFileName** is the file **path** in the server where the file is temporarily stored. Note the difference in bold terms above.
248252
249253Again, reusing the same request handler:
250254
251255``` pascal
252256procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
253257 AResponse: TResponse; var Handled: Boolean);
254258var
255259 n: Integer;
256260 f: TUploadedFile;
257261 i: Integer;
258262begin
259263 n := ARequest.Files.Count;
260264 if n = 0 then
261265 with AResponse.Contents do
262266 begin
263267 Add('<form id="form" action="' + ARequest.URI + '" method="POST" enctype="multipart/form-data">');
264268 Add('<label for="name">Drag n drop or click to add file:</label>');
265269 Add('<input type="file" name="input" />');
266270 Add('<input type="submit" value="Send" />');
267271 Add('</form>');
268272 end
269273 else
270274 begin
271275 f := ARequest.Files[0];
272276 AResponse.Contents.LoadFromStream(f.Stream);
273277 end;
274278 Handled := true;
275279end;
276280```
277281
278282drag n drop a file (preferably text, as it's will be rendered as text) to the input file field (or click the respective button) then click **Send** button. The file content should be displayed.
279283
280284## Cookies
281285
282286### Setting
283287
284288![The "cookie" concept, invented by Netscape in 1994, to allow the HTTP server to identify all its clients.](cookie_session_Object_Pascal.png "The "cookie" concept, invented by Netscape in 1994, to allow the HTTP server to identify all its clients.")\
285289Cookies are browser responsibility to save and keep, therefore server need to send it as part of the response in order to set one. **AResponse.Cookies** contains a list of cookies to be sent. It's a descendant of **TCollection**, respectively the contained **TCookie** is a descendant of **TCollectionItem**. Therefore, you can use TCollection way of managing items to manipulate it.
286290
287291Here's an example:
288292
289293``` pascal
290294procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
291295 AResponse: TResponse; var Handled: Boolean);
292296var
293297 C: TCookie;
294298begin
295299 C := AResponse.Cookies.Add;
296300 C.Name := 'mycookie';
297301 C.Value := 'somevalue';
298302 Handled := true;
299303end;
300304```
301305
302306You won't see any output in your browser. But if you use some kind of developer tools (Chrome has one built-in), you can see the response header:
303307
304308![](/wiki-images/6/69/Fpwebtutorial_cookie_set.png)
305309
306310Note that cookie has attributes, so Name and Value is not the only two you can set. Browse TCookie interface to see what properties are supported.
307311
308312### Getting
309313
310314Once you give **Set-Cookie** header above, subsequent request to your site will contain additional header containing the value you ask to set previously:
311315
312316![](/wiki-images/9/93/Fpwebtutorial_cookie_get.png)
313317
314318Fortunately, the way to read it is no different from GET & POST data. The related property is **ARequest.CookieFields**. To read previously set cookie:
315319
316320``` pascal
317321procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
318322 AResponse: TResponse; var Handled: Boolean);
319323begin
320324 AResponse.Contents.Add('<p>Cookie get: ' + ARequest.CookieFields.Values['mycookie'] + '</p>');
321325 Handled := true;
322326end;
323327```
324328
325329## Sessions
326330
327331TFPWebModule is a descendant of TSessionHTTPModule, so it has session management capability. Session is module based, so each module may choose to use or not to use session management.
328332
329333Session is implemented in abstract manner. By default, no implementation is provided. One sample implementation using .ini files is given in **iniwebsession** unit. You must have this unit in your project or implement one for session management to work. If you decide to implement one, basically you need to extend and implement abstract methods in **TCustomSession** and **TSessionFactory** classes.
330334
331335### Activating
332336
333337To activate session management, set **CreateSession** property to true. Session will be started prior to request handling. In case of a new session, **OnNewSession** will be called. Initialize your session variables here.
334338
335339### Session Variables Manipulation
336340
337341Session variables are provided as **Session.Variables** (nb: the Session object is the equivalent of the \$_SESSION array used in PHP). This is a string to string map like structure, so you can read / write it like:
338342
339343``` pascal
340344Session.Variables['myvar'] := myvar; // write
341345...
342346myvar := Session.Variables['myvar']; // read
343347```
344348
345349Setting a variable to empty string does **NOT** remove it. If you really want to remove a variable, call **Session.RemoveVariable** instead.
346350
347351### Terminating
348352
349353Call **Session.Terminate** whenever you want to terminate a session (e.g.: user logout). Session will also automatically expire if the next request comes after **Session.TimeOutMinutes** since last request. When session terminates, **OnSessionExpired** will be called. Do whatever cleanup you need there.
350354
351355## Routing
352356
353357Since FPC 3.0.4, a new routing mechanism has been implemented. Instead of maintaining backward compatibility, it is decided that the new routing will be the default. Thus, any old code  (or new code depending on old routing) must be ported by adding:
354358
355359``` pascal
356360Application.LegacyRouting := true;
357361```
358362
359363in the .lpr.
360364
361365### Old Mechanism
362366
363367#### Using Multiple Modules
364368
365369You can have multiple modules in your app. Click "File" menu, then click "New...". A dialog shall appear, select "Web Module" from the treeview.
366370
367371![](/wiki-images/e/e5/Fpwebtutorial_new_module.png)
368372
369373then click OK.
370374
371375With multiple modules exist in your app, you can no longer request just with /. The framework will not be able to magically select which module must serve the response, so there are two ways to state which module you'd like to call:
372376
373377- /\<module name\>
374378- /?module=\<module name\>
375379
376380In the 2nd format, you can change "module" (which is the default value) to whatever valid query string key by modifying **Application.ModuleVariable**.
377381
378382#### Using Actions
379383
380384So far, we've only used web modules with single request handler. This doesn't scale much as your web app gets more and more complex. Moreover, some features might have shared properties and be better logically grouped, e.g.:
381385
382386- Account module
383387 - Login action
384388 - Logout action
385389 - Register action
386390- Product module
387391 - Create action
388392 - Update action
389393 - Delete action
390394 - Details action
391395
392396##### Request Handling Flow
393397
394398Before using action, it is important to know fpWeb request handling flow. Failing to do so might render your action useless because it's always your data module that handles the request. How could that be? Going back a few chapters, remember the **Handled := true** that we always did before? Now this is where **Handled** parameter comes into play.
395399
396400**Every** requests will go through module's **OnRequest** first, regardless the requested action. Only if it does **not** set Handled to true, web action's OnRequest is executed.
397401
398402In general, the request flow is:
399403
400404![](/wiki-images/e/ea/Fpwebtutorial_request_flow.png)
401405
402406Notice the "Our Concern" box, that's what we're going to put our attention at.
403407
404408##### Add Actions to Web Modules
405409
406410To add an action, select the web module then go to object inspector. In the properties tab, select Actions and click the button on the second column.
407411
408412![](/wiki-images/9/95/Fpwebtutorial_manage_actions.png)
409413
410414A popup window shall appear where you can add, delete and reorder actions.
411415
412416![](/wiki-images/d/db/Fpwebtutorial_edit_actions.png)
413417
414418Press Add, a new action shall appear in the list. Select it then go to object inspector. It will currently show properties and events of that newly created action. Rename the Name property (this will be the name you write in the URL, so give it a short, simple but descriptive name) as you wish, I will choose "Hello". Move on to events tab, do the same as OnRequest for module, click button on the right of OnRequest row to create the request handler.
415419
416420![](/wiki-images/2/2e/Fpwebtutorial_action_onrequest.png)
417421
418422You will be presented in the same OnRequest interface, but this one handles web action instead of web module. Whatever you can do in web module's OnRequest can be done here as well. Copy the method body from the "Hello, World!" section.
419423
420424Remember to remove **Handled := true** from the previous web module's **OnRequest** body (or remove the event completely) for the action to take care of the request handling.
421425
422426Run your project, and fire up your browser. Now, since the request handling is delegated to web action, you can no longer just request /, but you need /\<action name\> or \<Module's ActionVar property\>=\<action name\>. Note that \<Module's ActionVar property\> has a default value of empty string, unlike **Application.ModuleVariable** which has "module" as the default value. So, by default, you can only use the /\<action name\> form.
423427
424428If you have multiple modules, then you have a variety of options:
425429
426430- /\<module name\>/\<action name\>
427431- /\<module name\>?action=\<action name\>
428432- /\<action name\>?module=\<module name\>
429433- /?module=\<module name\>&action=\<action name\>
430434
431435Note that as soon as a module have at least one action, /\<module or action name\> alone will by default map to /\<action name\>. To change the behavior such that it maps to /\<module name\> by default, set **Application.PreferModuleName** to **true**. In case of multiple modules, if no module name given, then the default module will handle the given action. To change the behavior such that module name must be explicitly given, set **Application.AllowDefaultModule** to **false**.
432436
433437The following tables summarize what will happen based on the two properties:
434438
435439<thead>
436440
437441</thead>
438442<tbody>
439443
440444</tbody>
441445
442446##### Default Action
443447
444448Remember the previous diagram, the "Delegate request handling to actions" is actually not so simple, but if we expand that diagram, the image will be too big to fit. So, here's the diagram of that part:
445449
446450![](/wiki-images/4/44/Fpwebtutorial_request_delegation_flow.png)
447451
448452Two important things from the flow: DefActionWhenUnknown and a default action. The former is a web module's property while the latter corresponds to Default property of an action. In the latter case, in case there are more than two actions having Default property set to true, the the action order (as how it's shown in the manage actions popup window) will be considered to decide which is the default action. The two properties forms what the application should do if no matching action found for a given request.
449453
450454The following tables summarize what will happen based on the two properties:
451455
452456<thead>
453457
454458</thead>
455459<tbody>
456460
457461</tbody>
458462
459463<thead>
460464
461465</thead>
462466<tbody>
463467
464468</tbody>
465469
466470In case of error response above, a stack trace shall follow, complete with line number information if you build your app with -gl. We'll see later on how to create custom handler for this (stacktrace are no good for production). But for now, make sure you understand the concept of web module and web action, especially the request flow. Play around until you think you're ready for next section.
467471
468472### New Mechanism
469473
470474The new mechanism is extremely flexible and works even without data modules (old mechanism only works with data modules).
471475
472476A dedicated unit for this routing is provided as **httproute** (add this to uses clause of program / unit where you want to register routes). The unit contains a function **HTTPRouter** that will return singleton object responsible for application's route management and has **RegisterRoute** method to register your route.
473477
474478#### Route Syntax
475479
476480The first parameter of **HTTPRouter.RegisterRoute** is the route that will be matched against incoming request. It can be as simple as ***** above, which means 0 or more paths or simply any paths, up to as complex as **/api/v1/:resource/*** which means **REQUEST_URI** header should start with **/api/v1/** followed by something else that will be bound to variable named  resource and finally ended with 0 or more paths. It will match:
477481
478482- /api/v1/products
479483- /api/v1/products/1
480484- /api/v1/products/1/clone
481485- /api/v1/products/something/else/that/is/really/long/and/silly
482486
483487but not:
484488
485489- /api/v1
486490- /excuse/me/api/v1/products
487491
488492Basically there are only 3 special characters:
489493
490494- *  denoting 0 or more paths
491495
492496-
493497 param  denoting a part
494498
495499- /  denoting part separator
496500
497501your route will be composed of these 3 characters, plus everything else that made up a route. 
498502
499503#### Registering a Route
500504
501505The 2nd, 3rd or 4th parameter (depending on whether you want to handle specific HTTP method and/or pass additional data to it) of **HTTPRouter.RegisterRoute** is overloaded with several possibilities:
502506
503507- Callback procedure
504508
505509``` pascal
506510TRouteCallback = Procedure(ARequest: TRequest; AResponse);
507511```
508512
509513- Callback event
510514
511515``` pascal
512516TRouteEvent = Procedure(ARequest: TRequest; AResponse) of object;
513517```
514518
515519- Object satisfying a (CORBA) interface
516520
517521``` pascal
518522IRouteInterface = Interface ['{10115353-10BA-4B00-FDA5-80B69AC4CAD0}']
519523 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse);
520524end;
521525```
522526
523527- Object extending abstract router class
524528
525529``` pascal
526530TRouteObject = Class(TObject, IRouteInterface)
527531Public
528532 Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); virtual; abstract;
529533end;
530534
531535TRouteObjectClass = Class of TRouteObject;
532536```
533537
534538By default, if 2nd parameter is not a **TRouteMethod**, then all HTTP methods will match. Use one of  **rmUnknown, rmAll, rmGet, rmPost, rmPut, rmDelete, rmOptions, rmHead, rmTrace** to match only a specific HTTP method.
535539
536540Registration order matters. If there are two or more routes matching the current request, the earlier registered one will handle it.
537541
538542In this new mechanism, standalone Hello, World! program can be as simple as:
539543
540544``` pascal
541545uses
542546 fphttpapp, httpdefs, httproute;
543547procedure DoHello(ARequest:TRequest; AResponse : TResponse);
544548begin
545549 AResponse.Content:='<html><body><h1>Hello,World!</h1></body></html>'
546550end;
547551
548552begin
549553 HTTPRouter.RegisterRoute('*', @DoHello);
550554 Application.Port := 9000;
551555 Application.Initialize;
552556 Application.Run;
553557end.
554558```
555559
556560#### Webserver example
557561
558562This is an example of a simple, cross-platform, multi-threaded web server.
559563
560564``` pascal
561565program webserver;
562566
563567{$mode objfpc}{$H+}
564568
565569uses
566570 {$ifdef UNIX}
567571 cthreads, cmem,
568572 {$endif}
569573 fphttpapp, httpdefs, httproute;
570574
571575procedure route1(aReq: TRequest; aResp: TResponse);
572576begin
573577 aResp.content:='<html><body><h1>Route 1 The Default</h1></body></html>'
574578end;
575579
576580procedure route2(aReq: TRequest; aResp: TResponse);
577581begin
578582 aResp.content:='<html><body><h1>Route 2</h1></body></html>'
579583end;
580584
581585begin
582586 HTTPRouter.registerRoute('/', @route1, true);
583587 HTTPRouter.registerRoute('/route2', @route2);
584588 Application.port := 8080;
585589 Application.threaded := true;
586590 Application.initialize;
587591 Application.run;
588592end.
589593```
590594
591595To open "Route 1" type into your browser the following URL: <http://127.0.0.1:8080>
592596
593597To open "Route 2" type into your browser the following URL: <http://127.0.0.1:8080/route2>
594598
595599## Using Templates
596600
597601fpWeb has integrated support for FPTemplate, Free Pascal's generic templating engine. It doesn't have to be used from web application context, but with integrated support things will be easier a bit. At least the memory management can be ignored as the module will take care of it.
598602
599603There are two levels where one can use templates at: action and module. The full RAD support is incomplete, so you need to go down to hand coding at some points.
600604
601605There are two modes of operation: non-parameterized and parameterized. The active mode is controlled by **AllowTagParams** property, which should be obvious enough what value refers to what mode.
602606
603607Template string can be given from a file through **FileName** property or a direct string through **Template** property. Template.Template, I know it sounds weird :) If both are filled then FileName will take precedence.
604608
605609The two properties: **StartDelimiter** and **EndDelimiter** define how the engine should recognize a template tag. For instance, if you have:
606610
607611- StartDelimiter = '{+'
608612- EndDelimiter = '+}'
609613
610614then a string '{+title+}' defines a template tag named 'title'. Note that spaces are significant, so '{+ title +}' defines a template tag named ' title ' instead of just 'title'.
611615
612616Special for parameterized mode, additional three properties: **ParamStartDelimiter**, **ParamEndDelimiter** and **ParamValueSeparator** defines how the engine should recognize a template tag parameter. For instance, if you have:
613617
614618- ParamStartDelimiter = '\[-'
615619- ParamEndDelimiter = '-\]'
616620- ParamValueSeparator = '='
617621
618622then a string '{+data \[-p=v-\]\[-a=b-\] +}' defines a template tag named 'data' with parameter 'p' of value 'v' and parameter 'a' of value 'b'. This can be used to pass template level parameter such as expected date format, header-row-footer for customizable output presentation, name of file, etc. you decide.
619623
620624As a consequence of different way of operation, the core event where the template works is different, too. Non-parameterized will use **OnGetParam** while the parameterized will use **OnReplaceTag**. The two has of course different interface:
621625
622626``` pascal
623627Type
624628 // OnGetParam: for simple template tag support only (ex: {Name})
625629 TGetParamEvent = Procedure(
626630 Sender: TObject;
627631 Const ParamName: String;
628632 Out AValue: String
629633 ) Of Object;
630634 // OnReplaceTag: for tags with parameters support
631635 TReplaceTagEvent = Procedure(
632636 Sender: TObject;
633637 Const TagString: String;
634638 TagParams: TStringList;
635639 Out ReplaceText: String
636640 ) Of Object;
637641```
638642
639643In **OnGetParam**, you check for **ParamName**, then assign **AValue** accordingly. i.e., if you want tag 'title' to be replaced by 'My App', then fill in the method with:
640644
641645``` pascal
642646// use Trim() if you want the spaces around tag to be insignificant
643647case Trim(ParamName) of
644648 'title': AValue := 'My App';
645649else
646650 AValue := 'UNKNOWN';
647651end;
648652```
649653
650654In **OnReplaceTag**, you check for **TagString** and optionally **TagParams**, then assign **ReplaceText** accordingly. i.e., if you want tag 'datetime' to be replaced by current time with parameter 'datetimeformat' to specify how the date and time should be formatted, then fill in the method like this:
651655
652656``` pascal
653657// use Trim() if you want the spaces around tag to be insignificant
654658case Trim(TagString) of
655659 'datetime': AValue := FormatDateTime(TagParams.Values['datetimeformat'],Now);
656660else
657661 AValue := 'UNKNOWN';
658662end;
659663```
660664
661665### At Action Level
662666
663667Create/select an action, then go to object inspector. You will see a subcomponent property named **Template**. This template is a normal **TFPTemplate** instance. Expand it and fill in the properties as explained above. Now go to Events tab, again expand **Template**, you will see the two events. Fill the one based on your value of **AllowTagParams** property.
664668
665669NOTE: If your Lazarus cannot autocomplete the event, try writing the name manually in the edit box then click the ... button. This is a bug in present Lazarus which may be fixed in the future.
666670
667671At this level, template with a content is not automatically set as request handler. It might change in the future, but let's deal with its current state. Fill in **OnRequest** event of the action, then fill it with:
668672
669673``` pascal
670674with Actions.CurrentAction as TFPWebAction do
671675begin
672676 AResponse.Content := Template.GetContent;
673677end;
674678Handled := true;
675679```
676680
677681The cast is required since **CurrentAction** is of type **TCustomWebAction** instead of **TFPWebAction**. Without it, we can't access the **Template** property.
678682
679683### At Module Level
680684
681685At module level, you currently have to do it by hand coding since no RAD support is implemented. The linked property is ModuleTemplate . This is however not a regular **TFPTemplate** instance, but special **TFPWebTemplate** class that's a descendant of it.
682686
683687The idea here is to have module provide a layout while the actions provide content, with the ability to provide additional variables. It's therefore a good idea to keep **AllowTagParams** as is and assign **OnGetParam** event **of the module** only. Do NOT assign **OnGetParam** of **ModuleTemplate** as it will **never** be called.
684688
685689A template tag named 'content' will be replaced automatically by what content produced by action, everything else is either replaced from internal template variables or from **OnGetParam**.
686690
687691### Using Separated Template
688692
689693Despite the somewhat incomplete integration, nothing stops you from using fpTemplate (or any other templating solutions) manually, outside from the integrated support. This could even be better in some cases since it's modular.
690694
691695## Using an html Producer object
692696
693697Assuming that there is a TWebAction named act_get_my_html, you must ask to write in AResponse, the return of an internal bytes stream: when answering with html Producer objects solution, we must use the methods of the writer. It manages a memory stream for a purpose of rapidity. No string type here, like with the above fpTemplate solution which uses text \\ string processing as in the regular Php way. The Producer writes in AResponse with a recursive " foreach " iteration, that traverses the polymorphic objects HTML Dom tree, composed with the hierarchy THtmlCustomElement = Class(TDOMElement) Classes (see unit htmlelements.pp). So, the AResponse is written by the parameter aWriter, with the following call:
694698
695699``` pascal
696700procedure TFPWebModule1.act_get_my_htmlRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
697701begin
698702 (* ask HTMLEntityProducer1 for the content of its html DOM elements: *)
699703 HTMLEntityProducer1.HandleRequest(ARequest, AResponse, Handled);
700704 Handled := True;
701705end;
702706```
703707
704708Said again, it's the job of the Producer to convert its internal memory stream into text in the response object: that's how this pattern was designed, although there is a text-html rendering method named ProduceContent for debugging purposes only. You can override this method if you are writing or debugging a Producer component.
705709
706710Here is an example, always with an html Producer object, allowing to create a web application in the "RAD way", i.e. with the drag-drop of the HTMLEntityProducer component from the pallet:
707711
708712``` pascal
709713procedure TFPWebModule1.HTMLEntityProducer1WriteEntity(Sender: THTMLContentProducer; aWriter: THTMLWriter);
710714begin
711715
712716 aWriter.startHeader;
713717 aWriter.Meta('','Content-Type','text/html; charset=UTF-8');
714718 aWriter.title('My web page');
715719 aWriter.link('stylesheet','stylesheet.css','text/css','screen');
716720 aWriter.EndHeader;
717721
718722 aWriter.Startbody;
719723 aWriter.Startparagraph;
720724 aWriter.heading2('Hello, world from inside §1:');
721725 aWriter.Text('Here is text written inside the current paragraph.');
722726 aWriter.Endparagraph;
723727 aWriter.paragraph('This is another text written inside a self "started and ended" paragraph.');
724728 aWriter.Startparagraph;
725729 aWriter.heading2('Hello, world from inside §2:');
726730 aWriter.Text('Here is the final text.');
727731 aWriter.Image.src := 'logo.png';
728732 AnotherProducer.WriteContent(aWriter);
729733 aWriter.Endparagraph;
730734 aWriter.Endbody;
731735
732736end;
733737```
734738
735739The TWebAction.ContentProducer property allows to couple a THTTPContentProducer to its web action exposed via a URI on the net.
736740
737741\[ToDo: there is no official documentation about the components "RAD way" of fpWeb (html producer, html provider, html adapter, html formatter, etc)\]
738742
739743## Tips and Tricks
740744
741745### Returning Different HTTP Response Code
742746
743747By default, fpWeb will return HTTP 200 OK to indicate successful request handling. This surely is not always the case, as user input might not be as what we expected. To do so, set **AResponse.Code** in your request handler to the code you want to return.
744748
745749### Redirect Request to Different URL
746750
747751A common flow after a successful login is to redirect user to his account page. This can be done by calling AResponse.SendRedirect in your request handler, supplying the URL to redirect request to.
748752
749753### Serving Static Files (Embedded Web Server)
750754
751755Remember the dialog in the #Hello, World! section after you select HTTP server Application? If you tick "Register location to serve files from" you can fill "Location" (the URI segment, must not contain any slashes) and "Directory" (physical directory in your computer, must exist at runtime) and the wizard will simply add:
752756
753757``` pascal
754758RegisterFileLocation('<Location>','<Directory>');
755759```
756760
757761to the beginning of your .lpr and add the unit **fpwebfile** to the uses clause. You can actually do this by hand anytime and also register multiple times for different locations / directories. After this you can request **/\<Location\>/\<any filename under Directory\>** and it will be served automatically. Note that the mimetype of the file is determined by fpmimetypes. Call **MimeTypes.LoadFromFile** with your mime.types file in order to give correct mimetype based on its extension. Otherwise, the file will always be served as **application/octet-stream** which means the browser will download it instead of interpreting it (especially important for JavaScript and CSS files).
758762
759763You can grab a complete mime.types here <http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?&view=co>
760764
761765In Lazarus 2.0.6 or newer you must add at the top of your program the full path of the mime.types file
762766
763767``` pascal
764768begin
765769 MimeTypesFile := Application.Location + 'mime.txt';
766770```
767771
768772Take into consideration that the default path coming with the project is lib\\\$(TargetCPU)-\$(TargetOS)
769773
770774For example httpproject\lib\i386-win32\mime.txt
771775
772776### Centralize Management of Configuration and Modules
773777
774778By default, the program file (.lpr) is the one that contains protocol unit. This limits the ability to use **Application** object from other contexts such as from web modules. Fortunately, it's not difficult to refactor to have what we want. We remove **RegisterHTTPModule** calls from web modules' units and left out the .lpr to empty main block with single unit identifier in the uses clause, we name it: brokers. The unit contains:
775779
776780``` pascal
777781unit Brokers;
778782
779783{$mode objfpc}{$H+}
780784
781785interface
782786
783787{ $define cgi}
784788{ $define fcgi}
785789{$define httpapp}
786790
787791uses
788792 CustWeb;
789793
790794function GetApp: TCustomWebApplication; inline;
791795
792796implementation
793797
794798uses
795799 {$ifdef cgi}fpcgi{$endif}
796800 {$ifdef fcgi}fpfcgi{$endif}
797801 {$ifdef httpapp}fphttpapp{$endif}
798802 ,webmodule1
799803 ,webmodule2
800804 ;
801805
802806function GetApp: TCustomWebApplication;
803807begin
804808 Result := Application;
805809end;
806810
807811initialization
808812 RegisterHTTPModule('wm1', TWebModule1);
809813 RegisterHTTPModule('wm2', TWebModule2);
810814 {$ifndef cgi}
811815 Application.Port := 2015;
812816 {$endif}
813817 Application.Initialize;
814818 Application.Run;
815819end.
816820```
817821
818822This way, we can control over web module registration and also provide an API to get **Application** object (casted as **TCustomWebApplication**), while still easily switch between protocol implementations, in a single place.
819823
820824### Terminating Gracefully (FastCGI / Embedded Web Server)
821825
822826Instead of Ctrl+C-ing your app, there is a way for your app to terminate gracefully, doing whatever cleanup it needs, by calling **Application.Terminate**. You might need to use previous trick to easily access the **Application** object. A common implementation is to provide a specific password protected module / action that calls the Terminate method. You may choose whatever way you want, though.
823827
824828### Custom Exception Handler
825829
826830\[edit the May 1, 2020 =\> moved from a method pointer to a simple procedure.\]
827831
828832To override the default exception handler, which prints stacktrace whenever an exception is raised (i.e.: on HTTP 404 or 500), and thus not good for production, you must assign **Application.OnShowRequestException**.
829833
830834This is a method so you will need to provide your procedure that implements the method and assign it by using the object. i.e.: if you have **MyExceptionHandler** as an object of **TMyExceptionHandler** which has **MyShowRequestException** method, you can assign it by:
831835
832836``` pascal
833837Application.OnShowRequestException := @MyExceptionHandler.MyShowRequestException;
834838```
835839
836840don't forget to **.Create()** **MyExceptionHandler** BEFORE assigning above or you will get an **EAccessViolation**!
837841
838842You must provide your global procedure that implements your own exception handler (in production, it is advisable to replace the call stack by an HTTP status code and its explanation). Then, you can override the default exception handler, by assigning it like this:
839843
840844``` pascal
841845Application.OnShowRequestException := @MyShowRequestException;
842846```
843847
844848### Pure Hand Coding (No Form Designer Required)
845849
846850It's not a must to use Lazarus' form designer to write an fpWeb application. You can use pure hand coding technique to write it. The secret lies in the 3rd parameter of **RegisterHTTPModule** : **SkipStreaming**. When this parameter is set to true, fpWeb will not search for .lfm resource. Therefore everything must be manually handled: property settings, event handlers, action registration, etc.
847851
848852Note that it's logical to do what's usually done through object inspector in an overriden constructor. Inside it, call the inherited constructor supplying both AOwner and CreateMode as parameters. After that you can set properties, assign event handlers, etc. Example:
849853
850854``` pascal
851855type
852856 THelloWorldModule = class(TFPWebModule)
853857 constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
854858 procedure Request(Sender: TObject; ARequest: TRequest;
855859 AResponse: TResponse; var Handled: Boolean);
856860 end;
857861
858862constructor THelloWorldModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
859863begin
860864 inherited CreateNew(AOwner,CreateMode);
861865 OnRequest := @Request;
862866end;
863867
864868procedure THelloWorldModule.Request(Sender: TObject; ARequest: TRequest;
865869 AResponse: TResponse; var Handled: Boolean);
866870begin
867871 AResponse.Content := 'Hello, World!';
868872 Handled := true;
869873end;
870874```
871875
872876## See also
873877
874878- [Possibly same content](https://github.com/leledumbo/fpWebTutorial)
875879- [Lazarus for the web (tutorial)](https://www.freepascal.org/~michael/articles/#web1)
876880- [Lazarus for the web: Sessions and Templates (tutorial)](https://www.freepascal.org/~michael/articles/#web2) This article explains a TSQLDBSession Class that manages a SQL connection, that inherits from a web session (in order to manage the histories of web session variables, within a connection-session to a database having row-locking capabilities).
877881- the examples - read the text files - in your directory ...\fpc\x.x.x\source\packages\fcl-web\examples.\
878882
879883![](/wiki-images/c/c0/Cgi-listrecords.png)
880884
881885- XML or JSON, on ExtJS: if you want to use a more or less complex engine that is very "accomplished" regarding the rendering aspect of Javascript objects on the browser side, in addition to their functionalities (like ExtJS, for example, to display a db-aware grid, dynamic graphics, etc), this kind of solution very very often expects an XML or a JSON file. There are Adapter and Formatter classes (e.g. the classes TExtJSXMLWebdataInputAdaptor, TExtJSJSONDataFormatter).
882886
883887Overall, the Adapter classes adapt the nodes of an incoming jSon or XML to a mapping of their TField database fields. And Formatter classes are mappers of each TField of a record to its node in jSon or XML format, before sending it out towards the browser.\
884888* [Web data formats in Lazarus/FPC](https://www.freepascal.org/~michael/articles/#webdata)
885889
886890- [Practical GUI construction in ExtJS](https://www.freepascal.org/~michael/articles/#extjs1)
887891- [Mixing ExtJS webapplications and Pascal: ExtPascal](https://www.freepascal.org/~michael/articles/#extpascal)
888892
889893---
890894*From the Free Pascal wiki, page "fpWeb Tutorial", as it stood on 2023-03-17 after a change by Trev, 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