пятница, 8 сентября 2017 г.

Использование child_process exec(), execFile(), spawn(), fork() в Node.js.

В Node.js модуль child_process предлагает 4 метода для выполнения внешних программ:
- exec()
- execFile()
- spawn()
- fork()

Все 4 метода выполняются асинхронно.

Каждый метод возвращает объект, которые является экземпляром класса ChildProcess.

--------------------------
child_process.exec()
child_process.execFile()
child_process.spawn()
child_process.fork()
--------------------------
              |
 ----------------------
  ChildProcess object
 ----------------------
              |
    -----------------
       ChildProcess
    -----------------
    - close <Event>
    - exit <Event>
    - error <Event>
    -----------------
    - stdin <Stream | String>
    - stdout <Stream | String>
    - stderr <Stream | String>
    - stdio <Array>
    -----------------
              |
 ----------------------
  extends EventEmitter
 ----------------------

Описание методов.

1. child_process.exec()

Что делает?

Создает отдельную оболочку терминала, внутри которой выполняет внешнюю программу и буферизирует возвращаемые в результате её выполнения данные.
Принимает путь до файла, набор необязательных опций, описывающих окружение, в котором будет выполняться внешняя программа и функцию callback, в которую передаётся набор значений о результате выполнения внешней программы.
Возвращает объект класса <ChildProcess>.

По сравнению с execFile() и spawn(), метод exec() не имеет массив аргументов argsArray, в который передаётся в исполняемый файл внешней программы, поскольку метод exec() позволяет выполняться несколько команд подряд внутри оболочки терминала.
Если необходимо в метод exec() передать аргументы, то их просто записывают, как часть команды или пути до исполняемого файла внешней программы.

В каких случаях необходимо использовать метод exec?

Метод exec() используется, когда необходимо выполнить внешнюю команду у получить доступ ко всей функциональности оболочки терминала, такой как pipe, redirect, backgrounding и тому подобное.
Метод exec() выполняет команду в оболочке терминала "/bin/sh" в Linux и "cmd.exe" в Windows.
Использование метода exec() в командной оболочке может нести в себе опасность  shell injection exploit.
Поэтому везде, где возможно, стоит взамен использовать метод execFile(), для которого неправильная передача аргументов приведёт к возникновению ошибке, а не взлому.

Как пользоваться?

const child_process = require('child_process');
child_process.exec('dir /D', {shell: true, encoding: 'utf8'}, function (error, stdout, stderr) {
    if (error) {throw error;}
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
});

Когда внешняя команда запускается в окне оболочке терминала, мы получаем доступ ко всей функциональности оболочки терминала.
В данном примере мы запускаем отдельную подоболочку терминала и выполняем в ней команду 'dir /D'.

Какие передаются аргументы?

child_process.exec(filePathOrCommandsWithArguments, optionsObject, callbackFunction);

- filePathOrCommandsWithArguments <String> - путь до исполняемого файла внешней программы, просто её имя или команда для консоли терминала вместе с набором аргументов для этой программы, отделенных друг от друга пробелами.
Откуда Node.js узнаёт, где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- encoding <String> - кодировка (по умолчанию: 'utf8').
--- shell <String> - внешняя программа будет запущена внутри оболочки терминала "cmd.exe". По умолчанию в качестве терминала используется программа "/bin/sh" на UNIX и "cmd.exe" на Windows. На Windows оболочка терминала должна быть совместима с "cmd.exe".
--- timeout <Number> - время ожидания до начала выполнения внешней программы (по умолчанию: 0).
--- maxBuffer <Number> - максимальный объём данных в байтах, поступающих из внешней программы, допустимый для передачи в stdout или stderr. Если допустимый объём переданных данных будет превышен, то процесс выполнения внешней программы будет убит (по умолчанию: 200 *1024).
--- killSignal <String> - сигнал для убийства процесса выполнения внешней программы (по умолчанию: 'SIGTERM').
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы(смотри setgid(2)).

- callbackFunction <Function> - функция обратного вызова, которая вызывается после завершения выполнения внешней программы, в неё поступаютс следующие значения:
--- error <Error> - аргумент, хранящий в себе сообщение об ошибке в случае её возникновения в процессе запуска внешней программы.
--- stdout <String> | <Buffer> - результат выполнения внешней программы в виде поступивших от неё данных.
--- stderr <String> | <Buffer> - результат ошибки во время выполнения внешней программы в виде поступивших от неё данных об ошибке.

2. child_process.execFile()

Что делает?

Выполняет любой исполняемый файл внешней программы.
Принимает путь до файла, набор необязательных аргументов, набор опций, описывающих окружение, в котором будет выполняться внешняя программа и функцию callback, в которую передаётся набор значений о результате выполнения внешней программы.
Возвращает объект класса <ChildProcess>.

В каких случаях необходимо использовать метод execFile?

Метод execFile() используется, когда необходимо выполнить внешнюю программу и получить только вывод результата её выполнения.
Например, мы можем использовать метод execFile() для запуска программы "ImageMagick", которая конвертирует картинку из формата PNG в формат JPG и нам необходимо только знать завершился ли процесс конвертации успешно или нет.
Не следует использовать метода execFile(), когда внешняя выполняемая программа возвращает огромный объём данных, которые надо обработать в режиме реального времени. Для этого случая используйте метод spawn().

Как пользоваться?

const child_process = require('child_process');
const child = child_process.execFile('node.exe', ['--version'], {cwd: process.cwd(), encoding: 'utf8'}, function (error, stdout, stderr) {
    if (error) {
        console.error('stderr: ' + Buffer.isBuffer(stderr) ? stderr.toString() : stderr);
        throw error;
    }
    console.log('stdout: ' + Buffer.isBuffer(stdout) ? stdout.toString() : stdout);
});

Внешняя программа "node.exe" вызывается с аргументом "--version".
После того, как она завершит своё выполнение, будет вызвана callback функция.
В callback функцию передаются значения "stdout" и "stderr" с результатами выполнения программы "node.exe".
Значения "stdout" и "stderr" могут передаваться в виде буфер или строки.

Какие передаются аргументы?

child_process.execFile(filePath, argsArray, optionsObject, callbackFunction);

- filePath <String> - путь до исполняемого файла внешней программы или просто её имя.
Откуда Node.js узнаёт, где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- encoding <String> - кодировка (по умолчанию: 'utf8').
--- timeout <Number> - время ожидания до начала выполнения внешней программы (по умолчанию: 0).
--- maxBuffer <Number> - максимальный объём данных в байтах, поступающих из внешней программы, допустимый для передачи в stdout или stderr. Если допустимый объём переданных данных будет превышен, то процесс выполнения внешней программы будет убит (по умолчанию: 200 *1024).
--- killSignal <String> - сигнал для убийства процесса выполнения внешней программы (по умолчанию: 'SIGTERM').
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы(смотри setgid(2)).

- callbackFunction <Function> - функция обратного вызова, которая вызывается после завершения выполнения внешней программы, в неё поступаютс следующие значения:
--- error <Error> - аргумент, хранящий в себе сообщение об ошибке в случае её возникновения в процессе запуска внешней программы.
--- stdout <String> | <Buffer> - результат выполнения внешней программы в виде поступивших от неё данных.
--- stderr <String> | <Buffer> - результат ошибки во время выполнения внешней программы в виде поступивших от неё данных об ошибке.

3. child_process.spawn()

Что делает?

Выполняет внешнюю программу в отдельном процессе и возвращает интерфейс ввода-вывода для передачи данных от внешней программы в потоке stream.
Принимает путь до файла, набор необязательных аргументов, набор опций, описывающих окружение, в котором будет выполняться внешняя программа и возвращает объект типа stream, для управления потоком данных, поступающих из внешней программы.
Возвращает объект класса <ChildProcess>.

В каких случаях необходимо использовать метод spawn?

Метод spawn() возвращает объект stream для управления потоком чтения и записи внешней выполняемой программы, поэтому он хорош для выполнения внешней программы, которая возвращает большие объёмы данных.

Как пользоваться?

Пример 1.

const child_process = require('child_process');
const spawnProcess = child_process.spawn('dir', ['/D'], {shell: true});

spawnProcess.on('error', function (error) {throw error;});
spawnProcess.stdout.on('data', function (data) {console.log('stdout: ' + data);});
spawnProcess.stderr.on('data', function (data) {console.log('stderr: ' + data);});
spawnProcess.on('close', function (code, signal) {console.log('Child process exited with code ' + code);});
spawnProcess.on('exit', function (code, signal) {console.log('Child exited with code ' + code);});

spawnProcess.stdin.write('data');
spawnProcess.stdin.end();

console.log(spawnProcess.pid);

Пример 2.

const fs = require('fs');
const child_process = require('child_process');

const spawnImageProcess = child_process.spawn('convert.exe', [
    "-", // use stdin
    "-resize", "640x", // resize width to 640
    "-resize", "x360<", // resize height if it's smaller than 360
    "-gravity", "center", // sets the offset to the center
    "-crop", "640x360+0+0", // crop
    "-" // output to stdout
], {shell: false});

const readImageStream = fs.createReadStream('./path/to/image.jpg');

readImageStream.pipe(spawnImageProcess.stdin);
spawnImageProcess.stdout.pipe(serverResponseStream);

Представим, что у нас есть сервис для преобразования картинок (imagemagick.org).
В данном примере создаётся поток чтения кода картинки из файла, который передается во входной поток внешней программы "convert.exe", которая преобразует картинку.
Затем преобразованные данные картинки передаются в поток ответа от сервера к браузеру.
До тех пор пока процесс spawnImageProcess производит преобразования данных картинки, они передаются в поток ответа от сервера к браузеру и пользователь сайта немедленно начинает получать результат обработки картинки без необходимости ожидать завершения всего процесса сразу.

Какие передаются аргументы?

const spawnProcess = child_process.spawn(filePathOrTerminalCommand, argsArray, optionsObject);

- filePathOrTerminalCommand <String> - путь до исполняемого файла внешней программы, просто её имя или команда для консоли терминала.
Откуда Node.js узнает где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- stdio <Array> | <String> конфигурация stdio для дочернего процесса (смотри options.stdio).
--- detached <Boolean> - заупскать и выполнять ли внешнюю программу независимо от выполнения вызвавшего его родительского процесса Node.js. Особенности выполнения зависят от платформы (смотри options.detached).
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы (смотри setgid(2)).
--- shell <Boolean> | <String> - если равно true, то внешняя программа будет запущена внутри оболочки терминала "cmd.exe". По умолчанию в качестве терминала используется программа "/bin/sh" на UNIX и "cmd.exe" на Windows. Другая оболочка терминала, в которой будет выполнена внешняя программа, может быть задана в виде строки. Оболочка терминала должна понимать команду -c switch на UNIX и /s /c на Windows. По умолчанию равно false, что означает не запускать внешнюю программу внутри оболочки терминала.

4. child_process.fork()

Что делает?

Метод fork() является частным случаем метода spawn().
Он используется для создания отдельно Node.js процесса, в котором выполняется файл внешней JavaScript-программы.
Как и метод spawn(), метод fork() возвращает объект класса <ChildProcess>.
Возвращенный объект класса <ChildProcess> из метода fork() имеет дополнительный канал коммуникации для передачи сообщений между родительской программой и дочерним процессом внешней программы.
Таким образом, метод fork() открывате IPC канал, которые позволяет передавать сообщения между двумя программами Node.js:
- со стороны дочернего процесса внешней программы для получения и посыла сообщений могут быть использованы методы process.on('message', function (dataFromParent) {}) и process.send('message to parent').
- со стороны родительского процесса, который запустил внешнюю программу, для получения и посыла сообщений могут быть использованы методы forkedChild.on('message', function (dataFromChild) {}) и forkedChild.send('message to child').
Канал обмена сообщений открыт до тех пор, пока внешняя исполняемая JavaScript-программа не завершит своего выполнения.
Процесс каждой программы имеет свою собственную память и запущенный экземпляр движка V8.

В каких случаях необходимо использовать метод fork?

Поскольку Node.js выполняет все операции в одном потоке, то долго выполняемые операции могут тормозить основной процесс.
Поэтому метод fork() используется, когда необходимо выполнить длительные JavaScript-операции. В этом случае запускается отдельная Node.js программа, которая общается с родительской программой с помощью передачи друг другу сообщений, что позволяет родительской программе быть доступной для выполнения других операций.

Как использовать?

Файл родительской программы parent.js:

const child_process = require('child_process');

const forkedChild = child_process.fork(__dirname + '/child.js', ['build']);

forkedChild.on('message', function (message) {
    console.log('PARENT got message from child: ' + message.foo + ' | '+ message.arg);
});
forkedChild.send({hello: 'world'});

Файл дочерней внешней программы child.js:

process.on('message', function (message) {
    console.log('CHILD got message: ' + message.hello);
    process.exit();
});
process.send({foo: 'bar', arg: process.argv[2]});

Какие передаются аргументы?

child_process.fork(jsFilePath, argsArray, optionsObject);

- jsFilePath <String> - путь до исполняемого файла внешней JavaScript-программы.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней JavaScript-программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя JavaScript-программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней JavaScript-программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- execPath <String> - путь до исполняемого файла, используемого для создания дочернего процесса внешней JavaScript-программы.
--- execArgv <Array> - массив строк аргументов, которые передаются в исполняемый файл, используемый для создания дочернего процесса внешней JavaScript-программы (по умолчанию: process.execArgv).
--- silent <Boolean> - если равно true, то stdin, stdout, stderr дочерней внешней JavaScript-программы будут связаны (pipe) с родительским процессом, в противном случае они будут наследованы от родительского процесса (смотри опции "pipe" и "inherit" метода child_process.spawn()) (по умолчанию: false).
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней JavaScript-программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней JavaScript-программы(смотри setgid(2)).

Комментариев нет:

Отправить комментарий