понедельник, 18 сентября 2017 г.

Node.js Cluster - особенности работы

const cluster = require('cluster')
        , http = require('http')
        , os = require('os');

const cpus = os.cpus().length;

// 3 Уровня создание кластера воркеров

// 1 уровень - уровень прослушивания всех событий для всех воркеров

cluster.on('message', function (worker, message, handle) {
    console.log('All messages: ' + worker.id + ' | ' + message);
});

cluster.on('fork', function (worker) {
    console.log('Worker ' + worker.id + ' forked.');
});

if (cluster.isMaster) {
    // 2 уровень - уровень создания воркеров и уровень прослушивания событий для каждого конкретного воркера
    for (let i = 0; i < cpus; i++) {
        let worker = cluster.fork(); // Создание воркера
        // Навешивание событий на конкретный созданный воркер
        worker.on('listening', (address) => {
            console.log(JSON.stringify(address));
        });
        worker.on('message', function (message) { // Получение сообщения из мастер-процесса кластера
            console.log(message + ' master ' + process.pid);
            worker.send('shutdown'); // Посылка сообщения из воркера в мастер-процесс кластера
            worker.kill(); // Уничтожение текущего активного воркера
        });
    }
    cluster.on('exit', function (worker, code, signal) {
        console.log('Worker ' + worker.id + ' killed.');
    });
} else if (cluster.isWorker) {
    // 3 уровень - уровень кода воркера и посылки сообщений из мастер-процесса кластера текущему активному воркеру
    // Код воркера
    http.createServer(function (request, response) {
        response.writeHead(200, 'OK');
        response.end('HI ' + process.pid);
        process.send('Done: ' + process.pid); // Посылка сообщения из мастер-процесса кластера текущему активному воркеру
    }).listen(8000, '127.0.0.1', function () {
        console.log('Server started ' + process.pid);
    });
    // Получение сообщения из текущего активного воркера мастер-процессом кластера
    process.on('message', function (message) {
        console.log('Message from worker: ' + message);
    });
}

среда, 13 сентября 2017 г.

Node.js UDP / Dgram Server and Client

const dgram  = require('dgram');

// Server

const server = dgram.createSocket('udp4');

server.on('listening', function () {
    console.log('Server listening: ' + JSON.stringify(server.address()));
});

server.on('message', function (message, rinfo) {
    console.log('Server got message: "' + message + '" from: ' + rinfo.address + ':' + rinfo.port);
    server.send(message, 0, rinfo.size, rinfo.port, rinfo.address, function (error) {
        if (error) {throw error;}
        server.close();
    });
});

server.on('close', function () {
    console.log('Server closed');
});

server.on('error', function (error) {
    console.log('Server error: ' + error.stack);
    server.close();
});

server.bind(8080, '127.0.0.1', function () {
    console.log('Datagram server started at 127.0.0.1:8080');
});

// Client

const client = dgram.createSocket('udp4');

client.on('message', function (message, rinfo) {
    console.log('Client got message: "' + message + '" from: ' + rinfo.address + ':' + rinfo.port);
    client.close();
});

client.on('close', function () {
    console.log('Client closed');
});

client.on('error', function (error) {
    console.log('Client error: ' + error.stack);
    client.close();
});

client.send([Buffer.from('Some bytes.'), Buffer.from(' And another bytes.')], 8080, '127.0.0.1', function (error) {
    if (error) {throw error;}
});

вторник, 12 сентября 2017 г.

Node.js TLS Socket connection full example

// https://github.com/digitalbazaar/forge#x509
// npm install node-forge

const tls = require('tls')
        , forge = require('node-forge');

// Generate certificate

const keyPair = forge.pki.rsa.generateKeyPair(2048)
        , cert = forge.pki.createCertificate()
        , now = new Date()
        , oneYear = new Date(new Date(now).setFullYear(now.getFullYear() + 1));

Object.assign(cert, {
      publicKey: keyPair.publicKey
    , serialNumber: '01'
    , validity: {
          notBefore: now
        , notAfter: oneYear
    }
});

cert.sign(keyPair.privateKey, forge.md.sha256.create()); // self signed

const serverPrivateKeyPem = forge.pki.privateKeyToPem(keyPair.privateKey)
        , serverPublicKeyPem = forge.pki.publicKeyToPem(keyPair.publicKey)
        , serverCertificatePem = forge.pki.certificateToPem(cert);

// Server

const serverOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
};

const server = tls.createServer(serverOptions, function (socket) {
    console.log('Insecure connection 2');
    // socket.authorized will be true if the client cert presented validates with our CA
    console.log('Client connected to Server. Client ' + (socket.authorized ? 'authorized' : 'unauthorized'));
    socket.setEncoding('utf8');
    socket.on('data', function (data) {console.log(data.toString());});
    socket.on('end', function () {console.log('END');});
    socket.write('Hello');
    socket.end('World');
});

server.on('connection', function (socket) {
    console.log('Insecure connection 1');
})

server.on('secureConnection', function (socket) {
    // socket.authorized will be true if the client cert presented validates with our CA
    console.log('Secure connection. Client authorized: ' + socket.authorized);
});

server.listen(8000, '127.0.0.1', function () {
    console.log('Server started at https://127.0.0.1:443');
});

// Client

const clientOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
    , rejectUnauthorized: false
};

const client = tls.connect(8000, '127.0.0.1', clientOptions, function () {
    if (client.authorized) {
        console.log('Connection authorized by a Certificate Authority.');

        client.write('GET /hey HTTP/1.1\r\n');
        client.write('\r\n');

        client.write('POST /ho HTTP/1.1\r\n');
        client.write('\r\n');

    } else {
        console.log('Connection not authorized: ' + client.authorizationError);

        client.write('We are not authorized');

    }
});

client.setEncoding('utf8');

client.on('data', function (data) {
    console.log(data.toString());
});

client.on('end', function (data) {
    server.close();
});

client.on('close', function() {
    console.log('SOCKET CLOSED');
});

Node.js Net Socket connection full example

// --------------------------------------------------------------------------
// | Соединение между сокетами net похоже на алгоритм соединения с помощью модуля http.
// --------------------------------------------------------------------------
// | HTTP Client
// --------------------------------------------------------------------------
// | const http = require('http');
// | const server = http.createServer();
// | server.on('request', function (request, response) {
// |     request.on('data', function (data) {
// |         console.log('Request BODY from client: ' + data.toString()); // Вызывается, только, если было послано BODY
// |     });
// |     request.on('end', function () {
// |         console.log('2) Data from client ended');
// |         response.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
// |         response.write('\n\r\ a. Data to client');
// |         response.end('\n\r\ b. Data to client ended');
// |         server.close();
// |     });
// | });
// | server.listen(8080, '127.0.0.1', function () {console.log('1) HTTP server started at 127.0.0.1:8080');});
// --------------------------------------------------------------------------
// | Net Server
// --------------------------------------------------------------------------
// | const net = require('net');
// | const server = net.createServer();
// | server.on('connection', function (socket) {
// |     socket.on('data', function (data) {
// |         console.log('3) Request from client: ' + data.toString());
// |     });
// |     socket.on('end', function () {
// |         console.log('5) Data from client ended');
// |     });
// |     socket.write('\n\r\ a. Data to client');
// |     socket.end('\n\r\ b. Data to client ended');
// |     server.close();
// | });
// | server.listen(8080, '127.0.0.1', function () {console.log('1) Socket server started at 127.0.0.1:8080');});
// --------------------------------------------------------------------------
// | HTTP Client
// --------------------------------------------------------------------------
// | const client = http.request({hostname: '127.0.0.1', port: 8080, path: '/', method: 'GET'});
// | client.on('response', function (response) {
// |     response.on('data', function (data) {console.log('3) Answer from server: ' + data.toString());});
// |     response.on('end', function () {console.log('4) Data from server ended');});
// | });
// | client.write('\n\r\ a. Data to server');
// | client.end('\n\r\ a. Data to server ended');
// --------------------------------------------------------------------------
// | Net Client
// --------------------------------------------------------------------------
// | const client = new net.Socket();
// | client.on('data', function (data) {console.log('2) Answer from server: ' + data.toString());});
// | client.on('end', function () {console.log('4) Data from server ended');});
// | client.on('connect', function () {
// |     client.write('\n\r\ a. Data to server');
// |     client.end('\n\r\ b. Data to server ended');
// | });
// | client.connect(8080, '127.0.0.1');
// --------------------------------------------------------------------------

const net = require('net');

// Server

const server = net.createServer(); // или тоже самое: new net.Server();

server.on('connection', function (socket) {
    socket.setEncoding('utf8');
    console.log('4) Server received connection from client: ' + socket.remoteAddress + ':' + socket.remotePort);
    socket.on('data', function (data) {
        console.log('10) Data from client: ' + data);
    });
    socket.on('end', function () {
        console.log('14) Server disconnected from client: ' + socket.remoteAddress + ':' + socket.remotePort);
        server.close();
    });
    socket.on('close', function () {
        console.log('16) Client closed connection to server.');
    });
    socket.on('error', function (error) {
        console.log('Connection client to server error: ' + error.message);
    });
    socket.write('\n\r a. Hello');
    server.getConnections(function (error, count) {
        if (error) {throw error;}
        socket.write('\n\r b. Total connections: ' + count);
        socket.end('\n\r c. Goodbye');
    });
});

server.on('close', function () {
    console.log('15) All connections to server closed.');
});

server.on('error', function (error) {
    if (error.code === 'EADDRINUSE') {
        console.log('Address in use, retrying...');
        setTimeout(function () {
            server.close();
            server.listen(9000, '127.0.0.1', function () {
                console.log('Socket server restarted at ' + JSON.stringify(server.address()));
            });
        }, 1000);
    }
});

server.on('listening', function () {
    console.log('1) Method server.listen() called.');
    if (server.listening) {
        console.log('2) Socket server is listening now.');
    }
});

server.listen(9000, '127.0.0.1', function () {
    console.log('3) Socket server started at ' + JSON.stringify(server.address()));
});

// Client

const client = new net.Socket(); // net.createConnection(); и net.connect(); вызывают "transmission error"

client.on('connect', function () {
    console.log('5) Client established connection to server.');
    console.log('6) Client socket connected at ' + JSON.stringify(client.address()));
    client.write('\n\r a. First data from client to server', 'utf8', function () {
        console.log('8) Data from client to server sent.');
    });
});

client.on('data', function (data) {
    console.log('9) Answer from server to client: ' + data);
    client.end('\n\r b. Last data from client to server.', 'utf8');
});

client.on('end', function () {
    console.log('11) Client recieved all data from server socket.');
    console.log('12) Client disconnected from server.');
});

client.on('close', function (has_error) {
    if (has_error) {
        console.log('4*) The socket was closed due to a transmission error.');
    } else {
        console.log('13) Client connection to server closed.');
    }
});

client.setTimeout(3000);
client.on('timeout', function () {
    console.log('Client connection timeout');
    client.close(); // или можно: client.destroy();
});

client.on('error', function (error) {
    console.log('3*) Client error: ' + error.message);
});

client.connect(9000, '127.0.0.1', function () {
    if (client.connecting) {
        console.log('Client socket is connecting to server socket');
    }
    console.log('7) Client connected to server.');
});

/*

// Server
1) Method server.listen() called.
2) Socket server is listening now.
3) Socket server started at {"address":"127.0.0.1","family":"IPv4", "port":9000}
4) Server received connection from client.

// Client
5) Client established connection to server.
6) Client socket connected at {"address":"127.0.0.1","family":"IPv4", "port":53977}
7) Client connected to server.
8) Data from client to server sent.
9) Answer from server to client:
 a. Hello
 b. Total connections: 1
 c. Goodbye

 // Server
10) Data from client:
 a. First data from client to server
 b. Last data from client to server.

// Client
11) Client recieved all data from server
12) Client disconnected from server.
13) Client connection to server closed.

 // Server
14) Server disconnected from client.
15) All connections to server closed.

*/

Node.js HTTPS Server with certificate generator

// https://github.com/digitalbazaar/forge#x509
// npm install node-forge
// https://127.0.0.1:443

const https = require('https')
        , forge = require('node-forge');

// Generate certificate

const keyPair = forge.pki.rsa.generateKeyPair(2048)
        , cert = forge.pki.createCertificate()
        , now = new Date()
        , oneYear = new Date(new Date(now).setFullYear(now.getFullYear() + 1));

Object.assign(cert, {
      publicKey: keyPair.publicKey
    , serialNumber: '01'
    , validity: {
          notBefore: now
        , notAfter: oneYear
    }
});

cert.sign(keyPair.privateKey, forge.md.sha256.create()); // self signed

const serverPrivateKeyPem = forge.pki.privateKeyToPem(keyPair.privateKey)
        , serverPublicKeyPem = forge.pki.publicKeyToPem(keyPair.publicKey)
        , serverCertificatePem = forge.pki.certificateToPem(cert);

// Server

const serverOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
};

const server = https.createServer(serverOptions, function (request, response) {
    response.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
    response.end('Hello World!');
})

server.listen(443, '127.0.0.1', function () {
    console.log('Server started at https://127.0.0.1:443');
});

// Client

const clientOptions = {
      hostname: '127.0.0.1'
    , port: 443
    , path: '/'
    , method: 'GET'
    , rejectUnauthorized: false // don't check certificate
    , requestCert: true
    , agent: false
};

const client = https.request(clientOptions, function (response) {
    response.on('data', function (data) {
        console.log(data.toString());
    });
    response.on('end', function () {
        server.close();
    });
});

client.on('error', function (error) {
    throw error;
});

client.end();

понедельник, 11 сентября 2017 г.

Node.js Net Socket Server and Client

const net = require('net');

const HOST = '127.0.0.1'
        , PORT = 8080;

// Server

const server = net.createServer();

server.on('connection', function (serverSocket) {
    console.log('Client connected to server at ' + serverSocket.remoteAddress + ':' + serverSocket.remotePort);
    serverSocket.on('data', function (data) {
        console.log('Data from client ' + serverSocket.remoteAddress + ': ' + data);
        serverSocket.write('Client said: ' + data);
    });
    serverSocket.on('close', function () {
        console.log('Client closed connection to server: ' + serverSocket.remoteAddress +' '+ serverSocket.remotePort);
    });
    serverSocket.on('error', function (error) {
        throw error;
    })
});

server.listen(PORT, HOST, function () {
    console.log('Server started at ' + server.address().address + ':' + server.address().port);
});

// Client

const client = new net.Socket();

client.connect(PORT, HOST, function () {
    console.log('Client established connection to: ' + HOST + ':' + PORT);
    client.write('Hello');
});

client.on('data', function (data) {
    console.log('Answer from server: ' + data);
    console.log('Client ready to close connection. ');
    client.destroy(); // kill client and close connection after server's response
});

client.on('close', function () {
    console.log('Client closed his connection.');
});

пятница, 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)).

среда, 6 сентября 2017 г.

TCP proxy server

client --> proxy --> remote --> proxy --> client

Файл proxy.js

var net = require('net');

var LOCAL_PORT = 8080
    , LOCAL_ADDR = '127.0.0.1'

    , REMOTE_PORT = 80
    , REMOTE_ADDR = 'google.com';

var server = net.createServer(function (socket) {
    socket.on('data', function (data) {
        console.log('------- START -------');
        console.log('<< From client to proxy ', data.toString());
        var serviceSocket = new net.Socket();
        serviceSocket.connect(REMOTE_PORT, REMOTE_ADDR, function () {
            console.log('>> From proxy to remote', data.toString());
            serviceSocket.write(data);
        });
        serviceSocket.on('data', function (data) {
            console.log('<< From remote to proxy', data.toString());
            socket.write(data);
            console.log('>> From proxy to client', data.toString());
        });
        serviceSocket.on('error', function () {throw error;});
    });
});

server.listen(LOCAL_PORT, LOCAL_ADDR, function () {
    console.log('TCP server accepting connection on port: ' + LOCAL_ADDR + ':' + LOCAL_PORT);
});

Node.js Spawn NPM install dependecies

Файл install.js

var child_process = require('child_process')
    , package = require('./package');

var dependencies = []
    , preinstall;

if (package.hasOwnProperty('dependencies')) {
    for (var key in package.dependencies) {
        if (package.dependencies.hasOwnProperty(key)) {
            dependencies.push(key + '@' + package.dependencies[key]);
        }
    }
}

if (package.hasOwnProperty('devDependencies')) {
    for (var key in package.devDependencies) {
        if (package.devDependencies.hasOwnProperty(key)) {
            dependencies.push(key + '@' + package.devDependencies[key]);
        }
    }
}

if (package.hasOwnProperty('scripts') && package.scripts.hasOwnProperty('preinstall')) {
    preinstall = package.scripts.preinstall;
    if (preinstall.indexOf('npm i ') === 0) {
        preinstall = preinstall.slice(6);
    }
}

dependencies.sort();

installNextDependency(dependencies);

function installNextDependency (dependencies) {
    var dependency = dependencies.pop()
        , parameters;
    if (typeof dependency === 'string') {
        parameters = ['install', dependency];
    } else if (typeof dependency === 'undefined' && typeof preinstall === 'string') {
        parameters = ['install', preinstall];
        preinstall = undefined;
    } else {
        console.log('The end. Dependencies array and scripts preinstall are empty.');
        return;
    }
    var spawnProcess = child_process.spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', parameters, {stdio: 'inherit', shell: true});
    spawnProcess.on('exit', function (code) {
        if (code === 0) {
            console.log('Dependency ' + parameters[1] + ' installed.');
            installNextDependency(dependencies);
        } else {
            console.log('Error in dependency: ' + parameters[1] + '.');
            console.log('Error. Exited with code: ' + code + '.');
        }
    });
    spawnProcess.on('error', function (error) {
        console.log('Error in dependency: ' + parameters[1] + '.');
        throw error;
    });
}

/*
// Test NPM
var child_process = require('child_process');
var spawnProcess = child_process.spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['--version'], {stdio: 'inherit', shell: true});
spawnProcess.on('exit', function (code) {
    if (code === 0) {
        console.log('Done');
    } else {
        console.log('Error. Exited with code: ' + code);
    }
});
spawnProcess.on('error', function (error) {
    throw error;
});
*/