UniServe : Guide du Développeur

Auteur: SOFTINNOV / Nenad Rakocevic
Date: 02/10/2004
Version: 0.9.9
Commentaires: info@softinnov.com
Traduction: lp.legoff-at-free.fr

Table des Matières

1. Généralités
2. Premiers Pas
      2.1. Installation
      2.2. Tester votre serveur UniServe
      2.3. Arborescence des répertoires
3. Démarrer UniServe
4. API des Services
      4.1. Définition de service
      4.2. Traitement des données entrantes
      4.3. Propriétés
      4.4. Evenements
      4.5. Méthodes
      4.6. Local values isolation
      4.7. Espace partagé
      4.8. Installer un service
      4.9. Fonctionnement des services
      4.10. Exemple: micro-httpd
5. API des Protocoles
      5.1. Défintion de Protocole
      5.2. Traitement des données entrantes
      5.3. Propriétés
      5.4. Evenements
      5.5. Evenements utilisateurs
      5.6. Méthodes
      5.7. Fonctions Globales
      5.8. Local values isolation
      5.9. Exemple: wget
6. Journalisation/Débogage



1. Généralités

UniServe est un serveur d'application multi/protocole pour la programmation réseau. Il est construit sur un moteur d'I/O asynchrone multiplexe et est inspiré par le framework MEDUSA.

Son objectif est d'offrir une solution simple, mais puissante, pour la programmation d'applications clients/serveurs qui peuvent être combinées avec des interfaces Rebol/View facilement.

UniServe consiste en :

Les services sont chargés au démarrage d'UniServe et peuvent être aussi changés, otés et (re)chargés en cours de fonctionnement. Ils peuvent être instanciés plusieurs fois pour écouter sur plusieurs ports simultanément.

Les protocoles sont chargés également au démarrage d'Uniserve et sont utilisables avec une API similaire à port! l'API native de REBOL. Ils peuvent être utilisés pour créer trés facilement des clients qui fonctionneront simultanément avec les services et les événements View au sein d'une boucle d'évenements REBOL.

Les APIs des services et des protocoles sont orientés "évenements" . Ceci signifie que la mise en place d'un nouveau service ou protocole se fait juste en définissant l'une ou l'autre des fonctions réentrantes avec le code approprié. (Un principe proche de ce que VisualBasic faisait pour la programmation facile d'interfaces utilisateur)



2. Premiers Pas


2.1. Installation

Dézipper votre archive Uniserve dans le répertoire où vous souhaitez l'installer. L'installation est finie !

Installer un nouveau logiciel ne devrait pas être plus diffcile que cela, non ? ;-)

Répertoires ayant des espaces dans leur chemin d'accés
Parfois, avoir des espaces dans le chemin d'accès peut être la cause de problèmes dans les applications REBOL, donc évitez les noms de dossier ou chemins d'accès contenant des espaces.


2.2. Tester votre serveur UniServe

Exécutez le script %starter.r. La console REBOL devrait s'afficher avec la liste de tous les services et les protocoles chargés, et Uniserve devrait rentrer dans sa boucle d'événements (attente de connexion de clients). Si vous constatez une ligne d'erreur dans la console indiquant que le service HTTPd ( par exemple) n'a pa pu être lancé, c'est probablement qu'il existe déjà un serveur utilisant le même port. Arrêtez votre serveur local ou essayez de lancer une autre instance du service HTTPd d'Uniserve sur un autre port (mettre 8181 au lieu de 80 par exemple) en utilisant l'outil Monitor.r (placé dans le répertoire %client ).

Ouvrez votre navigateur Web préféré et tester l'adresse suivante : http://localhost. Si tout est OK, vous devriez voir une page "Welcome" avec le logo de Softinnov. Vous avez réussi le test du service HTTPd d'Uniserve !

Remarque : il peut être nécessaire de tester la page avec l'adresse IP de votre machine au lieu de "localhost".

Si vous avez une version de REBOl avec le composant /Shell actif, vous pouvez essayer de cliquer sur le lien "Example CGI Link". Vous devriez voir à présent un extrait de votre environnement CGI.

Votre installation d'Uniserve est OK, à vous maintenant de construire la prochaine "killer-app" ! ;-)


2.3. Arborescence des répertoires

UniServe nécessite une arborescence spécifique pour certains de ses répertoires, afin de fonctionner correctement. Ne changez pas les noms de ces répertoires : directories :

*root*/                 Dossier d'installation
    uni-engine.r        moteur d'UniServe
    starter.r           script lanceur de tous les services
    ...
    libs/               Dossier Librairie
        log.r
        headers.r
    services/           Dossier des services actifs
        ...
        store/          Repository des services
            ...
    protocoles/         Dossier des protocoles actifs
        ...
        store/          Repository des protocoles
            ...
    modules/            Repository des modules
        ...
Utilisez le dossier store/ pour "activer" ou "désactiver" des services avant le démarrage d'UniServe.
Les scripts REBOL valides trouvés dans le répertoire %services seront chargés comme services lors du démarrage d'Uniserve. Un simple déplacement d'un script décrivant un service dans le répertoire %store a pour conséquence de désactiver le service.

Un service peut avoir besoin d'autres fichiers pour fonctionner. Ces fichiers supplémentaires devront être dans un sous-répertoire avec un nom identique à celui du service. (Remarque : attention aussi suivant votre OS aux majuscules/minuscules).

Exemple (ici pour le HTTPd):

services/
    ...
    HTTPd.r
    HTTPd/
        wwwroot/
            ...
        mime.types
        httpd.conf
        ...
NB: la gestion des services peut se faire via le service 'Admin ou via la fonction partagée 'control.
(voir le chapître "Services en cours d'utilisation")



3. Démarrer UniServe

Pour charger le moteur Uniserve :

>> do %uni-engine.r
Le moteur est chargé dans un contexte REBOL spécifique nommé : UniServe.

Pour démarrer le serveur :

>> UniServe/boot
Par défaut, Uniserve sera lancé avec tous les services déclarés dans le sous-répertoire %services/ and
et tous les protocoles trouvés dans le sous-répertoire %protocoles/.

La fonction boot posséde deux rafinements :

Exemple:

UniServe/boot/no-loop/with [
    services  [HTTPd FTPd...]
    protocoles [HTTP FTP...]
]
NB: Vous pouvez omettre l'un des mots-clés ('services ou 'protocoles) au lieu de fournir un bloc vide.

Indication du répertoire d'installation
Si vous démarrez UniServe depuis un répertoire différent du répertoire courant, vous devez spécifier le chemin d'accés à l'arborescence d'UniServe en positionnant la variable UniServe-path. Le chemin doit se terminer par un "slash". Vous devez définir le chamin d'accés avant d'appeler la fonction 'boot.

Example:

>> UniServe-path: %/library/UniServe/
>> UniServe/boot

Une fois démarré, si le raffinement /no-loop a été spécifié, UniServe nécessite une boucle d'évenements pour fonctionner.
Cette boucle évenementielle peut être réalisée par un appel à la fonction 'view ou directement avec un 'do-events :

Pour initier une attente d'évenements, vous pouvez choisir entre ces deux alternatives :

>> view ...
ou

>> do-events



4. API des Services


4.1. Définition d'un service

Un nouveau service devrait être codé dans un fichier dans le nom est : le nom du service associé à l'extension ".r" . (voir le répertoire %services dans l'archive UniServe )

Chaque service est défini en utilisant la fonction install-service. Un en-tête REBOL (REBOL[...]) est nécessaire. (Vous pouvez le laisser vide.)

install-service [
    ... definition service...
]
NB: Vous n'avez pas besoin d'effectuer un 'do ou un 'load de vos scripts pour utiliser la fonction.

Mettez tout le code concernant les services dans le bloc de définition de service.
Vous pouvez inclure des librairies externes si nécessaire. Tout ce qui n'est pas à l'intérieur du bloc de définition des services devrait être globalement BINDé.


4.2. Traitement des données entrantes

UniServe fournit au développeur un système unique pour implémenter des protocoles réseaux.
La gestion des données entrantes est réalisée à bas niveau par UniServe.

Pour être capable de décoder n'importe quel paquet logique de données entrantes (pas les paquets physiques IP),
vous devez savoir quand le paquet se termine. Il existe deux façons de réaliser ceci :

UniServe va gérer automatiquement les données entrantes en utilisant l'une de ces deux méthodes.
C'est au développeur de définir la bonne méthode en utilisant la propriété stop-at des services.

Lorsque l'un de ces indicateurs de fin est atteint, UniServe appelle la fonction on-received définie dans votre déclaration de service avec les données recues en argument.

Exemple :

Une machine distante envoie la chaîne "Hello. I'm ok." à votre service
[stop-at: #"."]       générera les appels suivants :
-> on-received "Hello."
-> on-received " I'm ok."
[stop-at: 1]        générera les appels suivants :
-> on-received "H"
-> on-received "e"
-> on-received "l"
-> on-received "l"
-> on-received "o"
-> on-received "."
-> on-received " "
-> on-received "I"
-> on-received "'"
-> on-received "o"
-> on-received "k"
-> on-received "."
[stop-at: 4]        générera les appels suivants :
-> on-received "Hell"
-> on-received "o. I"
-> on-received "'m o"
                    <- notez qu'il manque ici les caractères "k."
                    puisqu'il n'y a pas encore de données dans le buffer d'entrée
                    pour déclencher un nouvel évenement de type 'on-receive !!
                    Un nouvel évenement peut être générer si des nouvelles données arrivent
                    ("k." est présent dans le buffer interne d'UniServe)
stop-at peut être modifié à n'importe quel moment dans la définition de service, permettant l'implémentation de protocoles plus complexes (par exemple en switchant d'un mode à l'autre).
Les nouvelles valeurs deviennent effectives dès que la fonction en cours sur un évenement se termine.

Vous pouvez éventuellement décider de désengager le système de gestion des données entrantes en positionnant stop-at à la valeur none! .
Dans ce mode, UniServe appellera la fonction on-raw-received dans votre definition de service chaque fois que votre application recoit un paquet physique (TCP ou UDP) de la machine distante.

Type de données recues
Rappelez-vous que les données sont toujours transmises sous forme de valeurs binaires (binary!) à vos fonctions 'on-received et 'on-raw-received.
Il sera souvent nécessaire de les convertir en string! pour les manipuler correctement.


4.3. Propriétés

La liste suivante recense les propriétés utiles pour coder votre service.
Certaines sont obligatoires pour développer un service valide.

Mot/item Type Rebol Obligatoire? Description
name word! oui Id, nom identifiant de manière unique le service
port-id integer! oui Le numéro du port d'écoute par défaut.
stop-at integer!
string!
char!
binary!
none!
non Définit quand l'évenement 'on-received sera généré.
Valeur par défaut: none.
scheme word! non Valeur par défaut : 'tcp, il est possible de changer cela pour 'udp
module word! non Le nom du module pour le traitement en tâche de fond
hidden logic! non (réservé pour un usage futur)
client port! non Propriété positionnée sur la valeur courante du port! client
shared object! non Accès à l'espace partagé d'UniServe. Vous êtes libre d'ajouter des variables ici, pour que les autres services puissent les utiliser .

propriété Client
Client (de type port!) référence les informations de connexions utilisées par le client distant.
Vous pouvez utiliser les propriétés internes de cet objet pour accéder aux informations de connexion.

Par exemple, si vous voulez connaitre l'adresse IP du client :

client/remote-ip


4.4. Evenements

Les évenements sont des fonctions spéciales appelées par le moteur d'UniServe pour traiter les évenements réseau. Par défaut, ces fonctions sont définies à none. C'est au développeur de choisir et d'implémenter les évenements appropriés.

Mot Prototype Description
on-load func [] Appelée au chargement du service par Uniserve. Placez votre code d'initialisation ici si besoin.
on-new-client func [] Appelée quand une nouvelle connexion est acceptée par UniServe
on-close-client func [] Appelée lorsqu'un client ou le service clôture la connexion .
on-received func [data [binary!]] Appelée quand la séquence attendue ou la longueur définie dans stop-at est atteinte.

data : toutes les données recues du client depuis le précédent évenement 'on-received. data est effacé une fois que la fonction a été évaluée, donc utilisez 'copy si vous voulez conserver le contenu de data.
on-raw-received func [data [binary!]] Appelée quand un paquet brut TCP ou UDP est reçu..

data : Toutes les données recues du client depuis le dernier évenement 'on-raw-received. data est effacé une fois que la fonction a été évaluée, donc utilisez 'copy si vous voulez conserver le contenu de data.

Au moins un !
Vous devez définir au moins l'un de ces évenements si vous voulez que votre service serve à quelque chose !

Evitez d'employer du code bloquant dans les événements !
Les développeurs doivent faire attention à leur implémentation des fonctions pour les événements et doivent être sûr qu'il n'y a pas de tâches bloquantes
(comme une connexion à une machine distante en mode synchrone, effectuer de longs calculs, etc...).
Ceci affecte directement les performances du serveur Uniserve. Si vous avez à réaliser des tâches bloquantes ou longues, utilisez le service 'task-master et des processus en tâche de fond.


4.5. Méthodes

Il y a plusieurs fonctions pré-définies dans le contexte des services :

Mot Prototype Description
write-client data [string! binary! file!] Renvoie des données au client. Cette fonction est non bloquante, de sorte que vous pouvez l'utiliser sans souci dans les fonctions d'évenements. (Les données ne sont pas envoyées immédiatement, mais sont mises en attente pour le traitement suivant)

data : data à envoyer.
close-client /now Ferme la connexion du client . Cette fonction est non bloquante, de sorte que vous pouvez l'utiliser sans souci dans les fonctions d'évenements.
(Le port client sera fermé lorsqu'il n'y aura plus de données en attente)

/now : Ferme la connexion client immédiatement et efface les données en attente.
share values [block!] Ajoute des valeurs dans l'espace partagé. (voir la section "Espace partagé")

values : block of name/value pairs. (name [set-word!], value [any-type])

Serving files
si vous utilisez un argument de type file! pour write-client, le fichier considéré ne devrait pas être chargé (load) en mémoire. Uniserve l'enverra par morceaux au client, en lisant à chaque fois sur le disque seulement les données nécessaires. Ceci permet d'utiliser des fichiers sans limite de taille, sans impact sur les performances d'Uniserve ou la mémoire.


4.6. Portée locale des variables

Le code définissant un service doit être ré-entrant, c'est à dire qu'il peur être appelé avec différents contextes clients sans altération ou mélange de données entre clients. Pour éviter de tels problèmes, vous avez à stocker les paramètres de session d'un client dans : client/user-data.

Exemple :

client/user-data: context [
    id: 3432
    state: 'logged
    etc...
]
Tous les mots définis dans le contexte du service sont "globaux" pour tous les clients qui l'utilisent. Par contre, les mots stockés dans client/user-data sont spécifiques à ce client.

Par exemple, si vous comptabilisez le nombre courant de clients, vous pouvez faire :

install-service [
    ...
    count: 0
    on-new-client: does [count: count + 1]
    on-close-client: does [count: count - 1]
]
Il y a d'autres manières de sauver les paramétres de session. Vous êtes libre de choisir quelle méthode vous souhaitez pour "isoler" des données spécifiques d'un client.


4.7. Espace partagé

L'espace partagé global est juste une variable (object!) qui peut être utilisée à n'importe quel moment par n'importe quel service.

Tous les services peuvent ajouter au chargement leurs propres valeurs dans cet espace partagé avec la fonction share.

share [
    a: "hello"
    pool: 50
    do-task: func [...]...
    ...
]
Tous les services peuvent accéder à cet espace durant le fonctionnement grâce à : shared.

probe shared/a
shared/a: 2
shared/do-task args...
L'objet shared contient par défaut une seule méthode définie :

shared: context [
    control: func [...]...      ; fonction d'administration UniServe  .
]


4.8. Installer un service

Une fois créé, votre nouveau service se place dans le répertoire %services et sera automatiquement chargé par UniServe au démarrage suivant.

Chargement manuel des services
Si vous souhaitez charger manuellement des services particuliers, vous devrez le faire avant le démarrage de la boucle évenementielle.

Exemple :

do %uni-engine.r
UniServe/boot/no-loop
install-service [...]
ou

#include %services/my-service.r     ; pour le préprocesseur PREBOL 
puis

do-events   ; ou view...


4.9. Fonctionnement des services

Quand un nouveau client se connecte via un service actif, UniServe crée une variable de type port! pour gérer la connection.
UniServe utilise la propriété locals de l'objet port! pour stocker toutes les informations nécessaires au traitement des événements sur la connexion.

port/locals
La variable locals de chaque connexion ('port) gérée par UniServe est réservée pour un usage interne. Ne changez rien dans 'port/locals à moins de savoir exactement ce que vous faites !

Ici, un point intéressant à noter est que chaque port/locals/handler pointera sur votre définition de service.

L'arrêt d'un service, en cours de fonctionnement, peut être réalisé à distance grâce au protocole admin:// ou localement en utilisant la fonction Uniserve (partagée) control ( chemin : shared/control)

Voici une courte description de la fonction control :

control
    name        [word!]     Nom du service
    id          [integer!]  Numéro du port d'acoute du service
    /start                  Démarre une nouvelle instance du service sur le port d'écoute :id
        /all                Démarre tous les services avec leur port d'acouté par défaut
    /stop                   Arréte un service
    /list                   Retourne la liste des services
    /install                (not implemented)
        file                (not implemented)


4.10. Exemple: micro-httpd

Voici le code source pour un mini serveur web, adapté d'après le script %webserver.r script (vous pouvez trouver le script originel ici)

Les mots-clés UniServe's sont colorés en rouge.

'micro-httpd service source code
install-service [

    name: 'micro-httpd
    port-id: 81

    mime: htfile: file: none

    stop-at: join crlf crlf

    wwwpath: %wwwroot/  ; path must end with a slash !

    html: "text/html" gif: "image/gif"  jpg: "image/jpeg"

    not-found: [
        "404 Not Found"
        [<HTML><STRONG><H1>ERROR</H1></STRONG><H4><p><p>
         "REBOL Webserver error 404"<BR>"File not found"</html>]
    ]

    bad-perms: [
        "400 Forbidden"
        [<HTML><STRONG><H1>ERROR</H1></STRONG><H4><p><p>"REBOL Webserver"
         "error 400"<br>"You do not have permission to view the file"</html>]
    ]

    http-head: func [type mime][
        reform ["HTTP/1.0" type newline "Content-type:" mime newline newline]
    ]

    error: func [err][
        write-client append http-head err/1 html err/2
    ]

    on-received: func [data][
        if "HTTP" = htfile: third parse data "/" [htfile: "index.html"]
        mime: parse htfile "."
        if error? try [mime: get to-word mime/2][mime: html]
        any [all [not exists? file: join wwwpath htfile error not-found]
             all [error? try [read/binary/part file 1] error bad-perms]
             all [
                write-client http-head "200 OK" mime
                write-client file
             ]
        ]
        close-client
    ]
]

Remarques :



5. API des protocoles


5.1. Définition de Protocole

Les protocoles sont les équivalents côté client des services. Leur but est de fournir une implémentation de protocoles réseaux orientés clients, en tant que protocoles dédiés. Vous constaterez un grand nombre de ressemblance entre les APIs des protocoles et des services, de sorte que si vous savez coder un service, vous saurez aussi coder un protocole client ! :-)

Un nouveau protocole doit être écrit dans un fichier dont le nom correspond au nom du protocole, plus l'extension ".r" . (voir le répertoire %protocoles de l'archive UniServe)

Chaque protocole est défini avec la fonction install-protocol .
Un en-tête REBOL ( Rebol[...]) est requis, même si vous le laissez vide.

install-protocol [
    ...définition protocole ...
]
(NB: Vous n'avez pas besoin de charger ( 'do ou 'load) vos scripts pour utiliser cette fonction .)

Mettez tout le code définissant le protocole dans la définition. Vous pouvez inclure des librairies externes si besoin. Tout ce qui se trouve en dehors du bloc de définition devra être globalement BINDé.


5.2. Traitement des données entrantes

Les données entrantes sont manipulées exactement de la même manière que pour les services.


5.3. Propriétés

Voici la liste des propriétés possibles pouvant être utilisées dans la définition de votre protocole. Certaines d'entre elles sont obligatoires pour obtenir un protocole valide.

Mot Type Obligatoire?? Description
name word! oui Nom identifiant de manière unique le protocole
port-id integer! oui Le numéro de port par défaut
events block! oui Liste des noms d'évenements définis par l'utiisateur
stop-at integer!
string!
char!
binary!
none!
non Définit quand l'évenement 'on-received event sera généré.
La valeur par défaut est none.
scheme word! non La valeur par défaut est 'tcp, il est possible de le changer pour : 'udp
server port! non Valeur définie durant le fonctionnement et référencant la valeur server de l'objet port! (nom du serveur)
connect-retries integer! non Définit le nombre maximum de tentatives de connexions au serveur. Par défaut : 5

Propriété Server
L'objet Server référence les infos courantes de connections (type port!) pour la machine distante. Vous pouvez utilisez les définitions habituelles internes à port! pour accéder à toutes les informations de connection .

Par exemple, si vous souhaitez connaître l'adresse IP du serveur :

server/remote-ip


5.4. Evenements

Les évenements s'assimilent à des fonctions spéciales appelées par le moteur UniServe pour traiter les évenements du réseau. Par défaut, ces items sont définis à none. Il revient au développeur de choisir et d'implémenter les évenements appropriés.

Mot/item Prototype Description
on-init-port func [port [port!] url [url!]] Appelée avant l'ouverture d'une connexion. Utile pour les changements de paramétres de dernière minute comme le nom utilisateur et le mot de passe.
on-connected func [] Appelée lorsque la connection est établie avec la machine distante. (avant l'envoi ou la réception des données )
on-close-server func [] Appelée quand le client ou le serveur ferme la connection.
on-received func [data [binary!]] Appelée lorsque la séquence attendue ou la longueur définie par stop-at est atteinte.

data : contient toutes les données recues par le client depuis le précédent appel à 'on-received. data est effacée une fois que la fonction a été évaluée, de sorte qu'il est conseillé d'utiliser 'copy si vous souhaitez conserver le contenu de data .
on-raw-received func [data [binary!]] Appelée quand un paquet physique TCP ou UDP est reçu.

data : contient toutes les données recues par le client depuis le précédent appel à 'on-raw-received. data est effacée une fois que la fonction a été évaluée, de sorte qu'il est conseillé d'utiliser 'copy si vous souhaitez conserver le contenu de data .
on-error func [why [word!] port [port!]] Appelée lorsqu'une erreur se produit durant la connexion.

L'argument why dans l'évenement on-error peut prendre les valeurs suivantes :

 unknown-hostLe nom de machine n'a pu être résolu !
 unreachableil n'y a pas de route valable pour atteindre le serveur !
 connect-failedLe serveur n'a pas répondu après plusieurs tentatives de connections !


5.5. Evenements utilisateur

En vue de rendre vos protocoles utilisables, vous avez à définir une API, utilisant des évenements personnalisés, appelée ici "user events". Elle est orientée "user", car c'est l'interface que l'implémentation de votre protocole fournira au protocole utilisateur.

Les protocoles sont, en fait, juste une couche entre les événements Uniserve et les évenements utilisateurs !

Les événements utilisateurs sont définis dans votre protocole en utilisant le mot-clé events. Vous avez juste à fournir la liste des noms d'évenements, sans ordre particulier. Vous appelez alors ces évenements comme des fonctions aux endroits stratégiques de votre code . C'est tout !

Exemple :

events: [
    on-login-ok
    on-message
    ...
]
Vous pouvez utiliser n'importe quel nom. Dans UniServe, les noms des évenements sont préfixés avec on- pour facilement les reconnaître dans le code source. Utilisez des noms significatifs qui reflétent l'état courant de la communication avec le serveur ou de l'objet que vous vous attendez à recevoir, etc...

Si vous devez passer des arguments à vos événements d'utilisateur, faites-le !
Il n'y a rien de spécial à faire pour permettre à vos évenements de prendre des arguments. Souvenez-vous de documenter vos fonctions quelque part ! Pour l'instant, une ligne de commentaire pour chaque évenement spécifiant l'en-tête attendu . (Nous pensons ajouter un moyen de génération automatique de la documentation pour les évenements utilisateur dans les prochaines versions)

Un exemple plus complet :

install-protocol [
    name: 'pop
    ...
    on-received: func [data][
        ...
        on-login-ok server
        ...
        ...
        on-message server import-email msg
        ...
    ]

    events: [
        on-login-ok     ; [port [port!]]
        on-message      ; [port [port!] msg [object!]]
        ...
    ]
]
NB: Il est pratique (mais pas obligatoire) de passer la valeur du port en tant que premier argument. Ceci donne au protocole utilisateur un moyen facile d'accés à toutes les propriétés du port en incluant le contexte local port/user-data .


5.6. Méthodes

Plusieurs fonctions sont pré-définies dans le contexte du protocole :

Word Arguments Description
write-server data [string! binary! file!]

/with port [port!]
Envoie les données au serveur. Cette fonction n'est pas bloquante, aussi vous pouvez l'utiliser sans inquiétude dans vos fonctions évenements. (Les données ne sont pas envoyées en une fois, mais mise en cache pour le traitement suivant.)

data : Données à transmettre.

port : valeur du port à utiliser pour envoyer les données.
close-server   Ferme la connection avec le serveur. Cette fonction n'étant pas bloquante, vous pouvez l'utiliser sans souci dans vos fonctions évenements. (Le port serveur sera fermé lorsque qu'il n'y aura plus de données en attente)

Envoi de fichiers
Si l'argument de write-server est de type file!, le fichier ne sera pas chargé dans la mémoire. UniServe l'enverra par morceaux au serveur, lisant seulement sur le disque à chaque fois que des données sont requises. Ceci permet l'envoi de fichiers non limités en taille sans dégrader les performances d'UniServe.


5.7. Fonctions Globales

Ce jeu de fonctions vous permet d'administrer les protocoles Uniserve durant le fonctionnement, de façon très similaire à celle des protocoles pré-définis avec REBOL. Elles sont globalement définies (dans le contexte global REBOL), de façon à pouvoir les utiliser partout. Rappelez-vous que ces fonctions sont non bloquantes !

Word/mot Arguments Description
open-port url [url!] events [block!]

/with spec [block!]
Crée et renvoie un nouvel objet port! travaillant en mode asynchrone.

url : une url valide spécifiant les informations usuelles de connexion : scheme, host, target, etc...
events : un bloc de fonctions-évenements.

spec : étend la portée de l'objet port! avec des définitions supplémentaires. Cela consiste en une liste de paires : nom: valeur .
insert-port port [port!] data [any-type!] Envoie les données via le port!.
close-port port [port!] Ferme la connexion et déclenche l'évenement on-close-server.

L'argument events de 'open-port est un bloc d'évenements utilisant le format suivant :

open-port url [
    event1: func ...
    event2: func ...
    ...
]
Chaque nom d'événement doit être défini dans l'implémentation du protocole qui est appelé via l'URL.
N'ajoutez pas aucune autre genre de valeur au bloc d'événement !

Le rafinement /with de la fonction 'open-port est utile pour stoker des valeurs locales qui peuvent facilement être rappelées par l'évenement.

Personaliser 'insert-port
La fonction insert-port est, basiquement, un wrapper (encapsulateur) sur write-server. Cela signifie que les données devront être envoyées sans aucun formatage ou encodage. Si vous voulez ajouter une couche pour traiter les données avant envoi (dialecte, compression, encodage, etc...).
Ceci peut être réalisé en définissant une nouvelle fonction dans la définition de votre protocole, qui sera automatiquement appeléee par insert-port.

Votre nouvelle fonction insert-port devra être nommée new-insert-port et définie ainsi :

new-insert-port: func [port [port!] data [...]]...
si vous définissez 'new-insert-port, souvenez-vous d'appelez write-server pour envoyer les données traitées au serveur.


5.8. Portée locale des variables

Les mêmes régles décrites dans la définition des services s'appliquent à l'écriture du code des protocoles.

Vous pouvez aussi utiliser les rafinements /with de la fonction 'open-port pour ajouter des variables locales à votre 'port.


5.9. Exemple: wget

Voici le code source d'une implémentation pour un simple HTTP GET utilisant UniServe.

Les mots-clés d'Uniserve sont colorés en rouge.

'wget protocol source code
install-protocol [

    name: 'wget
    port-id: 80

    stop-at: header-end: join crlf crlf

    on-connected: does [
        write-server rejoin [
            "GET " server/target
            " HTTP/1.0" crlf
            "Host: " server/host
            crlf crlf
        ]
        print ["connecting to:" server/host]
    ]

    on-received: func [data /local pos][
        either integer? stop-at  [
            on-response server to-string data
            stop-at: header-end    ; Reset stop-at for next request
        ][
            ; HTTP header received
            either pos: find/tail data "Content-Length:" [
                ; Extract document size
                data: to-string trim/all copy/part pos find pos newline
                stop-at: to-integer data
            ][
                on-failed server
            ]
        ]
    ]

    events: [
        on-response     ; [port [port!] result]
        on-failed       ; [port [port!]]
    ]
]

Exemple d'usage avec la console :

>> UniServe/boot/no-loop/with [protocoles [wget]]
[UniServe] Async Protocol wget loaded
>> open-port wget://www.rebol.net [
    on-response: func [port result][
        print ["Page size =" length? result]
    ]
]
>> do-events
connecting to: www.rebol.net
Page size = 4375
Remarque :



6. Journalisation/Débogage

Tous les élèments d'UniServe : moteur, services, protocoles utilisent l'objet logger (défini dans le fichier %libs/log.r) pour générer des informations en cours de fonctionnement . Vous pouvez aussi l'utiliser facilement dans vos services et protocoles. L'objet logger étant chargé avec le moteur Uniserve, il est donc accessible par tout votre code source. Il posséde une fonction globale appelée log ayant les spécifications suivantes :

log msg
    /info       ; affiche une information
    /warn       ; affiche une alerte
    /error      ; affiche une erreur (sans bloquer le programme)
L'argument msg peut être de n'importe quel type. Il sera modifié (REJOIN) par la fonction log.

L'objet logger peut également permettre de contrôler le mode de sortie (output) via la variable logger/level. Celle-ci peut prendre l'une des valeurs : 'screen (default), 'file, 'both, 'csv. ('both = 'screen + 'file)

Pour autoriser ou non la redirection , nous utilisons habituellement un mot particulier dans tous les contextes dérivant de l'objet logger : verbose. En lui donnant une valeur entière (integer!), vous pouvez contrôler la quantité d'informations générée. (zero signifie : pas de sortie).

Chaque service et protocole dans l'archive Uniserve est bâti sur l'objet logger, lequel fournit des possibilités de journalisation avancées. Mettre la variable verbose à 0 supprime toute journalisation. A 4, la journalisation est maximale.

par exemple, pour voir comment fonctionne le moteur en temps réel :

uniserve/verbose: 2
L'exemple précédent avec 'wget en console devrait à présent donner :

>> do-events
[uniserve] Connecting to www.rebol.net : 80
[uniserve] Connected to 209.167.34.214 port: 80
[uniserve] Calling >on-connected<
connecting to: www.rebol.net
[uniserve] << low-level writing: 39
[uniserve] >> low-level reading: 1420
[uniserve] Calling >on-received< with "HTTP/1.1 200 OK^M^/Date: Sat, 09 Oct 2004 21:37:30 G"
[uniserve] >> low-level reading: 1420
[uniserve] >> low-level reading: 1420
[uniserve] >> low-level reading: 412
[uniserve] Calling >on-received< with {<HTML>
<!--Page generated by REBOL-->
<HEAD>
<TITL}
Page size = 4375
[uniserve] Connection closed by peer 209.167.34.214
[uniserve] Calling >on-close-server<
[uniserve] Port closed : 209.167.34.214
NdT: Autre exemple, l'usage de : "verbose: 2" dans la déclaration du service HTTPd permet d'obtenir les traces sur le service.


Copyright 2004 SOFTINNOV All Rights Reserved.
Formatted with REBOL Make-Doc 0.9.6.1 on 10-Oct-2004 at 17:53:46