Wywołaj działający program Pythona przez PYPELINE()
PYPELINE(name, [options]) uruchamia pypelines/name.py przez python3 i utrzymuje z nim połączenie, dzięki czemu aplikacja może używać bibliotek Pythona (dane, modele, przetwarzanie obrazów) bez uruchamiania nowego procesu dla każdego żądania. Zwraca obiekt z send(data) do wysyłania wiadomości, on('data', fn) do odbierania odpowiedzi i close() do zatrzymania skryptu. Nazwa zaczynająca się od ~ jest zamiast tego ścieżką do pliku (~/opt/job.py), a tekst będący kodem (ma podziały wierszy lub wygląda jak Python) jest uruchamiany jako program wbudowany przez python3 -c.
- Protokół. Skrypt jest uruchamiany jako
python3 -u script.py <socket>: ostatni argument to ścieżka gniazda Unix, na którym nasłuchuje aplikacja, a skrypt się z nim łączy. Każda wiadomość, w obu kierunkach, to ramka: 4 bajty z długością (big endian), a po nich ciało, które domyślnie jest JSON-em. - Opcje:
type('json'domyślnie,'text'lub cokolwiek innego dla surowychBufferów),autorestart(false, aby nie restartować skryptu po zakończeniu; domyślnie jest uruchamiany ponownie),threadsisocket: false(bez gniazda, tylko uruchom program). - Wiadomości wysłane, zanim skrypt się połączył, czekają i są dostarczane, gdy się połączy.
- Zdarzenia:
open(skrypt się połączył),data(wiadomość),restarticlose. - Ścieżka gniazda ma limit. Gniazdo to plik w folderze
tmpaplikacji, a system ogranicza całą ścieżkę (około 104 znaków w macOS i 108 w Linuksie). W projekcie głęboko na dysku serwer kończy się błędemlisten EINVAL, skrypt nigdy się nie łączy, a wiadomości czekają w nieskończoność. Ścieżką jest właściwośćpypeline.socket: ustaw ją zaraz poPYPELINE()(plik jest sprawdzany, a serwer uruchamiany później) na krótką, jak robi to przykład. - Program w Pythonie to zwykły program działający z uprawnieniami aplikacji: jest tak bezpieczny jak kod pliku i trzeba mieć zainstalowanego Pythona 3 (
python3wPATH).
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 });
}