# Volajte program v Pythone, ktorý stále beží, cez PYPELINE()

> Spustite skript Pythonu z aplikácie, posielajte mu správy JSON cez socket a prijímajte jeho odpovede; ak skript zanikne, reštartuje sa.

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

`PYPELINE(name, [options])` spustí `pypelines/<name>.py` pomocou `python3` a udržiava s ním spojenie, takže aplikácia môže používať knižnice Pythonu (dáta, modely, spracovanie obrázkov) bez spustenia nového procesu pre každú požiadavku. Vráti objekt s `send(data)` na odoslanie správy, `on('data', fn)` na prijímanie odpovedí a `close()` na zastavenie skriptu. Názov, ktorý začína `~`, je namiesto toho cesta k súboru (`~/opt/job.py`) a text, ktorý je kódom (má zalomenia riadkov alebo vyzerá ako Python), sa spustí ako inline program cez `python3 -c`.

- **Protokol.** Skript sa spustí ako `python3 -u script.py <socket>`: **posledný argument je cesta k Unix socketu**, na ktorom aplikácia počúva, a skript sa k nemu pripojí. Každá správa v oboch smeroch je rámec: 4 bajty s dĺžkou (big endian) nasledované telom, ktoré je predvolene JSON.
- **Voľby:** `type` (`'json'` predvolene, `'text'` alebo čokoľvek iné pre surové `Buffer`y), `autorestart` (`false`, ak sa skript po skončení nemá reštartovať; predvolene sa spustí znova), `threads` a `socket: false` (bez socketu, iba spustiť program).
- Správy odoslané predtým, než sa skript pripojil, čakajú a doručia sa, keď sa pripojí.
- Udalosti: `open` (skript sa pripojil), `data` (správa), `restart` a `close`.
- **Cesta k socketu má limit.** Socket je súbor v priečinku `tmp` aplikácie a systém obmedzuje celú cestu (asi 104 znakov na macOS a 108 na Linuxe). V projekte, ktorý je hlboko na disku, server zlyhá s `listen EINVAL`, skript sa nikdy nepripojí a správy čakajú navždy. Cesta je vlastnosť `pypeline.socket`: nastavte ju hneď po `PYPELINE()` (súbor sa kontroluje a server sa spúšťa až potom) na krátku, ako to robí príklad.
- Program v Pythone je **bežný program, ktorý beží s právami aplikácie**: je len tak bezpečný ako kód súboru a musí byť nainštalovaný Python 3 (`python3` v `PATH`).

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