Извикайте Python програма, която остава пусната, с PYPELINE()
PYPELINE(name, [options]) стартира pypelines/name.py с python3 и поддържа връзка с него, така че приложението да може да използва Python библиотеки (данни, модели, обработка на изображения), без да стартира нов процес за всяка заявка. Връща обект с send(data) за изпращане на съобщение, on('data', fn) за получаване на отговорите и close() за спиране на скрипта. Име, което започва с ~, е вместо това пътят на файл (~/opt/job.py), а текст, който е код (има нови редове или прилича на Python), се изпълнява като вграден код с python3 -c.
- Протоколът. Скриптът се стартира като
python3 -u script.py <socket>: последният аргумент е пътят на Unix сокет, на който приложението слуша, и скриптът се свързва към него. Всяко съобщение, в двете посоки, е кадър: 4 байта с дължината (big endian), следвани от тялото, което по подразбиране е JSON. - Опции:
type('json'по подразбиране,'text'или нещо друго за суровBuffer),autorestart(false, за да не се рестартира скриптът, когато приключи; по подразбиране се стартира отново),threadsиsocket: false(без сокет, само стартиране на програмата). - Съобщенията, изпратени преди скриптът да се е свързал, чакат и се доставят, когато той се свърже.
- Събития:
open(скриптът се свърза),data(съобщение),restartиclose. - Пътят на сокета има ограничение. Сокетът е файл в папката
tmpна приложението и системата ограничава целия път (около 104 знака в macOS и 108 в Linux). В проект, който е дълбоко на диска, сървърът се проваля сlisten EINVAL, скриптът никога не се свързва и съобщенията чакат вечно. Пътят е свойствотоpypeline.socket: задайте го веднага следPYPELINE()(файлът се проверява и сървърът се стартира след това) на кратък, както прави примерът. - Python програмата е обикновена програма, която работи с правата на приложението: тя е толкова сигурна, колкото е кодът на файла, и трябва да е инсталиран Python 3 (
python3вPATH).
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 });
}