fcl-web/fr
│
English (en) │
español (es) │
français (fr) │
русский (ru) │
Qu'est ce que fpWeb ?
fpWeb peut être utilisé pour construire des applications Web serveur. Une application serveur Web peut être l'une des suivantes:
- Application CGI.
- Application FastCGI.
- Module Apache.
- Serveur HTTP(s) autonome utilisant les composants Free Pascal HTTP server.
- Serveur HTTP(s) autonome utilisant libmicrohttp de GNU.
- Application Windows SysHTTP.
Dans tous les cas, le modèle de développement est le même : Vous enregistrez des gestionnaires (handlers) pour un ensemble d'URL (appelées routes) auxquels votre application saura répondre. Ensuite vous incorporez ces routes dans l'un des environnements du dessus : Ceci signifie que vous choisissez l'objet application que vous allez utilisez.
Dans l'EDI Lazarus, vous avez le choix des applications quand vous commencez un nouveau projet. Il est possible de changer le type d'application une fois que l'application a été démarrée, cela se limite souvent au changement de nom d'une unité dans la clause uses
du projet.
Il est possible de prendre en charge de multiples environnement dans un unique exécutable, au prix de quelques efforts supplémentaires. fpWeb offre aussi du support non conventionnel pour un serveur de base de données REST ou pour des mécanismes JSON RPC 2.0. Ceux-ci sont construits en haut de la structure du dessus. Le cadre d'application Brook est aussi construit en haut de l'architecture exposée au dessus. Plus d'informations sur le sujet peut être trouvé dans fpWeb Tutorial.
Utilisation de fpWeb avec Lazarus
Installation du paquet Lazarus fpWeb weblaz
The first step to do is installing the package which comes in the path lazarus/components/fpweb/weblaz.lpk. As usual with design-time packages, you'll have to rebuild Lazarus.
Création d'une application CGI
Après que le paquet weblaz
ait été installé, une très simple application Web CGI qui affiche une page HTML peut être créée en allant dans le menu Lazarus "Fichier" -> "Nouveau...". Dans la liste des applications possibles, sélectionnez "CGI Application" comme dans l'image ci-dessous, qui va créer un fichier de projet CGI principal et un Web module fpweb.
Le TFPWebModule vous permet de manipuler des propriétés et des événements utilisant l'inspecteur d'objet.
Pour ajouter du code pour montrer la page, un gestionnaire de requête doit être ajouté. Pour faire ceci, double-cliquer sur la propriété OnRequest
dans l'inspecteur d'objet, comme dans l'image qui suit:
Dans le gestionnaire d'événement, on doit écrire le code HTML qui sera affiché par le navigateur. Pour éviter de mélanger le Pascal et le HTML, cette page peut être chargée depuis le répertoire où se trouve l'exécutable CGI, en utilisant AResponse.Contents.LoadFromFile()
.
Le type de la réponse doit être défini dans AResponse.ContentType
. Pour les pages HTML, cela devrait avoir la valeur 'text/html;charset=utf-8'
.
Finalement, Handled
devrait être défini à True
pour indiquer que la requête a été traité avec succès (et retourne un code d'état '200 OK' au navigateur). Après l'ajout de ce code, le module Web devrait ressembler à ceci :
unit mainpage;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, HTTPDefs, websession, fpHTTP, fpWeb;
type
{ TFPWebModule1 }
TFPWebModule1 = class(TFPWebModule)
procedure DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
private
{ private declarations }
public
{ public declarations }
end;
var
FPWebModule1: TFPWebModule1;
implementation
{$R *.lfm}
{ TFPWebModule1 }
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
begin
AResponse.ContentType := 'text/html;charset=utf-8';
AResponse.Contents.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'mainpage.html');
Handled := True;
end;
begin
RegisterHTTPModule('TFPWebModule1', TFPWebModule1);
end.
Déploiement d'une application CGI
Cette section suppose que le serveur Web Apache est utilisé. Bien sûr, d'autres serveurs Web qui supportent CGI (nginx, cherokee) peuvent aussi être utilisé.
Apache peut être téléchargé ici ou installé en utilisant le gestionnaire de paquets de votre distribution.
L'installation par défaut d'Apache traitera tous les fichiers dans son répertoire cgi-bin comme des programmes CGI, afin que l'utilisateur ne puisse pas accéder aux fichiers HTML simples qui y sont placés. Ce répertoire peut être défini dans le fichier httpd.conf
dans la section suivante :
# # ScriptAlias: This controls which directories contain server scripts. # ScriptAliases are essentially the same as Aliases, except that # documents in the target directory are treated as applications and # run by the server when requested rather than as documents sent to the # client. The same rules about trailing "/" apply to ScriptAlias # directives as to Alias. # ScriptAlias /cgi-bin/ "C:/Program Files/Apache Software Foundation/Apache2.2/cgi-bin/"
Si vous placez un exécutable appelé "mywebpage.cgi" dans ce répertoire, alors la page peut être accédée comme http://localhost/cgi-bin/mywebpage.cgi
(ou à distance avec l'adresse IP correspondante ou le nom de domaine).
fcl-web avec Lazarus sur Windows produit des fichiers .exe. Pour qu'Apache serve ces fichiers, vous devez ajouter ceci:
AddHandler cgi-script .exe
Et pour servir vos exécutables depuis un autre répertoire, vous ajoutez ceci :
ScriptAlias /bin/ "C:/lazarus_projects/test-fclweb/bin/"
<Directory "C:/lazarus_projects/test-fclweb/bin/">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
Formatage du HTML and lecture des champs de requête
L'exemple précédent montrait juste une simple page HTML. On pourrait désirer p.ex. :
- changer dynamiquement la sortie de la page HTML, et
- en lisant les variables que le navigateur a transmises à la page Web dans les champs de requête (par exemple dans les actions HTTP GET et HTTP POST).
Une solution simple pour le premier problème est l'utilisation de routine de format du Pascal standard et l'ajout des %s
ou %d
dans le fichier HTML aux places appropriées qui recevront les valeurs spécifiques.
Lecture des données GET
Pour lire les variables GET, on peut utiliser ARequest.QueryFields
, qui est un descendant de TStrings. Chaque variable sera dans une ligne séparée de la TStrings selon le format NomVariable=Valeur, de la même façon qu'elles sont montrées dans l'adresse de la page du navigateur. Pour accéder à une variable, on utilise ARequest.QueryFields.Values[]
en passant le nom de la variable dans les crochets pour recevoir en retour sa valeur, au lieu d'analyser la chaîne manuellement (NdT: Explication superflue, classe de base du Pascal Objet).
Le code résultant :
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
var
HexText, AsciiText: string;
begin
HexText := ARequest.QueryFields.Values['hex'];
AsciiText := HexToAnsii(HexText);
AResponse.ContentType := 'text/html;charset=utf-8';
AResponse.Contents.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'mainpage.html');
AResponse.Contents.Text := Format(AResponse.Contents.Text,
[HexText, AsciiText]);
Handled := True;
end;
Lecture des données POST
Les données soumises par des requêtes POST peuvent être obtenues de TRequest.Content
. Il viendra sans les entêtes de requête - en d'autres mots, il contient le corps de la requête.
Les données de formulaire soumises peuvent être accédées en utilisant TRequest.ContentFields
qui est le contenu analysé comme des champs séparés par & et décodés. Par exemple, l'ensemble suivant de valeurs de formulaire :
login: dfg 345&& login_senha: ==== email: dfg
Sera décodé en 'APPLICATION/X-WWW-FORM-URLENCODED' comme ceci (voir wikipedia POST (HTTP)):
login=dfg+345%26%26&login_senha=%3D%3D%3D%3D&email=dfg
Et sera disponible dans TRequest.ContentFields
via l'index de ligne ou en utilisant la propriété Values
, ce qui est plus pertinent :
TRequest.ContentFields[0]: login=dfg 345&& TRequest.ContentFields[1]: login_senha===== TRequest.ContentFields[2]: email=dfg TRequest.ContentFields.Values['email']: dfg
En cas d'utilisation d'autres types MIME que 'MULTIPART/FORM-DATA' et 'APPLICATION/X-WWW-FORM-URLENCODED' (les types supportés par les formulaires HTML) :
- le contenu est uniquement disponible dans
TRequest.Content
et non pasTRequest.ContentFields
. - l'usage de ces types MIME lève un exception pour FPC 2.4.2 (corrigé dans FPC 2.5+).
Remarquez que HTTP POST peut aussi envoyer des champs de requête (dans l'URL), p.ex. http://server/bla?question=how
, et qui sont accédés par TRequest.QueryFields
comme expliqué dans la section précédente.
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest;
AResponse: TResponse; var Handled: Boolean);
var
lData: String;
begin
lData := ARequest.Content;
Lecture des données binaire POST
Les fichiers téléchargés vers le serveur sont sauvés dans un emplacement temporaire avec un nom de fichier temporaire, pour conserver le fichier, vous devrez le déplacer vers un emplacement permanent.
Pour recevoir des données sur le serveur qui a été POSTé p.ex comme multipart/form-data, utilisez quelques chose comme ceci :
procedure TMainWebModule.TFPWebActions2Request(Sender: TObject;
ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);
var
i: Integer;
begin
// Process all received files
for i := 0 to ARequest.Files.Count - 1 do
begin
// Doing something else than writeln is highly recommended ;)
writeln ('Received Filename: '+ARequest.Files[i].LocalFileName);
end;
Handled := true;
end;
Le client peut envoyer des données en utilisant p.ex. FileFormPost
.
Modules spécialisés
The *TFPWebModule* class (used below) is a simple example of a WEb module that can be used for all kinds of HTTP requests.
However, fpweb comes with some specialized modules, that have extra support for specialized tasks:
- The TSimpleFileModule class in unit fpwebfile.pp can be used to send files. You point it to a directory, and it does the rest.
- The TFPHTMLModule class in unit fphtml.pp can be used to produce HTML.
- The TProxyWebModule class in unit fpwebproxy.pp is a ready-made forwarding proxy.
- The TFPWebProviderDataModule class in unit fpwebdata.pp serves data in JSON format that can be consumed by ExtJS stores.
- The TSQLDBRestModule class in unit sqldbrestmodule.pp implements a full REST server backed by SQLDB. See more info in SQLDBRestBridge.
- The TJSONRPCModule class in unit webjsonrpc.pp implements a JSON-RPC service.
- The TExtDirectModule class in unit fpextdirect.pp implements a Ext.Direct variant of a JSON-RPC service.
Utiliser de multiples modules
If there is only one module in the web application, all requests will be directed to this module.
As your web application grows, multiple modules can be used. A new module can be added by choosing 'File - New' and then one of 'Web module' or 'HTML Web Module'.
FCL-web uses the URL to determine how a HTTP request should be handled. It must therefore know which web-modules exist in the application. To achieve this, each module must be registered.
Each module is registered with fcl-web in the initialization section of the unit it is defined in:
RegisterHTTPModule('location', TMyModule);
The module will then be invoked if an URL of the form
http://www.mysite.org/mycgi.cgi/location
or
http://www.mysite.org/mycgi.cgi?module=location
is used.
If multiple modules are present, the name of the module must appear in the URL, or an error will be raised.
This behaviour can also be forced for applications that have only a single module by setting the Application's property AllowDefaultModule to false:
Application.AllowDefaultModule := False;
In that case, the fcl-web application will always require the name of the module in the URL.
The name of the request variable that determines the module name (by default, this is 'module') can be set in the Application.ModuleVariable property. The following code
Application.ModuleVariable := 'm';
ensures that the following URL is directed to TMyModule:
http://www.mysite.org/mycgi.cgi?m=location
If all this is not enough to determine the module to which the request should be passed, the Application.OnGetModule event can be used. It is of type TGetModuleEvent:
type
TGetModuleEvent = Procedure (Sender : TObject; ARequest : TRequest;
Var ModuleClass : TCustomHTTPModuleClass) of object;
Creating an event handler for this event allows fine control over the module that is created to handle the request: the request (passed in ARequest) can be examined, and the 'ModuleClass' variable must be set to the class of the module that should handle the request.
If 'ModuleClass' is 'Nil' on return, an error will be sent to the browser.
Utilisation des actions
A module can be used to group certain kinds of actions that logically belong together. Imagine a module TUserModule that is used to handle user management in the webpages. There can be multiple actions associated with a user:
- Creating
- Deleting
- Editing
- Displaying
These different actions can be handled by the same module. One can determine the action from the URL manually, as in:
http://mysite/mycgi.cgi/user?action=delete&id=12
This can be automated.
In order to make it easier to distinguish between various actions, the module has a property actions: this is a collection, in which each item is associated with a different response to the request. The actions have various properties:
- Name
- The name of the action. The URL will be examined to determine the name of the action.
- Content
- A string. If set, this is sent to the browser.
- Contents
- A stringlist. If set, this is sent to the browser.
- ContentProducer
- If set, the contentproducer will handle the request.
- Default
- if set to 'True', then this action is the default action. That means that if FCL-Web cannot determine the action name, this action will be executed.
- Template
- If set, this template will be processed, and the results sent to the browser.
There are also some events:
- BeforeRequest
- executed before the request is processed. Can be used to set the 'Content' or other properties.
- AfterRequest
- executed after the request is processed.
- OnRequest
- an event handler to handle the request. If set, the handler is used to handle the request.
Again, as in the case of multiple modules, the URL is used to determine which action to execute. The part right after the module part ("user" in this example) is used:
http://mysite/mycgi.cgi/user/delete&id=12
would execute the action named 'delete'.
The 'ActionVar' property of the module can be used to set the name of the request variable to use. Setting
UserModule.ActionVar := 'a';
may be used to change the above URL to
http://mysite/mycgi.cgi/user?a=delete&id=12
If there is only one module in the application, the URL can be shortened to
http://mysite/mycgi.cgi/delete&id=12
Utilisation des patrons HTML
For information about using templates, template tags and template tag parameters to generate response pages, please refer to the fptemplate.txt file under your FPC directory in /packages/fcl-base/texts/.
Example projects that demonstrate using templates can be found under your FPC directory in /packages/fcl-web/examples/fptemplate/ (see the README.txt in there for more). (In earlier versions, these examples were in the Lazarus directory in /components/fpweb/demo/fptemplate/)
Conseils & résolution de problèmes
Envoi des codes de retour
To send a different HTTP response than 200 OK, use AResponse.Code and AResponse.CodeText, e.g.
AResponse.Code := 404;
AResponse.CodeText := 'Document not found';
Envoi de données binaires
An approach that seems to work to send e.g. a tiff file from the web server to the client - adapted from $(fpcdirectory)\packages\fcl-web\examples\jsonrpc\demo1\wmdemo.pp - something like:
AResponse.ContentStream := TMemoryStream.Create;
try
AResponse.ContentStream.LoadFromFile('/tmp/sample.tiff');
AResponse.ContentType := 'image/tiff'; //or whatever MIME type you want to send
// to do: there is an fpweb example that gets the mime type from the file extension...
AResponse.ContentLength:=AResponse.ContentStream.Size; //apparently doesn't happen automatically?
AResponse.SendContent;
finally
AResponse.ContentStream.Free;
end;
Handled := true;
Erreur: "Could not determine HTTP module for request"
You may have multiple modules and multiple actions. If you specify an URL with only 1 item, like:
http://localhost/cgi-bin/somemodule
then fpweb assumes you're specifying an action. If you don't have a default module set, you will get a 500 internal server error (Could not determine HTTP module for request)
You can modify this behaviour to let fpweb map to a module name instead by setting the application's PreferModuleName property to true.
Erreur: "response code 500 Internal Server error when trying to handle DELETE method"
In FPC 2.6.2 and lower, fcl-web does not accept the DELETE method and generates an error.
fpWeb/FCGI et Apache 2.4 (mode mod_proxy_fcgi)
When you want to deploy FCGI application behind Apache 2.4+ reverse proxy, you need PATH_INFO variable in headers that are sent via socket to FCGI daemon.
1, Enable following modules: setenvif_module, proxy_module and proxy_fcgi_module
(on CentOS 7 modules are defined in file /etc/httpd/conf.modules.d/00-base.conf and /etc/httpd/conf.modules.d/00-proxy.conf).
LoadModule setenvif_module modules/mod_setenvif.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
2, Reverse proxy configuration for fpWeb/FCGI listening on port 9000.
$1 = module name
$2 = action name
SetEnvIf Request_URI . proxy-fcgi-pathinfo=full ProxyPassMatch "/fcgi/(.*)/(.*)" "fcgi://127.0.0.1:9000/$1/$2"
fpWeb/FCGI et nginx
For correct routing, fcl-web requires the PATH_INFO variable in headers sent from nginx. For this you have to split the whole URL into FastCGI application name and the path info part whith the configuration statement fastcgi_split_path_info. The statement accepts a regular expression and puts the second match into $fastcgi_path_info configuration variable. This variable can be passed to your application with fastcgi_param statement.
The following configuration example includes a full virtual host for nginx which passes everything from http://myserver:8080/mycgiapp/ to an external FastCGI application.
server { listen 8080; listen [::]:8080; server_name _; root "/var/www"; location /mycgiap/ { # setting up PATH_INFO fastcgi_split_path_info ^(/mycgiapp/)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; # other parameters fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # pass requests to your FastCGI application fastcgi_pass 127.0.0.1:9000; } }
Notes
- The cgiapp unit is deprecated, please use fpcgi as much as possible.
- The fastcgi, custfcgi, and fpfcgi units are not supported on Darwin at this time, because it uses a different mechanism than the MSG_NOSIGNAL option for recv() to indicate that no SIGPIPE should be raised in case the connection breaks. See http://lists.apple.com/archives/macnetworkprog/2002/Dec/msg00091.html for how this should be fixed.
- If you deploy your CGI application and get errors like "Error: No action name and no default action", you should make sure there's an action assigned to the URL, or catch non-supported actions with an action marked Default. In both cases, an OnRequest event handler for the action that sets Handled:=true should be used.
Voir aussi
- fpWeb Tutorial
- fphttpclient Part of fcl-web that can be used stand-alone in client applications
- write me!fphttpserver Small stand alone Object Pascal web server. Can be used to serve fcl-web CGI applications.
- CGI_Web_Programming#Debugging_CGI Information about debugging CGI applications
- Part of a tutorial by Leonardo Ramé that shows how to program an fcl-web CGI application; includes example of generating JSON content