Services Web avec AppleScript et Perl
Avant Mac OS X, les utilisateurs de Perl étaient forcés de passer par telnet pour avoir complètement accès complet à leur langage favori hébergé sur un UNIX distant. Mais maintenant, avec sa base BSD et une fenêtre de terminal, Mac OS X possède la même version de Perl qu’on trouverait sur n’importe quel système UNIX.
Bien sûr, le langage de scripts propre à Apple, AppleScript, est présent depuis des années. Et une récente mise à jour a permis d’utiliser les services SOAP, ce qui en fait un complément intéressant de Perl.
On ne peut pas créer de service web SOAP avec AppleScript, mais on peut le faire se comporter comme un client web SOAP et utiliser Perl pour créer le service. On est alors ramené à un simple problème de passage de données structurées entre Perl et AppleScript, le service Perl fonctionnant soit sur la même machine soit de loin. C’est bien plus intéressant que de griffonner un nouveau processus Perl avec l’extension do shell script d’AppleScript parce que le temps de démarrage est beaucoup plus court, ce qui élimine presque le temps d’attente. L’état peut aussi être préservé sans avoir à l’écrire sur le disque entre des requêtes du genre connexion à une base de données “hard-to-marshal” ou résultats mis en cache.
Un service web SOAP implémenté avec Perl a accès à la plate-forme Mac OS X complète à partir d’une perspective UNIX et peut utiliser les milliers de routines pré-écrites du Comprehensive Perl Archive Network (CPAN). En fait, un service web SOAP dans Perl peut même utiliser d’autres services web SOAP pour repaqueter, filtrer ou lister les données.
(Attention, un service web SOAP fonctionne avec toutes les permissions de l’utilisateur qui a lancé le processus. Pensez-y deux fois avant de laisser des gens se connecter à votre service web à distance. Pensez-y dix fois avant de le lancer en tant qu’utilisateur root ou admin.)
Créer une Application de base
Pour montrer la relation entre un serveur AppleScript et un serveur Perl, j’ai créé une tâche simple pour faire démarrer la mécanique en douceur : aller chercher les titres d’un site de nouvelles.
Les sites qui mettent des articles en ligne ajoutent fréquemment un fichier Rich Site Summary (RSS; Sommaire de Site Complexe) avec les titres et des liens vers les articles proprement dits. Ces fichiers peuvent ensuite être utilisés par d’autres sites pour fournir un panel d’histoires intéressantes.
Le module XML::RSS (disponible sur le CPAN) analyse une structure RSS et renvoie un objet qui peut être utilisé pour examiner des histoires particulières. Je souhaitais une applet qui, quand on cliquait dessus, allait chercher les titres dans les RSS d’un de mes sites favoris et ouvrait une fenêtre de navigateur avec un listing de ces titres convertis en liens. Il s’est avéré que cette tâche ne nécessitait que peu de code.
AppleScript peut mener le processus, mais il ne peut aller chercher une URL directement ou analyser le XML résultant, et j’ai donc délégué ceci au programme Perl. L’applet AppleScript renverra une URL de RSS au programme Perl (fonctionnant en tant que démon) via le protocole SOAP, et le script Perl s’occupera de la recherche, l’analyse de RSS et le repaquetage des informations essentielles. Puis AppleScript reformatera les données en HTML et les donnera au navigateur pour être vues.
Il y a sans doute de meilleures manières de faire ceci. Par exemple, il serait trivial que Perl renvoie simplement le HTML repaqueté, ou mette le HTML dans un fichier puis renvoie le nom de ce dernier ou même lance le navigateur directement. Mais cette approche me permet de bien renvoyer les données structurées. Et en ramenant la partie Perl à un minimum, les utilisateurs d’AppleScript peuvent utiliser ce qu’ils connaissent pour fournir des vues différentes des données, en laissant les mordus de Perl se concentrer sur la recherche de données.
Créer le serveur SOAP Perl
Avant d’essayer de faire marcher le serveur SOAP Perl vous aurez besoin de quelques modules: XML::Parser, qui requiert l’analyseur Expat, SOAP::Lite, XML::RSS, et LWP::Simple. Si vous n’avez pas Expat, commencez par l’installer avec les instructions disponibles ici. Pour installer les autres modules, ouvrez juste une fenêtre de terminal et tapez sudo perl -MCPAN -eshell (authentifiez-vous si nécessaire), puis install XML::Parser, install SOAP::Lite, install XML::RSS, et install LWP::Simple. Répondez à toutes les questions nécessaires et assurez-vous de dire “oui” quand il vous sera demandé d’installer SOAP::Transport::HTTP. L’installeur CPAN vous demandera peut-être certaines informations de configuration si c’est la première fois que vous le faites marcher. Vous pouvez obtenir des détails sur le processus complet en entrant perldoc CPAN à une invite.
Jetez un coup d’oeil au serveur SOAP Perl. Vous pouvez voir le script entier ici. J’ai ajouté les numéros de lignes pour le rendre plus facile à lire. Voici comment je commence tout programme Perl que j’écrit:
=1= #!/usr/bin/perl -w =2= use strict; =3= $|++;
La ligne 1 écrit des avertissements Perl et authentifie ceci comme un script Perl en donnant le chemin vers le compilateur/interpréteur Perl de ce système. La ligne 2 offre trois restrictions de compilateur très utiles, qui exigent que je déclare mes variables (pour qu’un typo ne vienne pas ruiner toute une journée), que j’utilise seulement des références solides (par opposition à la dé-référenciation d’une chaîne de texte), et que je renonce à de simples-mots comme chaînes-de-texte. La ligne 3 permet à chaque impression d’être rafraîchie, me renvoyant effectivement la sortie dès que je la demande, et non pas mise en tampon jusqu’à plus tard par efficacité.
La Ligne 5 ouvre le module SOAP::Transport::HTTP qui fait partie de la distribution SOAP::Lite disponible sur le CPAN :
=5= use SOAP::Transport::HTTP;
Les lignes 7 à 9 créent un nouvel objet SOAP::Transport::HTTP::Daemon avec assez d’information pour devenir un endpoint SOAP :
=7= my $daemon = SOAP::Transport::HTTP::Daemon
=8= -> new (LocalAddr => 'localhost', LocalPort => 8001, Reuse => 1)
=9= -> dispatch_to ('Server');
J’ai défini l’hôte qui relie le socket de serveur comme localhost ce qui empêche les processus non-locaux de se connecter au socket. Le nombre de ports est fixé arbitrairement à 8001. Tout nombre entre 7000 et 65535 est souvent accepté tant qu’aucun autre processus ne l’utilise.
Le port est déclaré avec la valeur de Reuse assignée à vrai (1), ce qui me permet de stopper le processus et de le relancer immédiatement. Normalement, un port de serveur est mis en quarantaine pour quelque temps juste après sa sortie pour s’assurer que les stray TCP packets ne terminent pas dans la boîte d’entrée d’un nouveau serveur, mais on garde le contrôle ici, donc cette protection n’est pas nécessaire.
La méthode dispatch_to de la ligne 9 dirige les méthodes d’entrée à trouver dans la classe Server définie plus loin dans le même fichier.
La ligne 11 nous dit que le serveur est plein, et nous rappelle les ajustements du serveur :
=11= print "Contact SOAP server at ", $daemon->url, "\n";
Si vous laissez le paramètre LocalPort (voir ligne 8), le serveur peut passer sur le port disponible d’un système choisi, et le message nous dirait quoi éditer pour le logiciel du client pour contacter ce serveur.
La ligne 12 met ce serveur dans une boucle event-based, manipulant chaque requête d’entrée jusqu’à l’arrêt du programme. Le programme ne renvoie jamais à partir de l’appel de cette méthode :
=12= $daemon->handle;
Comme chaque requête SOAP arrive sur le port du serveur HTTP, il est analysé dans le nom et les paramètres de la méthode. Les méthodes doivent être trouvées dans la classe Server , ce qui est défini aux lignes 14 à 32 :
=14= BEGIN {
=15= package Server;
=16= use base qw(SOAP::Server::Parameters);
=17=
=18= use XML::RSS;
=19= use LWP::Simple;
=20=
=21= sub fetch_headlines {
=22= my $p = pop->method;
=23= my $uri = $p->{uri} or die "missing uri parameter";
=24= my $rdf = get $uri or die "Can't fetch rdf";
=25= (my $rss = XML::RSS->new)->parse($rdf); # might die, we don't care
=26=
=27= return [map {
=28= my $item = $_;
=29= +{ title => $item->Web Services with AppleScript and Perl,
link => $item->{link} };
=30= } @{$rss->{items}}];
=31= }
=32= }
Remarquez qu’on a placé ceci dans un block BEGIN pour émuler une directive de compilation use Server.
La ligne 15 débute le package Server , effectif juqu’à la fin du bloc dans lequel il a été déclaré (ce qui s’avère être la fin du bloc BEGIN ).
La ligne 16 amène la classe SOAP::Server::Parameter , et déclare aussi la classe Server comme étant héritée de SOAP::Server::Parameter. L’effet est que chaque méthode reçoit un paramètre additionnel à la fin de la liste d’arguments de type SOAP::SOM, qui contient des méthodes d’accès à des paramètres nommés à la place de simples paramètres positionnels.
Les lignes 18 et 19 amènent les modules XML::RSS et LWP::Simple .
Les lignes 21 à 31 définissent la méthode SOAP fetch_methods . A la ligne 22, la hashref de paramètres est obtenue en éclatant la liste d’arguments et en appelant method sur l’objet résultant, directement issu de la page principale SOAP::Lite . (Voyez les nombreux exemples SOAP::Lite dans la documentation
et la distribution).
La ligne 23 extrait le paramètre uri de la hashref. Si on die ici, le serveur SOAP capturera cette sortie anormale et renverra automatiquement une faute SOAP au requêteur. La réponse par défaut d’AppleScript était d’ouvrir une boîte de dialogue avec le texte d’erreur, donc cela semblait parfaitement acceptable pour un simple exemple, mais j’imagine que je voudrais mettre cela dans un bloc AppleScript try ce qui permet des manipulations plus souples. Ou peut-être mettez ceci du côté du serveur Perl et renvoyez une réponse par défaut pour un argument invalide.
La ligne 24 va chercher le fichier RSS à l’URI donné. La sous-routine get est importée de LWP::Simple, prenant une seule URI et renvoyant le contenu de la page, ou undef si cela échoue. De nouveau, une mort ici génèrera simplement une faute SOAP, créant une erreur du côté AppleScript.
La ligne 25 prend le contenu du RSS résultant et l’analyse dans un objet XML::RSS ,puis les lignes 27 à 31 renvoient la réponse. Basiquement, nous construisons une référence de tableau à une liste d’éléments, et chaque élément sera une hashref à un hachage contenant un titre et un URL. Nous obtiendrons ceci en faisant une itération sur la liste des éléments (Line 30), extrayant chaque titre, reliant et créant une hashref de là (Line 29), puis envoyant tout ceci dans un constructeur de tableaux anonyme (les accolades des lignes 27 à 30). La beauté de ceci est que les méthodes SOAP::Lite trouvent comment lier ceci de façon appropriée dans une liste et l’enregistrent pour que AppleScript agisse correctement.
Et c’est tout en ce qui concerne le serveur. Lancez ce script, et vous êtes accueilli avec :
Contact SOAP server at http://localhost:8001/
…et votre serveur est prêt et fonctionne à l’adresse indiquée.
Créer le client SOAP AppleScript
Maintenant, le deuxième aspect : le client AppleScript qui contactera ce serveur. Vous pouvez voir le script complet, avec lignes numérotées, ici. Je dois avertir le lecteur ici: ce code fonctionne, mais il n’est pas aussi robuste qu’il pourrait l’être. C’est un simple exemple de l’utilisation de services SOAP avec AppleScript. Pour plus d’information, voyez la documentation techpub.
Les lignes 1 à 5 définissent la sous-routine qui vous laisse passer d’AppleScript à un serveur Perl SOAP.
=1= on fetch_SOAP_lite(endpoint, method, p)
=2= using terms from application "http://www.apple.com/placebo"
=3= tell application endpoint to return call soap
{method name:method, method namespace uri:endpoint,
SOAPAction:(endpoint & "#" & method), parameters:p}
=4= end using terms from
=5= end fetch_SOAP_lite
J’ai obtenu les using terms de la ligne 2 du guide d’Apple AppleScript pour SOAP. Apparemment, quand l’application d’une tell application … est variable, AppleScript ne sait pas quel vocabulaire utiliser pour les interactions. Par contre, AppleScript sait que si c’est un URL HTTP, on utilise alors le vocabulaire SOAP ou XML-RPC. On met donc un URL tout bête comme ça AppleScript est content (le placebo), et tout est bien.
Le paramètre endpoint consiste en l’URL renvoyé au début du script en même temps que le nom de la classe que j’ai choisi pour dispatcher les méthodes. Ainsi, pour obtenir le serveur précédemment mentionné, nous utiliserons http://localhost:8001/Server ici.
Le paramètre method est la méthode SOAP particulière dans la classe. Ce sera fetch_headlines.
Le paramètre p est un enregistrement AppleScript correspondant aux paramètres nommés reçus par la méthode SOAP.
La ligne 3 effectue l’appel SOAP, se connecte à l’endpoint approprié, choisit la bonne méthode, et passe les bons paramètres. La réponse est renvoyée, à moins qu’une faute ou une exception SOAP ne soit émise, et dans ce cas l’exception appropriée se propage dehors.
Les lignes 7 à 9 définissent une routine fetch_headlines qui utilise la sous-routine fetch_SOAP_lite pour dialoguer avec notre serveur particulier:
=7= on fetch_headlines(uri)
=8= return fetch_SOAP_lite("http://localhost:8001/Server",
"fetch_headlines", {uri:uri})
=9= end fetch_headlines
Etant donné un paramètre d’URI, je le passe comme paramètre nommé uri à notre serveur Perl SOAP, puis je renvoie la réponse. Cela lie effectivement fetch_headlines dans cet AppleScript comme s’il était la méthode fetch_headlines du serveur éloigné. Et voici la magie de SOAP, si vous ignorez tout le plombage en dessous.
La ligne 11 invoque la sous-routine fetch_headlines (qui appelle le serveur éloigné pour obtenir une réponse), et garde la réponse dans la variable de réponse AppleScript:
=11= set response to fetch_headlines("http://www.perl.com/pace/perlnews.rdf")
J’utilise l’URI pour les titres du site web O’Reilly’s Perl.com, obtenant ainsi les dernières informations du monde Perl.
Les lignes 12 à 16 formattent la sortie de la réponse en page HTML minimale:
=12= set output to "<ul>" & return
=13= repeat with i in response
=14= set output to output & "<li><a href=\"" ¬
& (i's link) & "\">" & (i's title) & "</a></li>" & return
=15= end repeat
=16= set output to output & "</ul>" & return
Une liste boulet est créée en sortie, où chaque élément arrive par la saisie des éléments link et title à partir des enregistrements d’éléments de la réponse. En utilisant un enregistrement, le serveur peut finalement renvoyer plus que simplement le titre et le lien (c.a.d., l’âge de l’élément) sans casser de code existant. C’est important d’envisager quand écrire les applications client/serveur: concevoir les données afin qu’elles soient conservées lors de mises à jour pour d’autres compatibilités.
Maintenant que j’ai le HTML, je dois l’écrire dans un fichier pour qu’un navigateur puisse le lire. Je sélectionnerai le nom en utilisant le sélecteur spécial temporary items folder built-in, ajoutant fetch_headlines.html au nom, écrit ligne 17 :
=17= set output_file to (path to temporary items folder as string)
& "fetch_headlines.html"
La ligne 18 invoque la sous-routine write_to_file (définie plus tard) pour mettre le contenu du HTML généré dans le fichier désigné:
=18= write_to_file(output, output_file, false)
Finalement, la ligne 19 demande au Finder d’ouvrir le fichier:
=19= tell application "Finder" to open output_file
Comme ce fichier se termine en .html, on activera ainsi ce qu’on a désigné comme étant le navigateur par défaut pour les fichiers HTML. Pour moi, cela a ouvert une fenêtre d’Internet Explorer contenant les liens appropriés.
Les lignes 21 à 28 ont été tirées d’un script de l’équipe AppleScript :
=21= -- borrowed code =22= on write_to_file(this_data, target_file, append_data) =23= try =24= set the target_file to the target_file as text =25= set the open_target_file to ¬ =26= open for access file target_file with write permission =27= if append_data is false then ¬ =28= set eof of the open_target_file to 0 =29= write this_data to the open_target_file starting at eof =30= close access the open_target_file =31= return true =32= on error =33= try =34= close access file target_file =35= end try =36= return false =37= end try =38= end write_to_file
Cela prend la donnée du premier paramètre et l’écrit (ou l’ajoute, cela dépend du troisième paramètre) au fichier nommé dans le second paramètre. Je me contenterai de le traiter comme une boîte noire et de vous la donner de la même manière. Une bonne chose à propos d’AppleScript est que c’est là depuis assez longtemps pour trouver plein de bouts de code comme celui-ci disponibles en ligne.
Conclusion
Et voilà, vous l’avez. Ajoutez-le à un script compilé en utilisant quelque chose comme Script Editor, puis lancez-le. L’application contactera le serveur Perl SOAP pour les titres actuels de Perl.com. Les données seront ensuite reformattées par AppleScript en une belle liste, et votre navigateur s’ouvrira automatiquement à cette page. Si vous remarquez un titre qui vous plaît, cliquez dessus et sortez tout l’article.
Dans la réalité, j’aurais probablement demandé à AppleScript d’ouvrir une boîte de dialogue me demandant de choisir dans une liste de sites au lieu de juste relier un seul nom dans le script. En fait, vous pourriez le construire comme une application Cocoa complète en utilisant AppleScript Studio pour que l’application elle-même montre les titres et fasse des liens qui seraient ensuite ouverts par un navigateur. Et avec une boucle on idle , la partie AppleScript pourrait aller chercher les titres une fois toutes les cinq minutes par exemple, montrant les nouveaux éléments en couleur vive pour attirer votre attention. Vous pourriez ensuite le laisser fonctionner dans un coin de votre écran, pour voir les nouvelles qui arrivent.
Maintenant que vous savez combien il est facile de créer un service web SOAP avec Perl, vous pouvez fournir des services pour vos AppleScripts locaux et
accéder beaucoup plus que AppleScript ne peut le faire lui même.

Textes originaux en anglais sur O’Reilly : Services Web avec AppleScript et Perl par Randal L. Schwartz
Chargement
Commentaires récents