Folyamatosan futó Python-program hívása a PYPELINE()-nal
A PYPELINE(name, [options]) elindítja a pypelines/name.py fájlt python3-mal, és kapcsolatot tart vele, így az alkalmazás Python-könyvtárakat (adatok, modellek, képfeldolgozás) használhat anélkül, hogy minden kéréshez új folyamatot indítana. Egy olyan objektumot ad vissza, amelyben a send(data) üzenetet küld, az on('data', fn) fogadja a válaszokat, a close() pedig leállítja a szkriptet. A ~-lel kezdődő név ehelyett egy fájl útvonala (~/opt/job.py), a kódot tartalmazó szöveg (sortörései vannak vagy Pythonnak néz ki) pedig beágyazott programként fut a python3 -c-vel.
- A protokoll. A szkript
python3 -u script.py <socket>alakban indul: az utolsó argumentum egy Unix socket útvonala, amelyen az alkalmazás figyel, és a szkript ehhez csatlakozik. Minden üzenet, mindkét irányban, egy keret: 4 bájt a hosszal (big endian), majd a törzs, amely alapértelmezés szerint JSON. - Opciók:
type('json'alapértelmezett,'text', vagy bármi más nyersBufferekhez),autorestart(false, hogy a szkript ne induljon újra, amikor véget ér; alapértelmezés szerint újraindul),threadséssocket: false(nincs socket, csak a program indul). - Az üzenetek, amelyeket a szkript csatlakozása előtt küldtek, várnak, és a csatlakozáskor kézbesítődnek.
- Események:
open(a szkript csatlakozott),data(egy üzenet),restartésclose. - A socket útvonalának van korlátja. A socket az alkalmazás
tmpmappájában lévő fájl, és a rendszer a teljes útvonalat korlátozza (macOS-en kb. 104, Linuxon 108 karakter). Egy mélyen a lemezen lévő projektben a szerverlisten EINVALhibával elbukik, a szkript sosem csatlakozik, az üzenetek pedig örökké várnak. Az útvonal apypeline.sockettulajdonság: állítsa be közvetlenül aPYPELINE()után (a fájl ellenőrzése és a szerver indítása ezután történik) egy rövidre, ahogy a példa is teszi. - A Python-program egy hétköznapi program, amely az alkalmazás jogaival fut: csak annyira biztonságos, mint a fájl kódja, és a Python 3-nak telepítve kell lennie (
python3aPATH-ban).
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 });
}