вторник, 14 июля 2020 г.

JavaScript Console log image


function consoleLogImage (imageUrlOrBase64Data, imageSizeInPercent) {

    if (imageSizeInPercent === undefined) {imageSizeInPercent = 100;}

    var image = new Image();

    image.onload = function () {

        var style = [

            'font-size: 1px;',

            'padding: ' + (this.height / 100 * imageSizeInPercent) + 'px ' + (this.width / 100 * imageSizeInPercent) + 'px;',

            'background: url(' + imageUrlOrBase64Data + ') no-repeat;',

            'background-size: contain;'

        ].join(' ');

        console.log('%c ', style);

    };

    image.src = imageUrlOrBase64Data;

}



var imageBase64Data = (

    'data:image/png;base64,' +

    'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbG' +

    'yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJ' +

    'cEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVBhXY3wro8KA' +

    'BJigNAyQxmdgAABeXwE3yYq9egAAAABJRU5ErkJggg=='

);


consoleLogImage(imageBase64Data, 50);

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

Node.js HTTPS SSL Сертификаты

Обычно HTTPS сервер делают базовое TLS-рукопожатие и принимают любые клиентские запросы пока может быть найден подходящий шифр. Однако сервер может быть настрое на вызов клиентского CertificateRequest в процессе TLS-рукопожатия.
Это заставляет клиента предоставить валидный сертификат прежде чем можно будет продолжить соединение.

Давайте пройдемся по процессу создания сертификатов вместе с сервером и клиентом использующим их.
Сначала мы создадим Certificate Authority (CA) для возможности подписания наших клиентский сертификатов. (Также давайте используем Certificate Authority (CA) для подписания сертификата для сервера.)
Для упрощения процесса конфигурирования Certificate Authority (CA) создадим следующий конфигурационный файл ca.cnf со следующим содержимым:

[ ca ]
default_ca      = CA_default

[ CA_default ]
serial = ca-serial
crl = ca-crl.pem
database = ca-database.txt
name_opt = CA_default
cert_opt = CA_default
default_crl_days = 9999
default_md = md5

[ req ]
default_bits           = 4096
days                   = 9999
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no
output_password        = password

[ req_distinguished_name ]
C                      = US
ST                     = MA
L                      = Boston
O                      = Example Co
OU                     = techops
CN                     = ca
emailAddress           = certs@example.com

[ req_attributes ]
challengePassword      = test
Далее мы создадим новую certificate authority, используя файл с этой конфигурацией с помощью команды:
openssl req -new -x509 -days 9999 -config ca.cnf -keyout ca-key.pem -out ca-crt.pem

После того, как после генерации мы получим certificate authority в файле ca-key.pem и ca-crt.pem мы создадим private key для нашего сервера командой:

openssl genrsa -out server-key.pem 4096
На следующем шаге мы сгенерируем certificate signing request

Снова для упрощения процесса конфигурирования мы создадим конфигурационный файл server.cnf со следующим соедержимым:

[ req ]
default_bits           = 4096
days                   = 9999
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no
x509_extensions        = v3_ca

[ req_distinguished_name ]
C                      = US
ST                     = MA
L                      = Boston
O                      = Example Co
OU                     = techops
CN                     = localhost
emailAddress           = certs@example.com

[ req_attributes ]
challengePassword      = password

[ v3_ca ]
authorityInfoAccess = @issuer_info

[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert
Теперь на основе этого конфигурационного файла мы сгенерируем certificate signing request командой:
openssl req -new -config server.cnf -key server-key.pem -out server-csr.pem

Теперь подпишем request командой:

openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
В результате этого сертификат для нашего сервер будет готов.

Сервер.

Создадим простой Node.js HTTPS сервер, используя сертификат, который будет доступен по адресу 0.0.0.0:4433

var fs = require('fs'); 
var https = require('https'); 
var options = { 
    key: fs.readFileSync('server-key.pem'), 
    cert: fs.readFileSync('server-crt.pem'), 
    ca: fs.readFileSync('ca-crt.pem'), 
}; 
https.createServer(options, function (req, res) { 
    console.log(new Date() + ' ' + 
        req.connection.remoteAddress + ' ' + 
        req.method + ' ' + req.url); 
    res.writeHead(200); 
    res.end("hello world\n"); 
}).listen(4433);
Вы можете проверить работу сервера через браузер. Не забудьте в сообщить вашей операционной системе доверять созданной намиcertificate через установку файла ca-crt.pem и пометки его, как trusted.

Клиент.

Давайте создадим клиента для соединения с сервером с целью демонстрации работы с клиентским сертификатом.

var fs = require('fs'); 
var https = require('https'); 
var options = { 
    hostname: 'localhost', 
    port: 4433, 
    path: '/', 
    method: 'GET', 
    ca: fs.readFileSync('ca-crt.pem') 
}; 
var req = https.request(options, function(res) { 
    res.on('data', function(data) { 
        process.stdout.write(data); 
    }); 
}); 

req.end();
Мы добавили объект options, в котором установили certificate authority (ca) для того, чтобы клиент доверял public key нашего certificate authority. Для проверки вы можете запустить код клиента, чтобы увидеть, как сервер ответит ему фразой "hello world".

Клиентские сертификаты.

Теперь создадим сертификаты для клиента, которые он будет передавать серверу, следующими командами:

openssl genrsa -out client1-key.pem 4096
openssl genrsa -out client2-key.pem 4096

Для удобства создания сертификатом опять создадим вспомогательные конфигурационные файлы.

Файл client1.cnf со следующим содержимым:

[ req ]
default_bits           = 4096
days                   = 9999
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no
x509_extensions        = v3_ca

[ req_distinguished_name ]
C                      = US
ST                     = MA
L                      = Boston
O                      = Example Co
OU                     = techops
CN                     = client1
emailAddress           = certs@example.com

[ req_attributes ]
challengePassword      = password

[ v3_ca ]
authorityInfoAccess = @issuer_info

[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert

И файл client2.cnf со следующим содержимым:

[ req ]
default_bits           = 4096
days                   = 9999
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no
x509_extensions        = v3_ca

[ req_distinguished_name ]
C                      = US
ST                     = MA
L                      = Boston
O                      = Example Co
OU                     = techops
CN                     = client2
emailAddress           = certs@example.com

[ req_attributes ]
challengePassword      = password

[ v3_ca ]
authorityInfoAccess = @issuer_info

[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/

caIssuers;URI.0 = http://example.com/ca.cert

Теперь выполним команды для создания двух certificate signing requests:

openssl req -new -config client1.cnf -key client1-key.pem -out client1-csr.pem

openssl req -new -config client2.cnf -key client2-key.pem -out client2-csr.pem

И подпишем наши два клиентских сертификата следующими командами:

openssl x509 -req -extfile client1.cnf -days 999 -passin "pass:password" -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem

openssl x509 -req -extfile client2.cnf -days 999 -passin "pass:password" -in client2-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client2-crt.pem

Для того, чтобы убедиться, что всё получилось правильно проверим наши сертификаты следующими командами:

openssl verify -CAfile ca-crt.pem client1-crt.pem
openssl verify -CAfile ca-crt.pem client2-crt.pem

В результате мы должны получить “OK”, если всё сделано верно.

Проверка работы клиента и сервера вместе.

Немного изменим настройки сервера для того, установив requestCert и rejectUnauthorized в true, в options для того, чтобы разрешать запросы от клиента только по сертификатам.

var fs = require('fs'); 
var https = require('https'); 
var options = { 
    key: fs.readFileSync('server-key.pem'), 
    cert: fs.readFileSync('server-crt.pem'), 
    ca: fs.readFileSync('ca-crt.pem'), 
    requestCert: true, 
    rejectUnauthorized: true
}; 
https.createServer(options, function (req, res) { 
    console.log(new Date() + ' ' + 
        req.connection.remoteAddress + ' ' + 
        req.socket.getPeerCertificate().subject.CN + ' ' + 
        req.method + ' ' + req.url); 
    res.writeHead(200); 
    res.end("hello world\n"); 

}).listen(4433);

Теперь, если вы произведете запрос к серверу из клиента без сертификата, то такой запрос сервером будет отвергнут из-за того, что валидный сертификат не будет передан. В Node.js в этом случае будет получена ошибка socket hangup error, из-за того, что TLS будет незавершенным.
Для того, чтобы всё заработало добавим в код клиента key и cert внутрь options.

var fs = require('fs'); 
var https = require('https'); 
var options = { 
    hostname: 'localhost', 
    port: 4433, 
    path: '/', 
    method: 'GET', 
    key: fs.readFileSync('client1-key.pem'), 
    cert: fs.readFileSync('client1-crt.pem'), 
    ca: fs.readFileSync('ca-crt.pem') 
}; 
var req = https.request(options, function(res) { 
    res.on('data', function(data) { 
        process.stdout.write(data); 
    }); 
}); 
req.end(); 
req.on('error', function(e) { 
    console.error(e); 

});

После этого при соединении клиента с сервером клиент в ответ полуичт строку "hello world". А на стороне сервера в это время в консоль будет выведен лог  со строкой "client1", поскольку сервер сможет распознать значение "Common Name", полученное из сертификата клиента.

Если вы попробуете заменить сертификат на клиенте с client1 на client2, тогда вы увидите, как сервер выведет в консоль распознанное "client2".

Отзыв сертификата.

Давайте добавим возможность для отзыва существующих сертификатов.

Для того, чтобы это сделать мы создадим Certificate Revocation List (CRL) и отзовем клиентский сертификат client2.
Прежде чем это сделать мы создадим  пустой текстовый файл:

ca-database.txt

Теперь отзовем клиентский сертификат client2 следующей командой:

openssl ca -revoke client2-crt.pem -keyfile ca-key.pem -config ca.cnf -cert ca-crt.pem -passin 'pass:password'

И далее обновим CRL следующей командой:

openssl ca -keyfile ca-key.pem -cert ca-crt.pem -config ca.cnf -gencrl -out ca-crl.pem -passin 'pass:password'

Наконец определим отозванный сертификат внутри crl внутри options нашего сервера:

var fs = require('fs'); 
var https = require('https'); 
var options = { 
    key: fs.readFileSync('server-key.pem'), 
    cert: fs.readFileSync('server-crt.pem'), 
    ca: fs.readFileSync('ca-crt.pem'), 
    crl: fs.readFileSync('ca-crl.pem'), 
    requestCert: true, 
    rejectUnauthorized: true 
}; 
https.createServer(options, function (req, res) { 
    console.log(new Date()+' '+ 
        req.connection.remoteAddress+' '+ 
        req.socket.getPeerCertificate().subject.CN+' '+ 
        req.method+' '+req.url); 
    res.writeHead(200); 
    res.end("hello world\n"); 

}).listen(4433);

Теперь наш сервер будет учитывать список отозванных сертификатов, благодаря чему запросы от клиента с сертификатом client2 будут сервером отвергаться, а запросы от клиента с сертификатом client1, так же, как и раньше будут приниматься.
Итак, мы увидели, как можно создавать самоподписанные сертификаты (self signed certificates) для клиента и сервера и убедились, что клиенты могут взаимодействовать с сервером, используя только валидные сертификаты, подписанные нами.
Дополнительно мы научились отзывать сертификаты.
Поскольку на сервер мы может видеть значение "Common Name" из сертификата клиента, то мы можем использовать его для идентификации подключившегося клиента на нашем сервере.

engineering.circle.com/https-authorized-certs-with-node-js-315e548354a2

Ajax, XMLHttpRequest, GET, POST, Form, encodeURIComponent, application/x-www-form-urlencoded, multipart/form-data

Во время обычной отправки формы <form> браузер собирает значения из её полей, делает из них строку, которая вставляет в тело GET или POST запроса для отправки на сервер.

При отправке данных через XMLHttpRequest процедуру составления такой строки нужно делать самим в JavaScript-коде.

Кодирование запроса браузером - кодировка urlencoded.

urlencoded - это основной способ кодировки запросов, то есть стандартное кодирование URL.

Возьмем для примера следующую форму:

<form method="GET" action="/submit" >
  <input name="name" value="Boris">
  <input name="surname" value="Ivanov">
  <input type="submit" value="Send">
</form>

Здесь есть два поля: name=Boris и surname=Ivanov.

Браузер при составлении запроса перечисляет такие пары "имя=значение" через символ амперсанда "&" и, поскольку в нешей форме прописан метод GET, то итоговый запрос на сервер будет выглядеть как:

 /submit?name=Boris&surname=Ivanov

Все символы, кроме английских букв, цифр и символов - _ . ! ~ * ' ( ) заменяются на их цифровой код в UTF-8 со знаком %.

Например, пробел заменяется на %20, символ / на %2F, русские буквы кодируются двумя байтами в UTF-8, поэтому, к примеру, Ц заменится на %D0%A6.

Например, форма:

<form method="GET" action="/submit">
  <input name="name" value="Виктор">
  <input name="surname" value="Цой">
  <input type="submit" value="Send">
</form>

будет отправлена на сервер так:

/submit?name=%D0%92%D0%B8%D0%BA%D1%82%D0%BE%D1%80&surname=%D0%A6%D0%BE%D0%B9

в JavaScript есть функция encodeURIComponent для кодирования символов "врчуную":

console.log(encodeURIComponent(' ')); // %20
console.log(encodeURIComponent('/')); // %2F
console.log(encodeURIComponent('В')); // %D0%92
console.log(encodeURIComponent('Виктор')); // %D0%92%D0%B8%D0%BA%D1%82%D0%BE%D1%80

Эта кодировка используется в основном для метода GET, то есть для передачи параметра в строке запроса. По стандарту строка запроса не может содержать произвольные Unicode-символы, поэтому они кодируются как показано выше.

GET-запрос с urlencoded.

В методе GET параметры передаются в URL.

При формировании XMLHttpRequest, мы должны формировать запрос руками, кодируя поля из формы функцией encodeURIComponent.

Например, для посылки GET-запроса с параметрами name и surname, взятых из формы выше, их необходимо закодировать так:

var xhr = new XMLHttpRequest();

var params = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname);

xhr.open('GET', '/submit?' + params, true);

xhr.onreadystatechange = function () {
  if (xhr.readyState == 0) {
    // UNSENT = 0 - исходное состояние
  }
  if (xhr.readyState == 1) {
    // OPENED = 1 - вызван метод open
  }
  if (xhr.readyState == 2) {
    // HEADERS_RECEIVED = 2 - получены заголовки ответа
  }
  if (xhr.readyState == 3) {
    // LOADING = 3 - ответ в процессе передачи (данные частично получены)
  }
  if (xhr.readyState == 4) {
    // DONE = 4 - запрос завершён
  }
};

xhr.send();

Браузер автоматически добавит к данному запросу важнейшие HTTP-заголовки, такие как Content-Length и Connection.

По спецификации браузер запрещает их явную установку, а также некоторых других низкоуровневых HTTP-заголовков, которые могли бы ввести в заблуждение сервер относительно того, кто и сколько данных ему прислал, например Referer. Это сделано в целях контроля безопасности и правильности запроса.

Запрос, отправленный кодом выше через XMLHttpRequest, никак не отличается от обычной отправки формы. Сервер не в состоянии их отличить.

Поэтому в некоторых фреймворках, чтобы сказать серверу, что это AJAX-запрос, добавляют специальный заголовок, например такой:

xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

xhr.open(method, URL, [async, user, password]) - это метод, в который передаются основные параметры запроса:

- method – HTTP-метод. Обычно это "GET" или "POST".
- url – URL, куда отправляется запрос: строка, может быть и объект URL.
- async – если указать false, тогда запрос будет выполнен синхронно, если true, то асинхронно.
- user, password – логин и пароль для базовой HTTP-авторизации (если требуется).

Заметим, что вызов open(), вопреки своему названию, не открывает соединение. Он лишь конфигурирует запрос, но непосредственно отсылается запрос только лишь после вызова send().

Особенностью XMLHttpRequest является то, что отменить setRequestHeader() невозможно.

Если заголовок определён, то его нельзя снять. Повторные вызовы лишь добавляют информацию к заголовку, а не перезаписывают его.

Например:

xhr.setRequestHeader('X-Auth', '123');
xhr.setRequestHeader('X-Auth', '456');

// В результате заголовок получится такой:
// X-Auth: 123, 456

POST-запрос с urlencoded.

В методе POST параметры передаются не в URL, а в теле запроса. Оно указывается в вызове функции send(body).

В стандартных HTML-формах для метода POST доступны три кодировки, задаваемые через атрибут enctype:

application/x-www-form-urlencoded
multipart/form-data
text-plain

В зависимости от enctype браузер кодирует данные соответствующим способом перед отправкой на сервер.

При POST-запросе, созданном с помощью XMLHttpRequest обязательно надо задавать  заголовок Content-Type, содержащий одну из перечисленных выше кодировок.
Заданная кодировка указывает серверу как обрабатывать (раскодировать) пришедший запрос.

Для примера отправим запрос в кодировке application/x-www-form-urlencoded:

var body = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname);

var xhr = new XMLHttpRequest();

xhr.open('POST', '/submit', true);

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

xhr.onreadystatechange = function () {
    ...
    ...
};

xhr.send(body);

В теле POST-запроса всегда используется только кодировка UTF-8, независимо от языка и кодировки страницы.

Если сервер вдруг ожидает данные в другой кодировке, к примеру windows-1251, то их нужно будет перекодировать.

POST-запрос с multipart/form-data.

Кодировка urlencoded за счёт замены символов на %-код может сильно раздуть общий объём пересылаемых данных.
Поэтому для пересылки файлов используется кодировка: multipart/form-data.

В этой кодировке поля пересылаются одно за другим через строку разделитель.

Чтобы использовать этот способ нужно указать метод POST и в атрибуте enctype multipart/form-data :
             
<form method="POST" action="/submit" enctype="multipart/form-data">
  <input name="name" value="Виктор">
  <input name="surname" value="Цой">
  <input type="submit" value="Send">
</form>

Форма при такой кодировке будет выглядеть примерно так:

... HTTP-Заголовки ...

Content-Type: multipart/form-data; boundary=RaNdOmDeLiMiTeR

--RaNdOmDeLiMiTeR
Content-Disposition: form-data; name="name"

Виктор
--RaNdOmDeLiMiTeR
Content-Disposition: form-data; name="surname"

Цой
--RaNdOmDeLiMiTeR--

То есть, поля передаются одно за другим, значения не кодируются, а чтобы было чётко понятно, какое значение где – поля разделены случайно сгенерированной строкой, которую называют "boundary" (англ. граница), в примере выше это RaNdOmDeLiMiTeR.

Сервер видит заголовок Content-Type: multipart/form-data, читает из него границу и раскодирует поля формы.

Такой способ используется в первую очередь при пересылке файлов, так перекодировка мегабайтов через urlencoded существенно загрузила бы браузер. Да и объём данных после неё сильно вырос бы.

Однако, никто не мешает использовать эту кодировку всегда для POST запросов.
Для GET доступна только urlencoded.

Сделать POST-запрос в кодировке multipart/form-data можно и через XMLHttpRequest.

Достаточно указать в заголовке Content-Type кодировку и границу, и далее сформировать тело запроса, удовлетворяющее требованиям кодировки.

Пример кода для того же запроса в кодировке multipart/form-data:

var data = {
  name: 'Виктор',
  surname: 'Цой'
};

var boundary = String(Math.random()).slice(2);
var boundaryMiddle = '--' + boundary + '\r\n';
var boundaryLast = '--' + boundary + '--\r\n'

var body = ['\r\n'];
for (var key in data) {
  // Добавление поля.
  body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data[key] + '\r\n');
}

body = body.join(boundaryMiddle) + boundaryLast;

// Тело запроса готово, отправляем.

var xhr = new XMLHttpRequest();

xhr.open('POST', '/submit', true);

xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);

xhr.onreadystatechange = function() {
  if (this.readyState != 4) return;
  console.log(this.responseText);
}

xhr.send(body);

Тело запроса, передаваемое на сервер, будет иметь вид, описанный выше, то есть поля через разделитель.

Отправка на сервер файла через XMLHttpRequest.

Можно создать запрос, который сервер воспримет как загрузку файла.

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

Content-Disposition: form-data; name="myfile"; filename="pic.jpg"
Content-Type: image/jpeg
(пустая строка)
содержимое файла в виде строки

Пример:

var data = {
  myfile: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUxLjEgKDU3NTAxKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cCBDb3B5IDI8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iMl8xNDQwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzAuMDAwMDAwLCAtMTI3LjAwMDAwMCkiPgogICAgICAgICAgICA8ZyBpZD0iR3JvdXAtQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMC4wMDAwMDAsIDEyNy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJGaWxsLTEiIGZpbGw9IiM5MkNGQUUiIHBvaW50cz0iMCAwIDAgMTguNDQ2NjAxOSAxOC40NDY2MDE5IDAiPjwvcG9seWdvbj4KICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJGaWxsLTIiIGZpbGw9IiMyQTgyODgiIHBvaW50cz0iMS41NTMzOTgwNiAyMCAyMCAyMCAyMCAxLjU1MzM5ODA2Ij48L3BvbHlnb24+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==',
};

var boundary = String(Math.random()).slice(2);
var boundaryMiddle = '--' + boundary + '\r\n';
var boundaryLast = '--' + boundary + '--\r\n'

var body = ['\r\n'];
for (var key in data) {
  // Добавление поля.
  body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data[key] + '\r\n');
}

body = body.join(boundaryMiddle) + boundaryLast;

// Тело запроса готово, отправляем.

var xhr = new XMLHttpRequest();

xhr.open('POST', '/submit', true);

xhr.setRequestHeader('Content-Disposition', 'form-data; name="myfile"; filename="pic.jpg"');
xhr.setRequestHeader('Content-Type', 'image/jpeg');

xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);

xhr.onreadystatechange = function() {
  if (this.readyState != 4) return;
  console.log(this.responseText);
}

xhr.send(body);

Формирование данных для отправки на сервер с помощью FormData.

Современные браузеры поддерживают встроенный объект FormData, который кодирует данные из формы для отправки на сервер.

Например:

<form name="person">
  <input name="name" value="Виктор">
  <input name="surname" value="Цой">
  <input type="submit" value="Send">
</form>

<script>

  // Создать объект для формы.
  var formData = new FormData(document.forms.person);

  // Добавить к пересылке ещё пару ключ - значение.
  formData.append("patronym", "Робертович");

  // Отправить запрос на сервер.
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/url", false);
  xhr.send(formData);

</script>

Этот код отправит на сервер форму с полями name, surname и patronym.

Конструктор new FormData([form]) вызывается либо без аргументов, либо с DOM-элементом формы.

Метод formData.append(name, value) добавляет данные к форме.

Объект formData можно отсылать сразу, интеграция FormData с XMLHttpRequest встроена в браузер. Кодировка данных в запросе при этом будет multipart/form-data.

При просмотре содержимого объекта formData в консоли браузера на первый взгляд может показаться, что объект formData пустой. Но встроенный метод get() может выцепить данные value из него так:

var formData = new FormData(document.forms.person);
formData.get(document.forms.person.elements[0].name); // выдаст value первого дочернего input

Передача данных в формате JSON и другие кодировки запросов на сервер.

XMLHttpRequest сам по себе не ограничивает кодировку и формат пересылаемых данных.

Поэтому для обмена данными часто используется формат JSON:

var xhr = new XMLHttpRequest();

var body = JSON.stringify({
  name: "Виктор",
  surname: "Цой"
});

xhr.open('POST', '/submit', true)

xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');

xhr.onreadystatechange = function () {
    ...
    ...
};

// Отправляем объект в формате JSON с указанием Content-Type: application/json
// Сервер должен уметь такой Content-Type принять и раскодировать.

xhr.send(body);


Запросы на другой сервер.

XMLHttpRequest может осуществлять кроссдоменные запросы на другие сайты (сервера), используя политику CORS.

По умолчанию при запросах на другой сервер не отсылаются cookie и заголовки HTTP-авторизации. Чтобы это изменить, установите xhr.withCredentials в true:

var xhr = new XMLHttpRequest();

xhr.withCredentials = true;

xhr.open('POST', 'http://anywhere.com/request', true);

Выводы.

У форм есть две основные кодировки:
application/x-www-form-urlencoded - по умолчанию и
multipart/form-data - для POST запросов, если явно указана в enctype.

Вторая кодировка обычно используется для больших данных и только для тела запроса.

Для составления запроса в application/x-www-form-urlencoded используется функция encodeURIComponent.

Для отправки запроса в multipart/form-data удобно использовать объект FormData.

Для обмена данными с сервером можно использовать формат JSON с указанием кодировки в заголовке Content-Type: application/json.

В XMLHttpRequest можно использовать и другие HTTP-методы, например PUT, DELETE, TRACE. К ним применимы все те же принципы, что описаны выше.

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

Node.js - Simple Stream Readable Example

var Readable = require('stream').Readable;

var count = 0;

var readStream = new Readable({
    objectMode: true,
    read: function () {
        count += 1;
        if (count === 10) {
            this.push(null);
        } else {
            this.push(count);
        }
    }
});

readStream.on('data', function (chunk) {
    console.log(chunk);
    /*
    // Асинхронное чтение.
    setTimeout(function () {
        readStream.read();
    }, 1000);
    */
});

readStream.on('end', function (chunk) {
    console.log('END');
});

// Синхронное чтение.
console.log(readStream.read());
console.log(readStream.read());
console.log(readStream.read());

/*
// Старт асинхронного чтения.
readStream.read();
*/

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!