Ein weiterlaufendes Python-Programm mit PYPELINE() aufrufen
PYPELINE(name, [options]) startet pypelines/name.py mit python3 und hält eine Verbindung zu ihm, sodass die Anwendung Python-Bibliotheken (Daten, Modelle, Bildverarbeitung) nutzen kann, ohne für jede Anfrage einen neuen Prozess zu starten. Es gibt ein Objekt mit send(data) zum Senden einer Nachricht, on('data', fn) zum Empfangen der Antworten und close() zum Stoppen des Skripts zurück. Ein Name, der mit ~ beginnt, ist stattdessen der Pfad einer Datei (~/opt/job.py), und ein Text, der Code ist (er hat Zeilenumbrüche oder sieht nach Python aus), wird als Inline-Programm mit python3 -c ausgeführt.
- Das Protokoll. Das Skript wird als
python3 -u script.py <socket>gestartet: Das letzte Argument ist der Pfad eines Unix-Sockets, auf dem die Anwendung lauscht und mit dem sich das Skript verbindet. Jede Nachricht ist in beiden Richtungen ein Frame: 4 Bytes mit der Länge (Big Endian), gefolgt vom Body, der standardmäßig JSON ist. - Optionen:
type('json'als Standard,'text'oder irgendetwas anderes für roheBuffer),autorestart(false, um das Skript nicht neu zu starten, wenn es endet; standardmäßig wird es erneut gestartet),threadsundsocket: false(kein Socket, nur das Programm starten). - Nachrichten, die gesendet werden, bevor sich das Skript verbunden hat, warten und werden zugestellt, sobald es sich verbindet.
- Ereignisse:
open(das Skript hat sich verbunden),data(eine Nachricht),restartundclose. - Der Pfad des Sockets hat ein Limit. Der Socket ist eine Datei im Ordner
tmpder Anwendung, und das System begrenzt den gesamten Pfad (etwa 104 Zeichen unter macOS und 108 unter Linux). In einem Projekt, das tief auf der Platte liegt, schlägt der Server mitlisten EINVALfehl, das Skript verbindet sich nie und die Nachrichten warten für immer. Der Pfad ist die Eigenschaftpypeline.socket: Setze sie direkt nachPYPELINE()(die Datei wird danach geprüft und der Server danach gestartet) auf einen kurzen, wie es das Beispiel tut. - Das Python-Programm ist ein normales Programm, das mit den Rechten der Anwendung läuft: Es ist nur so sicher wie der Code der Datei, und Python 3 muss installiert sein (
python3imPATH).
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 });
}