среда, 4 марта 2020 г.

Node.js Net TCP server and TLS (SSL) server

Node.js Net TCP server and client.


Файл tcp-socket-server.js

var net = require('net');

var server = net.createServer();

server.maxConnections = 10;

server.on('connection', function (clientSocket) {
   
    var clientRequest, serverResponse;
    clientRequest = serverResponse = clientSocket;
   
    console.log('Client connected.');

    console.log('Server local address: ' + clientSocket.localAddress + ':' + clientSocket.localPort + '. Client remote address: ' + clientSocket.remoteAddress + ':' + clientSocket.remotePort + '.');

    server.getConnections(function (error, count) {
        if (error) {throw error;}
        console.log('Number of clients connected to server: ' + count);
    });
   
    clientRequest.setEncoding('utf8');
   
    clientRequest.setTimeout(1000);
    clientRequest.on('timeout', function () {
        console.log('Client request timeout.');
        clientRequest.end(); // или также можно выполнить clientRequest.destroy(); для закрытия соединения с клиентом.
        console.log('Connection with client ' + clientSocket.remoteAddress + ':' + clientSocket.remotePort + ' closed.');
        server.close();
    });
   
    clientRequest.on('data', function (data) {
       console.log('Received data from client: "' + data + '". Data size: ' + clientRequest.bytesRead);
       serverResponse.write('Data from server.');
       console.log('Sent data to client size: ' + serverResponse.bytesWritten);
    });
   
    clientRequest.on('end', function () {
        console.log('Client ' + clientSocket.remoteAddress + ':' + clientSocket.remotePort + ' disconnected from server.');
        server.getConnections(function (error, count) {
            if (error) {throw error;}
            console.log('Number of clients connected to server: ' + count);
        });
    });
   
});

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

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

server.on('listening', function () {
    if (server.listening) {
        console.log('Server started at ' + server.address().address + ':' + server.address().port + '. Family: ' + server.address().family + '.');
    }
});

server.listen(8080, '127.0.0.1');


Файл tcp-socket-client.js

var net = require('net');

var client = net.connect(8080, '127.0.0.1', function () {
    console.log('Client local address: ' + client.localAddress + ':' + client.localPort + '. Remote server address: ' + client.remoteAddress + ':' + client.remotePort + '.');
});

console.log('Is client pending? ' + client.pending);
console.log('Is client connecting? ' + client.connecting);

client.setNoDelay(true); // Set no delay before write to server. No buffer data.

client.setEncoding('utf8');

client.setTimeout(500);
client.on('timeout', function () {
    console.log('Client connection timeout.');
    console.log('Connection with server closed.');
});

client.on('lookup', function (error, address, family, host) { // DNS lookup - работает при подключении к сайтам типа google.com:80
    if (error) {throw error;}
    console.log('Found server with IP-address: ' + address + '. Family: ' + family + '.');
});

client.on('connect', function () {
    console.log('Is client pending? ' + client.pending);
    console.log('Is client connecting? ' + client.connecting);
    console.log('Client connected to server.');
   
    client.write('Second data from client.');

    console.log('Sent second data to server - data size: ' + client.bytesWritten);
    console.log('Data in client buffer size: ' + client.bufferSize);
});

client.on('ready', function () {
    console.log('Client ready to transfer data.');
});

client.on('data', function (data) {
    console.log('Receive data from server: "' + data + '". Data size: ' + client.bytesRead);
});

client.on('end', function () {
    console.log('Client disconnected from server.');
});

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

client.write('First data from client.');

console.log('Sent first data to server - data size: ' + client.bytesWritten);
console.log('Data in client buffer size: ' + client.bufferSize);


Node.js TLS (SSL) server and client.


Выполнить установку библиотеки "selfsigned":

npm install selfsigned


Файл create-ssl-certificates.js

// npm install selfsigned

var fs = require('fs');
var selfsigned = require('selfsigned');
var attrs = [{name: 'commonName', value: 'contoso.com'}];
var pems = selfsigned.generate(attrs, {days: 365});

console.log(pems);

fs.writeFile('./private-key.pem', pems.private, function (error) {if (error) {throw error;}});
fs.writeFile('./public-key.pem', pems.public, function (error) {if (error) {throw error;}});
fs.writeFile('./cert.pem', pems.cert, function (error) {if (error) {throw error;}});


Файл private-key.pem

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCRyF/m2lVb7BVpudPydqz8WUOItxP6GI7XtHVdPJCWa2Ktjj1R
6W2HZm3eqNV7j4IgSStRfZlPDNhT9P3BC7bbyyaODxDlu6hyM24NHm22H6q5QDsC
web/p8q5GAR2978X/Uys2iJGZmOn2RCE5VJhRFcykdN2BohSPdzh7GUh+wIDAQAB
AoGAf/FYYXWqxnLq1BA3+Cq5ZPs+bwUmLi9RZfRFsJ9P0gPK5cDZBkOUUenOcUTB
n6ByNr2gm/NcEmmWjhCMh9ktEGQhnw3VX3UuQP/NXN+ZIDpySA6wQVxZS3b3eIL0
a7rb6W9QH8/Ww1ggLnGdgE6MBRAh5dc1xHoK6TtcOPkKziECQQDBfVO5rFJLwqPR
L1FRyou2SpOoZvqT3RPs+vv2VLdmDPFv56zGKG8xTZky2nOuK345P6tcr5cp5PXo
Nz37L6BLAkEAwOFsb1JZLorliE1/n7psE7iTAjc5OuT1D+Bmkuhh524PYC4jcvxg
d8aFteskUFjE9rhy9Khnt7rZ4PKtxwZXEQJBAJU0+h2SXxwBCqbDYGg8UyTNubQB
RXZE45q5qRc6GPtfO8fZ1ggxIh3ZAyyN/OrwqzOmf5TH6z/pSiA6iVdsUKUCQCiS
AfX0c0/H5XnjGzokw0DurPVlWkNaD3X0dH7oJFfCnbdUXR59mWj4N/3I5Q2FboCa
0YtGtPI/ej7HrOfVHJECQETRNoUKDf6lqsMc3pK1vTNjF80IU9wfYjTuIHBYRVWo
vBHiGtBm5yx6SqrQ/rSpfTNNmO94A1Hl4AoLSFnrk+c=
-----END RSA PRIVATE KEY-----


Файл public-key.pem

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRyF/m2lVb7BVpudPydqz8WUOI
txP6GI7XtHVdPJCWa2Ktjj1R6W2HZm3eqNV7j4IgSStRfZlPDNhT9P3BC7bbyyaO
DxDlu6hyM24NHm22H6q5QDsCweb/p8q5GAR2978X/Uys2iJGZmOn2RCE5VJhRFcy
kdN2BohSPdzh7GUh+wIDAQAB
-----END PUBLIC KEY-----


Файл cert.pem

-----BEGIN CERTIFICATE-----
MIIB7zCCAVigAwIBAgIJIqEQUie5+FU5MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMTC2NvbnRvc28uY29tMB4XDTE5MDcxMjE1MTcxMVoXDTIwMDcxMTE1MTcxMVow
FjEUMBIGA1UEAxMLY29udG9zby5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
AoGBAJHIX+baVVvsFWm50/J2rPxZQ4i3E/oYjte0dV08kJZrYq2OPVHpbYdmbd6o
1XuPgiBJK1F9mU8M2FP0/cELttvLJo4PEOW7qHIzbg0ebbYfqrlAOwLB5v+nyrkY
BHb3vxf9TKzaIkZmY6fZEITlUmFEVzKR03YGiFI93OHsZSH7AgMBAAGjRTBDMAwG
A1UdEwQFMAMBAf8wCwYDVR0PBAQDAgL0MCYGA1UdEQQfMB2GG2h0dHA6Ly9leGFt
cGxlLm9yZy93ZWJpZCNtZTANBgkqhkiG9w0BAQUFAAOBgQBMXBY8LmuZ1DCgzsg2
l4fcHJ8lDUgc/17PF7TWCZLiIuH8To7Gg1x9yli2M9w8vMXrbbhZeluokR8gzeol
pJoZTlP+JZ+lYl+5q01f/zea+7lZW3WksMfsfau6ajZMn8O9L8rv3M6sSD0Dei/1
hAZZMrupHYINWnVgjShzVGpFBA==
-----END CERTIFICATE-----


Файл tls-socket-server.js

var tls = require('tls');
var fs = require('fs');

var options = {
    key: fs.readFileSync('./private-key.pem'),
    cert: fs.readFileSync('./public-key.pem'),
    ca: [fs.readFileSync('./cert.pem')],
    requestCert: true,
    rejectUnauthorized: true
};

const server = tls.createServer(options, function (clientSocket) {
});

server.on('connection', function (clientSocket) {
    console.log('insecure connection');
    console.log('Server connected ' + (clientSocket.authorized ? 'authorized' : 'unauthorized'));
    clientSocket.setEncoding('utf8');
    clientSocket.on('data', function (data) {
        console.log(data);
        clientSocket.write('Welcome.');
    });
    clientSocket.on('end', function () {
        console.log('End');
    });
});

server.on('secureConnection', function (clientSocket) {
    // clientSocket.authorized will be true if the client cert presented validates with our CA
    console.log('secure connection; client authorized: ', connection.authorized);
    console.log('Server connected ' + (clientSocket.authorized ? 'authorized' : 'unauthorized'));
    clientSocket.setEncoding('utf8');
    clientSocket.on('data', function (data) {
        console.log(data);
        clientSocket.write('Welcome.');
    });
    clientSocket.on('end', function () {
        console.log('End');
    });
});

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

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


Файл tls-socket-client.js

var tls = require('tls');
var fs = require('fs');

var options = {
    ca: [fs.readFileSync('./server-cert.pem')]
};

var client = tls.connect(443, '127.0.0.1', options, function () {
    console.log('Client connected ' + (client.authorized ? 'authorized' : 'unauthorized'));
    if (client.authorized) {
        console.log('Connection authorized by a Certificate Authority.');
    } else {
        console.log('Connection not authorized: ' + client.authorizationError)
    }
    client.write('I am the client sending you a message.');
});

client.setEncoding('utf8');

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

client.on('end', function () {
    console.log('Ended')
});

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

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

вторник, 3 марта 2020 г.

Node.js global module - Как создать модуль запускающийся глобально отовсюду


Запускать созданные вами модули для Node.js через командную строку с указанием полного пути до модуля достаточно неудобно:

node ../path/to/mytool/script.js --arguments somevalue

Вместо этого хочется делать это также коротко, как делают это TypeScript, Webpack или Cordova:

tsc do-something
cordova do-something
nodemon do-something

Гораздо лучше было бы запускать ваш модуль также:

mytool do-something

В этой статье вы узнаете, как создать модуль, который будет доступен глобально после установки из NPM.

1. Создание обычного модуля.

Сперва создадим обычный модуль. Пусть его название будет "my-toolkit".
Для этого создадим папку с именем "my-toolkit".
Внутри папки "my-toolkit создадим папку "lib".
Внутри папки "lib" создадим файл "index.js" со следующим кодом:

// ./lib/index.js

/**
 * Функция выводит текст в консоль.
 *
 * @param {string_to_say} String строка с текстом для вывода в консоль.
 */
function say (string_to_say) {
    return console.log(string_to_say);
}

exports.say = say;

Код данного файла будет являться главным для нашего модуля. Функция say() будет выводить переданный текст в консоль.

Создадим для нашего модуля в папке "my-toolkit" файл "package.json" для того, чтобы его можно было публиковать и устанавливать через NPM со следующим содержимым:

{
    "name": "my-toolkit",
    "version": "1.0.0",
    "description": "My custom global module",
    "author": "Noname",
    "license": "MIT",
    "main": "./lib/index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    }
}

2. Создание файла для глобализации модуля.

Для того, чтобы сделать простой модуль глобальным создадим внутри папки "my-toolkit" папку "bin".
Далее в папке "bin" создадим файл "demo-global.js" со следующим содержимым:

#!/usr/bin/env node

var myLibrary = require('../lib/index.js');

myLibrary.say('Hello, World!');

Важное замечание.

Обратите внимание на первую строку (#!/usr/bin/env node), которая позволит NPM после инсталляции вашего модуля корректно создать для него файл исполнителя (имя-команды.cmd).

В случае работы на Windows при выполнении кода из файла будет проигнорирована линия #!/usr/bin/env node и код модуля будет выполнен, как обычный JavaScript-файл, с расширением .js.

3. Подготовка модуля для глобальной инсталляции.

Для того, чтобы сделать ваш модуль глобальным и доступным для выполнения отовсюду с помощью простой команды "dosomethingawesome" вы должны прописать в файле "package.json" модуля свойство "bin", в котором должны будете указать название команды и путь до исполняемого файла вашего модуля:

{
    ...
    "bin": {
      "dosomethingawesome": "./bin/demo-global.js"
    },
    ...
}

Этого будет достаточно для регистрации вашего модуля, если кто-либо проведет его инсталляцию из NPM с указанием флага -g:

npm install -g mymodule

После установки вашего модуля на Windows NPM самостоятельно создаст обертку dosomethingawesome.cmd в папке с установленным модулем для того, чтобы пользователи могли вызывать код вашего модуля по прописанной вами короткой команде dosomethingawesome.

Обратите внимание, что в свойстве "bin" вы можете прописать сколько угодно команд, если вы имеете, например, несколько исполняемых файлов, для которых хотите прописать пути в переменной PATH в процессе инсталляции вашего модуля из NPM.

Если ваш модуль предназначается для использования только глобально, тогда вам следует в файл "package.json" добавить свойство preferGlobal со значением true, которое заставит NPM выводить на экран предупреждение в случае, если ваш модуль попытаются установить локально без флага -g:

{
    ...
    "preferGlobal": true,
    ...
}

Прописывание данного свойство не помешает пользователю установить ваш модуль локально, но поможет хотя бы уведомить его о том, что он выполняет инсталляцию неправильно.

В итоге после всех изменений код файла "package.json" будет иметь следующий вид:

{
    "name": "my-toolkit",
    "version": "1.0.0",
    "description": "My custom global module",
    "author": "Noname",
    "license": "MIT",
    "main": "./lib/index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "bin": {
      "dosomethingawesome": "./bin/demo-global.js"
    },
    "preferGlobal": true
}

Теперь ваш модуль готов к глобальной инсталляции.

4. Инсталляция вашего глобального модуля.

На последнем шаге вы решаете как инсталлировать ваш модуль.
Вы можете инсталлировать его из локального источника (папки), опубликовать его в Github, затем в NPM, откуда будет инсталлировать его командой:

npm install -g my-toolkit

В нашем случае мы собираемся инсталлировать модуль глобально из локальной папки, выполнив команду:

npm install -g C:\my-toolkit

Не забудьте, что в команде для инсталляции обязательно должен присутствовать флаг -g.

После инсталляции ваш модуль будет доступен для вызова по команде:

dosomethingawesome

5. Деинсталляция вашего глобального модуля.

Для деинсталляции (uninstall) вашего модуля (даже если он был инсталлирован из локальной папки, расположенной на вашем компьютере) выполните команду:

npm uninstall -g my-toolkit

6. Примечания.

Если вы хотите сделать что-то более сложное, то можете поиграть с передачей аргументов в ваш глобальный модуль. Пример следующего кода для файла "demo-global.js", расположенного в папке "bin", позволит принимать переданные в ваш модуль аргументы с помощью короткой команды dosomethingawesome:

#!/usr/bin/env node

// Удалить нулевой и первый аргумент: node и script.js.
var args = process.argv.splice(process.execArgv.length + 2);

// Получить первый аргумент.
var name = args[0];

var myLibrary = require('../lib/index.js');

//  Вывести переданный текст в консоль.
myLibrary.say('Hello, ' + name + '!');

В итоге выполнение команды:

dosomethingawesome Boris

выведет на экран:

Hello, Boris!

среда, 26 февраля 2020 г.

Node.js - TLS Server and Client

1. Overview

This is an example of using module tls in NodeJS to create a client securely connecting to a TLS server.

It is a modified version from documentation about TLS, in which:

- The server is a simple echo one. Clients connect to it, get the same thing back if they send anything to the server.

- The server is a TLS-based server.

- Clients somehow get the server's public key and use it to work securely with the server.

2. Preparation

We need to generate keys & certs for the server. Pay attention to Common Name (e.g. server FQDN or YOUR name) when creating server-csr.pem. It should be your domain name.

$ mkdir tls
$ cd tls
$ openssl genrsa -out server-key.pem 4096
$ openssl req -new -key server-key.pem -out server-csr.pem
$ openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem

For example:

$ openssl req -new -key server-key.pem -out server-csr.pem

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank.
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:VN
State or Province Name (full name) [Some-State]:Hanoi
Locality Name (eg, city) []:Hanoi
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Evolas Technologies
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:evolastech.com
Email Address []:hi@evolastech.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

3. Run the demo

$ node server.js
$ node client.js

You may have following things printed out:

server bound
server connected unauthorized
client connected authorized
welcome!

4. Files

client.js

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

const options = {
    ca: [ fs.readFileSync('server-cert.pem') ]
};

var socket = tls.connect(8000, 'evolastech.com', options, () => {
    console.log('client connected', socket.authorized ? 'authorized' : 'unauthorized');
    process.stdin.pipe(socket);
    process.stdin.resume();
});

socket.setEncoding('utf8');

socket.on('data', (data) => {
    console.log(data);
});

socket.on('end', () => {
    console.log('Ended')
});

sever.js

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

const options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem'),
    rejectUnauthorized: true
};

const server = tls.createServer(options, (socket) => {
    console.log('server connected', socket.authorized ? 'authorized' : 'unauthorized');
    socket.write('welcome!\n');
    socket.setEncoding('utf8');
    socket.pipe(socket);
});

server.listen(8000, () => {
    console.log('server bound');
});

четверг, 8 августа 2019 г.

Remove files after "npm install" command

Файл remove-files.js:

var fs = require('fs');

var doNotRemoveFiles =  [
    'remove-files.js',
    'node_modules',
    '@types',
    'dist',
    'docs',
    'src',
    'tests',
    '.gitignore',
    '.npmignore',
    '.npmrc',
    '.stylelintrc',
    'karma.conf.js',
    'package.json',
    'postcss.config.js',
    'readme.md',
    'tsconfig.json',
    'tslint.json',
    'webpack.config.js',
];

fs.readdir('./', function (error, fileNames) {
    if (error) {throw error;}
    var fileNamesCounter = fileNames.length;
    fileNames.forEach(function (fileName) {
        var len = doNotRemoveFiles.length;
        while (len--) {
            if (doNotRemoveFiles[len] === fileName) {return;}
        }
        fs.stat('./' + fileName, function (error, stats) {
            if (stats.isFile()) {
                fs.unlink('./' + fileName, function (error) {
                    if (error) {throw error;}
                    fileNamesCounter--;
                    if (fileNamesCounter === 0) {
                        console.log('DONE');
                    }
                });
            } else if (stats.isDirectory()) {
                fs.rmdir('./' + fileName, function (error) { // Remove empty "etc" directory.
                    if (error) {throw error;}
                    fileNamesCounter--;
                    if (fileNamesCounter === 0) {
                        console.log('DONE');
                    }
                });
            }
        });
    });
});

Node.js UUID

Файл uuid.js:

var crypto = require('crypto');

function rng () {
    return crypto.randomBytes(16);
}

/**
 * Convert array of 16 byte values to UUID string format of the form:
 * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 */
var byteToHex = [];
for (var i = 0; i < 256; ++i) {
    byteToHex[i] = (i + 0x100).toString(16).substr(1);
}

function bytesToUuid (buf, offset) {
    var i = offset || 0;
    var bth = byteToHex;
    // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
    return [
        bth[buf[i++]], bth[buf[i++]],
        bth[buf[i++]], bth[buf[i++]], '-',
        bth[buf[i++]], bth[buf[i++]], '-',
        bth[buf[i++]], bth[buf[i++]], '-',
        bth[buf[i++]], bth[buf[i++]], '-',
        bth[buf[i++]], bth[buf[i++]],
        bth[buf[i++]], bth[buf[i++]],
        bth[buf[i++]], bth[buf[i++]]
    ].join('');
}

function uuid (options, buf, offset) {
    var i = buf && offset || 0;

    if (typeof options === 'string') {
        buf = options === 'binary' ? new Array(16) : null;
        options = null;
    }
    options = options || {};

    var rnds = options.random || (options.rng || rng)();

    // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
    rnds[6] = (rnds[6] & 0x0f) | 0x40;
    rnds[8] = (rnds[8] & 0x3f) | 0x80;

    // Copy bytes to buffer, if provided
    if (buf) {
        for (var ii = 0; ii < 16; ++ii) {
            buf[i + ii] = rnds[ii];
        }
    }

    return buf || bytesToUuid(rnds);
}

Файл index.js:

var uuid = require('./uuid');

console.log(uuid());

module.exports = uuid;

Node.js CLI Spinner

Файл spinner.js

function spinner () {
    var sequence = ['|', '/', '-', '\\'];
    var index = 0;
    var intervalId;
    return {
        start: function (doNotBlockNodeJsProcess) {
            index = 0;
            process.stdout.write(sequence[index]);
            intervalId = setInterval(function () {
                process.stdout.write(sequence[index].replace(/./g, '\b'));
                if (index < sequence.length - 1) {
                    index = index + 1;
                } else {
                    index = 0;
                }
                process.stdout.write(sequence[index]);
            }, 100);
           if (doNotBlockNodeJsProcess) {
                intervalId.unref();
           }
        },
        stop: function () {
            clearInterval(intervalId);
            process.stdout.write(sequence[index].replace(/./g, '\b'));
        }
    };
}

module.exports = spinner;

Файл index.js

var spinner = require('spinner')();

spinner.start(true);

setTimeout(function () {
     spinner.stop();
}, 5000);

XHR Test request in browser console

var data = {a: 1, b: 2};
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://site.com/send/data', false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
if (xhr.status !== 200) {
    console.log('ERROR');
} else {
    console.log(xhr.responseText);
}