Un chat en Pharo : le client

Magazine
Marque
GNU/Linux Magazine
Numéro
189
Mois de parution
janvier 2016
Spécialité(s)


Résumé
Poursuivons la découverte de Pharo et de quelques-uns de ses principaux frameworks. Vous avez déjà fait connaissance avec Teapot permettant de concevoir des services. Dans cet article, vous allez étudier la construction de requêtes HTTP à l'aide de Zinc [1] et construire une interface graphique à l'aide de Spec.

Body

Nous allons nous concentrer sur la partie client de TinyChat. Pour la construire, nous allons simplement ajouter deux classes au projet :

- TinyChat est la classe contenant la logique métier (connexion, envoi et réception des messages) ;

- TCConsole est une classe définissant l'interface graphique.

La logique du client est la suivante :

- au lancement du client, celui-ci demande au serveur l'index du dernier message reçu ;

- toutes les deux secondes, le client se connecte au serveur pour lire les messages échangés depuis sa dernière connexion. Pour cela, il transmet au serveur l'index du dernier message dont il a eu connaissance.

De plus, lorsque le client transmet un message au serveur, il en profite également pour lire les messages échangés depuis sa dernière connexion.

1. La classe TinyChat

Nous créons la classe TinyChat dans le package TinyChat-client :

Object subclass: #TinyChat

instanceVariableNames: 'url login exit messages console lastMessageIndex'

classVariableNames: ''

category: 'TinyChat-client'

Cette classe définit les variables suivantes :

- url contient l'url HTTP permettant au client de se connecter au serveur ;

- login est une chaîne de caractères identifiant le client ;

- messages est une variable d'instance contenant les messages lus par le client ;

- lastMessageIndex est le numéro du dernier message lu par le client ;

- exit est une valeur booléenne. Tant que cette valeur est vraie, le client se connecte à intervalle régulier au serveur pour lire les messages échangés depuis sa dernière connexion ;

- console est une instance de TCConsole : une console graphique permettant à l'utilisateur de saisir et de consulter les messages.

Nous initialisons les variables qui le nécessitent dans la méthode initialize :

TinyChat >> initialize

super initialize.

exit := false.

LastMessageIndex := 0.

messages := OrderedCollection new.

2. Définir les commandes HTTP

Nous devons maintenant définir les méthodes permettant au client HTTP de communiquer avec le serveur.

Deux méthodes permettent d'assembler les chemins d'accès. L'une n'a pas d'argument et permet de construire les requêtes /messages/add et /messages/count. L'autre méthode a un argument qui est utilisé pour la lecture des messages à partir d'une position.

TinyChat >> command: aPath

^'{1}{2}' format: { url . APath }


TinyChat >> command: aPath argument: anArgument

^'{1}{2}/{3}' format: { url . aPath . anArgument asString }

Il suffit ensuite de définir les trois commandes HTTP du client :

TinyChat >> cmdLastMessageID

^ self command: '/messages/count'


TinyChat >> cmdNewMessage

^self command: '/messages/add'


TinyChat >> cmdMessagesFromLastIndexToEnd

"Returns the server messages from my current last index to the last one on the server."

^ self command: '/messages' argument: lastMessageIndex

3. Gérer les opérations du client

Nous avons besoin d'émettre ces commandes et de pouvoir récupérer des informations à partir du serveur. Pour cela, nous définissons deux méthodes. La méthode readLastMessageID retourne l'index du dernier message reçu par le serveur :

TinyChat >> readLastMessageID

| id |

id := (ZnClient new url: self cmdLastMessageID; get) asInteger.

id = 0 ifTrue: [ id := 1 ].

^ id

La méthode readMissingMessages ajoute les derniers messages reçus par le serveur à la liste des messages connus par le client. Cette méthode retourne le nombre de messages récupérés :

TinyChat >> readMissingMessages

"Gets the new messages that have been posted since the last request."

| response receivedMessages |

response := (ZnClient new url: self cmdMessagesFromLastIndexToEnd; get).

^ response

ifNil: [ 0 ]

ifNotNil: [

receivedMessages := response subStrings: (String crlf).

receivedMessages do: [ :msg | messages add: (TCMessage fromString: msg)].

receivedMessages size.

]

figure_01

Fig. 1 : Lecture de l'historique des messages reçus par le serveur.

Nous sommes prêts à définir le comportement de rafraîchissement du client avec la méthode refreshMessages. Elle utilise un processus léger pour lire à intervalle régulier les messages reçus par le serveur. Le délai est fixé à deux secondes. Le message fork, envoyé à un bloc (une fermeture lexicale en Pharo), exécute ce bloc dans un processus léger. La logique est de boucler tant que le client ne spécifie pas qu'il veut s'arrêter via la variable exit. L'expression (Delay forSeconds: 2) wait suspend l'exécution du processus léger dans lequel elle se trouve, pendant un certain nombre de secondes :

TinyChat >> refreshMessages

[

[ exit ] whileFalse: [

(Delay forSeconds: 2) wait.

lastMessageIndex := lastMessageIndex + (self readMissingMessages).

console print: messages.

]] fork

La méthode sendNewMessage: poste le message de l'utilisateur au serveur :

TinyChat >> sendNewMessage: aMessage

^ ZnClient new

url: self cmdNewMessage;

formAt: 'sender' put: (aMessage sender);

formAt: 'text' put: (aMessage text);

post

Cette méthode est utilisée par la méthode du client send: qui reçoit en paramètre le texte saisi par l'utilisateur. La chaîne de caractères est alors convertie en une instance de TCMessage. Le message est ensuite envoyé. Le client met à jour l'index du dernier message connu et déclenche l'affichage du contenu du message dans l'interface graphique :

TinyChat >> send: aString

| msg |

msg := TCMessage from: login text: aString.

self sendNewMessage: msg.

lastMessageIndex := lastMessageIndex + (self readMissingMessages).

console print: messages.

La déconnexion du client est gérée par la méthode disconnect qui envoie un message au serveur pour signaler le départ de l'utilisateur. Elle met fin à la boucle de récupération périodique des messages.

TinyChat >> disconnect

self sendNewMessage: (TCMessage from: login text: 'I exited from the chat room.').

exit := true

3.1 Fixer les paramètres du client

Pour initialiser les paramètres de connexion, on définit une méthode de classe TinyChat class>>connect:port:login:. Cette méthode permet de se connecter de la manière suivante :

TinyChat connect: 'localhost' port: 8080 login: 'username'

Le code de cette méthode est donc :

TinyChat class >> connect: aHost port: aPort login: aLogin

^ self new host: aHost port: aPort login: aLogin; start

Le code appelle la méthode host:port:login:. Cette méthode met à jour la variable d'instance url en construisant l'URL et en affectant le nom de l'utilisateur à la variable d'instance login :

TinyChat >> host: aHost port: aPort login: aLogin

url := 'http://' , aHost , ':' , aPort asString.

login := aLogin

La méthode start envoie un message au serveur pour présenter l'utilisateur, récupérer l'index du dernier message reçu par le serveur et mettre à jour la liste des messages connus par le client. C'est également cette méthode qui initialise l'interface graphique de l'utilisateur. Une évolution pourrait consister à séparer le modèle de son interface graphique en utilisant une conception basée sur des événements.

TinyChat >> start

console := TCConsole attach: self.

self sendNewMessage: (TCMessage from: login text: 'I joined the chat room').

lastMessageIndex := self readLastMessageID.

self refreshMessages.

4. Création de l'interface graphique

L'interface graphique est composée d'une fenêtre contenant une liste et un champ de saisie :

ComposableModel subclass: #TCConsole

instanceVariableNames: 'chat list input'

classVariableNames: ''

category: 'TinyChat-client'

La variable d'instance chat est une référence à une instance de la classe TinyChat et nécessite uniquement un accesseur en écriture.

Les variables d'instance list et input disposent chacune d'un accesseur en lecture. Ceci est imposé par le constructeur d'interface Spec :

TCConsole >> input

^ input


TCConsole >> list

^ list


TCConsole >> chat: anObject

chat := anObject

L'interface graphique possède un titre pour la fenêtre. Pour le définir, il faut écrire une méthode title :

TCConsole >> title

^ 'TinyChat'

La méthode de classe TCConsole class>>attach: reçoit en argument l'instance du client de chat avec laquelle l'interface graphique va être utilisée. Cette méthode déclenche l'ouverture de la fenêtre et met en place l'événement gérant la fermeture de celle-ci ainsi que la déconnexion du client :

TCConsole class >> attach: aTinyChat

| window |

window := self new chat: aTinyChat.

window openWithSpec whenClosedDo: [ aTinyChat disconnect ].

^ window

La méthode TCConsole class>>defaultSpec définit la mise en page des composants contenus dans la fenêtre. Nous avons une colonne avec une liste et un champ de saisie placé juste en dessous :

TCConsole class >> defaultSpec

<spec: #default>

^ SpecLayout composed newColumn: [ :c |

c add: #list; add: #input height: 30 ]; yourself

figure_02

Fig. 2 : L'interface graphique de TinyChat.

La méthode initializeWidgets spécifie la nature et le comportement des composants graphiques. Ainsi, le code passé à acceptBlock: permet de définir l'action à exécuter lorsque le texte est entré dans le champ de saisie. Nous l'envoyons à chat et nous effaçons son contenu lorsque l'utilisateur appuie sur la touche <Entrée>.

TCConsole >> initializeWidgets

list := ListModel new.

input := TextInputFieldModel new

ghostText: 'Type your message here...';

enabled: true;

acceptBlock: [ :string |

chat send: string.

input text: '' ].

self focusOrder add: input.

La méthode print: affiche les messages reçus par le client en les affectant au contenu de la liste.

TCConsole >> print: aCollectionOfMessages

list items: (aCollectionOfMessages collect: [ :m | m printString ])

Notez que cette méthode est invoquée par la méthode refreshMessages et que changer tous les éléments de la liste à chaque ajout d'un nouveau message est peu élégant mais l'exemple se veut volontairement simple.

Conclusion

Et voilà ! Quelques classes regroupant quelques dizaines de lignes de code vous ont permis de construire un sympathique petit outil de chat. La définition de TinyChat donne un cadre ludique à l'exploration de la programmation en Pharo et nous espérons que vous avez apprécié cette ballade. TinyChat est une petite application que nous avons développée de manière très simple afin de vous permettre de l'étendre et d'expérimenter. Il y a de nombreuses améliorations possibles : gestion parcimonieuse des ajouts d'éléments dans la liste graphique, gestion d'accès concurrents dans la collection sur le serveur (en effet, si le serveur pouvait recevoir des requêtes concurrentes la structure de données utilisée n'est pas adéquat), gestion des erreurs de connexion, rendre les clients robustes à la fermeture du serveur, obtenir la liste des personnes connectées, pouvoir définir le délai de récupération des messages, utiliser JSON ou XML pour le transport des messages, afficher le nom de la personne connectée dans la fenêtre. Le projet est disponible sur le site de dépôt Smalltalkhub [3]. À vous de jouer!

Références

[1] Site officiel du projet Zinc: http://zn.stfx.eu/zn/index.html

[2] Site officiel du projet Pharo : http://www.pharo.org

[3] Télécharger le projet TinyChat : http://www.smalltalkhub.com/#!/~olivierauverlot/TinyChat

Pour aller plus loin

L'ouvrage collectif « Pharo par l'exemple », Square Bracket Associates, 2011

L'ouvrage collectif « Deep inside Pharo », Square Bracket Associates, 2013




Article rédigé par

Par le(s) même(s) auteur(s)

Une introduction aux sons en Pharo

Magazine
Marque
GNU/Linux Magazine
Numéro
178
Mois de parution
janvier 2015
Spécialité(s)
Résumé
Dans cet article, nous allons vous présenter une facette de Pharo qui n'est pas habituellement mise en avant. Ce domaine, c'est la synthèse sonore. Si nous avons besoin de sonoriser un peu notre tout nouveau programme ce qui nous vient à l'esprit c'est l'utilisation du mp3 pour jouer la musique en fond sonore ou des sons digitalisés pour les bruitages. Dans Pharo nous disposons d'outils et bibliothèques permettant de créer de toute pièce du son synthétisé pour nos productions. Nous allons vous montrer comment faire cela en quelques lignes de code seulement.

Les derniers articles Premiums

Les derniers articles Premium

Sécurisez vos applications web : comment Symfony vous protège des menaces courantes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les frameworks tels que Symfony ont bouleversé le développement web en apportant une structure solide et des outils performants. Malgré ces qualités, nous pouvons découvrir d’innombrables vulnérabilités. Cet article met le doigt sur les failles de sécurité les plus fréquentes qui affectent même les environnements les plus robustes. De l’injection de requêtes à distance à l’exécution de scripts malveillants, découvrez comment ces failles peuvent mettre en péril vos applications et, surtout, comment vous en prémunir.

Bash des temps modernes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les scripts Shell, et Bash spécifiquement, demeurent un standard, de facto, de notre industrie. Ils forment un composant primordial de toute distribution Linux, mais c’est aussi un outil de prédilection pour implémenter de nombreuses tâches d’automatisation, en particulier dans le « Cloud », par eux-mêmes ou conjointement à des solutions telles que Ansible. Pour toutes ces raisons et bien d’autres encore, savoir les concevoir de manière robuste et idempotente est crucial.

Présentation de Kafka Connect

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Un cluster Apache Kafka est déjà, à lui seul, une puissante infrastructure pour faire de l’event streaming… Et si nous pouvions, d’un coup de baguette magique, lui permettre de consommer des informations issues de systèmes de données plus traditionnels, tels que les bases de données ? C’est là qu’intervient Kafka Connect, un autre composant de l’écosystème du projet.

Le combo gagnant de la virtualisation : QEMU et KVM

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

C’est un fait : la virtualisation est partout ! Que ce soit pour la flexibilité des systèmes ou bien leur sécurité, l’adoption de la virtualisation augmente dans toutes les organisations depuis des années. Dans cet article, nous allons nous focaliser sur deux technologies : QEMU et KVM. En combinant les deux, il est possible de créer des environnements de virtualisation très robustes.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 65 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous