Apelează un program Python care rămâne pornit cu PYPELINE()
PYPELINE(name, [options]) pornește pypelines/name.py cu python3 și păstrează o conexiune cu el, astfel încât aplicația să poată folosi biblioteci Python (date, modele, procesare de imagini) fără să pornească un proces nou pentru fiecare cerere. Returnează un obiect cu send(data) pentru a trimite un mesaj, on('data', fn) pentru a primi răspunsurile și close() pentru a opri scriptul. Un nume care începe cu ~ este în schimb calea unui fișier (~/opt/job.py), iar un text care este cod (are rupturi de rând sau seamănă cu Python) se rulează ca program inline cu python3 -c.
- Protocolul. Scriptul este pornit ca
python3 -u script.py <socket>: ultimul argument este calea unui socket Unix pe care aplicația ascultă, iar scriptul se conectează la el. Fiecare mesaj, în ambele sensuri, este un cadru: 4 octeți cu lungimea (big endian) urmați de corp, care implicit este JSON. - Opțiuni:
type('json'implicit,'text'sau orice altceva pentruBuffer-e brute),autorestart(falsepentru a nu reporni scriptul când se termină; implicit este pornit din nou),threadsșisocket: false(fără socket, doar pornește programul). - Mesajele trimise înainte ca scriptul să se conecteze așteaptă și sunt livrate când se conectează.
- Evenimente:
open(scriptul s-a conectat),data(un mesaj),restartșiclose. - Calea socketului are o limită. Socketul este un fișier în folderul
tmpal aplicației, iar sistemul limitează calea întreagă (aproximativ 104 caractere pe macOS și 108 pe Linux). Într-un proiect aflat adânc pe disc, serverul eșuează culisten EINVAL, scriptul nu se conectează niciodată, iar mesajele așteaptă la nesfârșit. Calea este proprietateapypeline.socket: setează-o imediat dupăPYPELINE()(fișierul este verificat și serverul pornit după aceea) la una scurtă, așa cum face exemplul. - Programul Python este un program obișnuit care rulează cu drepturile aplicației: este sigur doar cât este codul fișierului, iar Python 3 trebuie să fie instalat (
python3înPATH).
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 });
}