# Folyamatosan futó Python-program hívása a PYPELINE()-nal

> Indítson Python-szkriptet az alkalmazásból, küldjön neki JSON-üzeneteket socketen át, és fogadja a válaszait; a szkript újraindul, ha leáll.

Framework: total5
Product: Total.js 5 (total5@0.0.19)
Language: hu
Content-SHA256: 4d9044d8375500aa7d0fac3e6b010f6f2594ee3f525217f7fa31cbb488e7107e
Snapshot: c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c
Sources: total5-installed@total5@0.0.19
Review: not-recorded

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 nyers `Buffer`ekhez), `autorestart` (`false`, hogy a szkript ne induljon újra, amikor véget ér; alapértelmezés szerint újraindul), `threads` és `socket: 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` és `close`.
- **A socket útvonalának van korlátja.** A socket az alkalmazás `tmp` mappá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 szerver `listen EINVAL` hibával elbukik, a szkript sosem csatlakozik, az üzenetek pedig örökké várnak. Az útvonal a `pypeline.socket` tulajdonság: állítsa be közvetlenül a `PYPELINE()` 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 (`python3` a `PATH`-ban).

```python 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)
```

```js 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 });
}
```
