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
La classe *TFPWebModule
* (utilisée dessous) est un simple exemple de module Web quii peut être utilisé pour toutes sortes de requêtes HTTP..
Toutefois, fpweb vient avec quelques modules spécialisés, qui réalisent des tâches spécialisés :
- La classe TSimpleFileModule dans l'unité fpwebfile.pp peut etre utilisée pour envoyer des fichiers. Vous pointez vers un répertoire et elle fait le reste.
- La classe TFPHTMLModule dans l'unité fphtml.pp peut être utlisé pour produire du HTML.
- La classe TProxyWebModule dans l'unité fpwebproxy.pp est un proxy de transmission (forwarding proxy) prêt à l'emploi.
- La classe TFPWebProviderDataModule dans l'unité fpwebdata.pp sert des données au format JSON qui peut être consommé par des magasins (stores) ExtJS.
- La classe TSQLDBRestModule dans l'unité sqldbrestmodule.pp implémente un serveur full REST appuyé par SQLDB. Voir plus d'informations dans SQLDBRestBridge.
- La classe TJSONRPCModule dans l'unité webjsonrpc.pp implémente un service JSON-RPC.
- La classe TExtDirectModule dans l'unité fpextdirect.pp implémente une variante Ext.Direct d'un service JSON-RPC.
Utiliser de multiples modules
Si il y a un seul module dans l'application Web, toutes les requêtes seront redirigées vers ce module.
Comme votre application Web grossit, de multiples modules peuvent être utilisés. Un nouveau module peut être ajouté en choisissant "Fichier" -> "Nouveau..." et ensuite un des 'Module Web' ou 'Module Web HTML'.
FCL-web utilise l'URL pour déterminer comment une requête HTTP qui devrait être manipulé. Il doit donc savoir quels modules Web existent dans l'application. Pour ce faire, chaque module doit être enregistré.
Chaque module est enregistré avec fcl-web dans la section 'initialisation de l'unité dans lequel il est défini:
RegisterHTTPModule('location', TMyModule);
Le module sera alors invoqué si une URL de la forme
http://www.mysite.org/mycgi.cgi/location
ou
http://www.mysite.org/mycgi.cgi?module=location
est utilisée.
Si plusieurs modules sont présents, le nom du module doit apparaître dans l'URL, ou une erreur sera levée.
Ce comportement peut aussi être forcé pour les applications qui ont seulement un unique module en définissant la propriéte AllowDefaultModule
de l'application à False
:
Application.AllowDefaultModule := False;
Dans ce cas, l'application fcl-web requerra toujours le nom du module dans l'URL.
Le nom de la variable de requête qui détermine le nom du module (par défaut, c'est 'module') peut être défini dans la propriété Application.ModuleVariable
. Le code suivant
Application.ModuleVariable := 'm';
assure que l'URL suivante est dirigée vers TMyModule
:
http://www.mysite.org/mycgi.cgi?m=location
Si tout ceci n'est pas suffisant pour déterminer le module vers lequel la requête devrait être passée, l'événement Application.OnGetModule
peut être utilisé. Il est du type TGetModuleEvent
:
type
TGetModuleEvent = Procedure (Sender : TObject; ARequest : TRequest;
Var ModuleClass : TCustomHTTPModuleClass) of object;
La création d'un gestionnaire pour cet événement permet un contrôle fin sur le module qui est créé pour manipuler la requête : la requête (passée dans ARequest
) peut être examinée et la variable ModuleClass
doit être définie par la classe du module qui devrait manipuler la requête.
Si ModuleClass
vaut Nil
au retour, une erreur sera envoyée au navigateur.
Utilisation des actions
Un module peut être utilisé pour grouper certaines sortes d'actions logiquement liées. Imaginez un module TUserModule
qui est utilisé pour traiter la gestion utilisateur dans les pages Web. Il peut y avoir de multiples actions associées avec un utilisateur :
- Création
- Suppression
- Mise à jour
- Affichage
Ces différentes actions peuvent être maniée par le mêm module. On peut déterminer l'action depuis l'URL manuellement, comme dans :
http://mysite/mycgi.cgi/user?action=delete&id=12
Cela peut être automatisé.
Afin de rendre plus facile la distinction entre les diverses actions, le module a une propriété actions
: c'est une collection, dans laquelle chaque article est associé avec une réponse différente à la requête. Les actions ont diverses propriétés :
Name
- Le nom de l'action. L'URL sera examinée pour déterminer le nom de l'action.
Content
- Une chaîne de caractères. Si définie, c'est envoyé vers le navigateur.
Contents
- Une TStringList. c'est envoyé vers le navigateur.
ContentProducer
- Si définie, the ContentProducer maniera la requête.
Default
- Si définie à
True
, alors cette action est l'action par défaut. Ceci signifie que si FCL-Web ne peut pas déterminer le nom de l'action, cette action sera exécutée. Template
- Si définie, ce patron sera traité et les résultats envoyés vers le navigateur.
Il y a aussi quelques événements :
BeforeRequest
- exécuté avant que la requête ne soit traitée. Peut être utilisé pour définir le 'Content' ou d'autres propriétés.
AfterRequest
- exécuté après le traitement de la requête.
OnRequest
- un gestionnaire d'événement pour manier la requête. Si défini, le gstionnaire est utilisé pour manier la requête.
Encore une fois, comme dans le cas de plusieurs modules, l'URL est utilisée pour déterminer quelle action exécuter. La partie juste après la partie module ("utilisateur" dans cet exemple) est utilisée:
http://mysite/mycgi.cgi/user/delete&id=12
exécutera l'action nommée 'delete'.
La propriété ActionVar
du module peut être utilisée pour définir le nom de la variable de requête à utiliser. En affectant :
UserModule.ActionVar := 'a';
peut être utilisé pour changer l'URL du dessus en :
http://mysite/mycgi.cgi/user?a=delete&id=12
S'il y a seulement un module dans l'application, l'URL peut être raccourci en :
http://mysite/mycgi.cgi/delete&id=12
Utilisation des patrons HTML
Pour de l'information au sujet des patrons (template), des étiquettes de patron (template tags) et des paramètres d'étiquettes de patron (template tag parameters) pour générer les pages de réponse, veuillez vous référer au fichier fptemplate.txt
dans le répertoire FPC dans /packages/fcl-base/texts/
(NdT : spécifique Linux)
Des projets d'exemple qui démontrent l'utilisation des patrons peuvent être trouvés sous le répertoire FPC dans /packages/fcl-web/examples/fptemplate
(voir le README.txt dedans pour plus).
(Dans les versions antérieures, ces exemples se trouvaient dans le répertoire Lazarus dans /components/fpweb/demo/fptemplate/
)
Conseils & résolution de problèmes
Envoi des codes de retour
Pour envoyer une réponse HTTP différente de 200 OK, utiliser AResponse.Code
et AResponse.CodeText
, p.ex. :
AResponse.Code := 404;
AResponse.CodeText := 'Document not found';
Envoi de données binaires
Une approche qui semble marcher pour envoyer p.ex. un fichier tiff depuis le serveur Web vers le client - adapté de $(fpcdirectory)\packages\fcl-web\examples\jsonrpc\demo1\wmdemo.pp
- quelque chose comme ceci :
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"
Vous pouvez avoir de multiples modules et de multiples actions. Si vous spécifiez une URL avec un seul item, comme :
http://localhost/cgi-bin/somemodule
alors fpweb suppose que vous êtes en train de spécifier une action. Si vous n'avez pas défini un module par défaut, vous obtiendrez une "500 internal server error (Could not determine HTTP module for request)".
Vous pouvez modifier ce comportement pour permettre à fpweb de correspondre à un nom de module à la place en définissant la propriété PreferModuleName
de l'application sur True
.
Erreur: "response code 500 Internal Server error when trying to handle DELETE method"
Dans FPC 2.6.2 et antérieur, fcl-web n'accepte pas la méthode DELETE et génère une erreur.
fpWeb/FCGI et Apache 2.4 (mode mod_proxy_fcgi)
Quand vous voulez déployer une application FastCGI derrière un proxy inverse Apache 2.4+, vous avez besoin de la variable PATH_INFO dans les entêtes qui sont envoyés vai une socket au démon FastCGI.
1, Activer les modules suivants: setenvif_module, proxy_module et proxy_fcgi_module
(sur CentOS 7 modules sont définis dans le fichier /etc/httpd/conf.modules.d/00-base.conf
et /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, Configuration du proxy inverse pour fpWeb/FCGI écoutant sur le 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
Pour un routage correct, fcl-web requiert que la variable PATH_INFO dans les entêtes envoyés depuis nginx. Pour cela, vous devez diviser l'URL entière en nom d'application FastCGI et en partie information de chemin (path info) avec l'instruction de configuration fastcgi_split_path_info. L'instruction accepte une expression régulière et pose la seconde correspondance dans la variable de configuration $fastcgi_path_info. Cette variable peut être passée à votre application avec l'instruction fastcgi_param.
L'exemple de configuration suivante inclut un hôte virtuel complet pour nginx qui passe tout depuis http://myserver:8080/mycgiapp/ vers une application FastCGI externe.
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
- L'unité cgiapp est dépréciée, veuillez utiliser fpcgi autant que possible.
- Les unités fastcgi, custfcgi et fpfcgi ne sont pas supportées sur Darwin en ce moment (Ndt: Qu'en est-il en 2020 ?), parce qu'il utilise un mécanisme qui différe de l'option MSG_NOSIGNAL pour recv() pour indiquer qu'aucun SIGPIPE ne doit être levé en cas de rupture de la connexion. Voir http://lists.apple.com/archives/macnetworkprog/2002/Dec/msg00091.html pour comment cela peut être fixé.
- Si vous déployez votre application et obtenez des erreurs comme "Error: No action name and no default action", vous devrez vous assurer qu'il y a une action affectée à l'URL, ou intercepter les actions non supportées avec une action marquée Default. Dans les deux cas, un gestionnaire d'événement
OnRequest
pour l'action qui définitHandled:=true
devrait être utilisé.
Voir aussi
- fpWeb Tutorial
- fphttpclient Partie de fcl-web qui peut être utilisé de manière autonome dans des applications clientes.
- write me!fphttpserver Petit serveur Web autonome en Pascal Objet. Peut être utilisé pour servir des applications CGI fcl-web.
- Débogage de CGI Information concernant le débogage des applications CGI.
- Part of a tutorial by Leonardo Ramé that shows how to program an fcl-web CGI application; includes example of generating JSON content