Appeler un programme Python qui reste en marche avec PYPELINE()
PYPELINE(name, [options]) démarre pypelines/name.py avec python3 et garde une connexion avec lui, de sorte que l'application peut utiliser des bibliothèques Python (données, modèles, traitement d'images) sans démarrer un nouveau processus pour chaque requête. Il renvoie un objet avec send(data) pour envoyer un message, on('data', fn) pour recevoir les réponses et close() pour arrêter le script. Un nom qui commence par ~ est au contraire le chemin d'un fichier (~/opt/job.py), et un texte qui est du code (il a des sauts de ligne ou ressemble à du Python) est exécuté comme un programme en ligne avec python3 -c.
- Le protocole. Le script est démarré comme
python3 -u script.py <socket>: le dernier argument est le chemin d'un socket Unix sur lequel l'application écoute, et le script s'y connecte. Chaque message, dans les deux sens, est une trame : 4 octets avec la longueur (big endian) suivis du corps, qui est du JSON par défaut. - Options :
type('json'par défaut,'text', ou toute autre valeur pour desBufferbruts),autorestart(falsepour ne pas relancer le script quand il se termine ; par défaut il est relancé),threadsetsocket: false(pas de socket, seulement démarrer le programme). - Les messages envoyés avant que le script se soit connecté attendent et sont remis quand il se connecte.
- Événements :
open(le script s'est connecté),data(un message),restartetclose. - Le chemin du socket a une limite. Le socket est un fichier dans le dossier
tmpde l'application et le système limite le chemin entier (environ 104 caractères sur macOS et 108 sur Linux). Dans un projet profondément enfoui sur le disque, le serveur échoue aveclisten EINVAL, le script ne se connecte jamais et les messages attendent indéfiniment. Le chemin est la propriétépypeline.socket: définissez-la juste aprèsPYPELINE()(le fichier est vérifié et le serveur démarré ensuite) à un chemin court, comme le fait l'exemple. - Le programme Python est un programme normal qui s'exécute avec les droits de l'application : il n'est aussi sûr que le code du fichier, et Python 3 doit être installé (
python3dans lePATH).
import json
import socket
import struct
import sys
# The last argument is the socket that the application listens on
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(sys.argv[-1])
def read(count):
data = b''
while len(data) < count:
chunk = sock.recv(count - len(data))
if not chunk:
raise SystemExit(0)
data += chunk
return data
while True:
size = struct.unpack('>I', read(4))[0]
message = json.loads(read(size).decode('utf-8'))
answer = json.dumps({ 'id': message['id'], 'upper': message['text'].upper(), 'words': len(message['text'].split()) }).encode('utf-8')
sock.sendall(struct.pack('>I', len(answer)) + answer)exports.install = function() {
ROUTE('POST /shout/ @json', shout);
};
const pypeline = PYPELINE('shout');
// A short path for the socket: the folder of a project can be too deep for a Unix socket
pypeline.socket = require('os').tmpdir() + '/forge-shout-' + process.pid + '.socket';
const pending = {};
let counter = 0;
// Every answer says which message it is for
pypeline.on('data', function(answer) {
const callback = pending[answer.id];
if (callback) {
delete pending[answer.id];
callback(answer);
}
});
function shout($) {
const id = ++counter;
const timer = setTimeout(() => $.invalid('The script did not answer'), 5000);
pending[id] = function(answer) {
clearTimeout(timer);
$.json(answer);
};
pypeline.send({ id: id, text: $.body.text });
}