ForgeTotal.js 5
cs

Total.js 5

Volejte program v Pythonu, který stále běží, pomocí PYPELINE()

Total.js 5 · total5@0.0.19 pythonpypelineintegrationsai Spuštěno s Total.js 5

PYPELINE(name, [options]) spustí pypelines/name.py pomocí python3 a udržuje s ním spojení, takže aplikace může používat knihovny Pythonu (data, modely, zpracování obrázků), aniž by pro každý požadavek spouštěla nový proces. Vrátí objekt s send(data) k odeslání zprávy, on('data', fn) k přijímání odpovědí a close() k zastavení skriptu. Název, který začíná ~, je místo toho cesta k souboru (~/opt/job.py) a text, který je kódem (má zalomení řádků nebo vypadá jako Python), se spustí jako inline program přes python3 -c.

  • Protokol. Skript se spustí jako python3 -u script.py <socket>: poslední argument je cesta k Unix socketu, na kterém aplikace naslouchá, a skript se k němu připojí. Každá zpráva v obou směrech je rámec: 4 bajty s délkou (big endian) následované tělem, které je ve výchozím stavu JSON.
  • Volby: type ('json' výchozí, 'text' nebo cokoli jiného pro surové Buffery), autorestart (false, pokud se skript po skončení nemá restartovat; výchozí je spustit jej znovu), threads a socket: false (bez socketu, jen spustit program).
  • Zprávy odeslané dříve, než se skript připojil, čekají a doručí se, když se připojí.
  • Události: open (skript se připojil), data (zpráva), restart a close.
  • Cesta k socketu má limit. Socket je soubor ve složce tmp aplikace a systém omezuje celou cestu (asi 104 znaků na macOS a 108 na Linuxu). V projektu, který je hluboko na disku, server selže s listen EINVAL, skript se nikdy nepřipojí a zprávy čekají navždy. Cesta je vlastnost pypeline.socket: nastavte ji hned po PYPELINE() (soubor se kontroluje a server spouští až poté) na krátkou, jak to dělá příklad.
  • Program v Pythonu je běžný program, který běží s právy aplikace: je tak bezpečný jako kód souboru a musí být nainstalován Python 3 (python3 v PATH).
pypelines/shout.py
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)
controllers/py.js
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 });
}