четверг, 17 июня 2021 г.

Monkey patch XMLHttpRequest

Monkey patch XMLHttpRequest.


 const xhrLogs: any[] = [];


monkeyPatchXMLHttpRequest(xhrLogs);


function monkeyPatchXMLHttpRequest (xhrLogs: any[]): void {

    const open: any = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function (method: string, url: string, async?: boolean, username?: string, password?: string): void {

        xhrLogs.push({method: method, requestURL: url});

        open.apply(this, arguments);

    };

   const send: any = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.send = function (data): void {

        const logIndex: number = xhrLogs.length - 1;

        xhrLogs[logIndex].date = new Date();

        xhrLogs[logIndex].data = data;

        const self: any = this;

        const onreadystatechange: any = self.onreadystatechange;

        if (onreadystatechange) {

            self.onreadystatechange = function () {

                xhrLogs[logIndex].readyState = self.readyState;

                if (self.readyState === 4) {

                    xhrLogs[logIndex].url = self.responseURL;

                    xhrLogs[logIndex].readyState = 4;

                    xhrLogs[logIndex].responseHeaders = self.getAllResponseHeaders();

                    xhrLogs[logIndex].status = self.status;

                    xhrLogs[logIndex].statusText = self.statusText;

                    xhrLogs[logIndex].responseText = self.responseText.indexOf('PNG') !== -1 ? 'Картинка PNG' : self.responseText;

                }

                onreadystatechange.apply(self, arguments);

            }

        }

        send.apply(self, arguments);

    };

}

четверг, 25 марта 2021 г.

Package JavaScript code with Node.js in one executable file using PKG

Package JavaScript code with Node.js in one executable file using PKG.

1) Install library PKG:

npm install pkg

2) Open in your code editor file node_modules/pkg/lib-es5/fabricator.js

3) Find in this file row:

child = children[key] = (0, _child_process.spawn)(cmd, bakes.concat('-e', script), {

4) After this row add line of code to fix administarrtor rights problem:

shell: true,

5) Save your changes in file node_modules/pkg/lib-es5/fabricator.js

6) Open command line ("CMD") and type in it:

cd "... here is path to folder with your source code..."

Then press button "Enter" on your keyboard.

7) Then type in command line:

set NODE_TLS_REJECT_UNAUTHORIZED=0

and press "Enter" again.

This will fix SSL problem.

(If your want to run PKG library from your custom source code you can also write in it:

process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;

to do the same.)

8) Then type in command line for example:

node "D:\Work\Your application\node_modules\pkg\lib-es5\bin.js" your-start-file.js --debug --targets node14-windows-x64

to package your JavaScript application files together with Node.js in one executable file.


Execute JavaScript code in browser address bar

 How to execute JavaScript code in browser address bar?

1) Clear your browser address bar.

2) Copy this line of JavaScript code:

function showlog (string) {var pre = document.createElement('pre'); pre.innerHTML = string; document.getElementById('sbpllogs').appendChild(pre);} var d = document.createElement('div'); d.style.position = 'absolute'; d.style.zIndex = 100000; d.style.top = '0px'; d.style.left = '0px'; d.innerHTML = '<div style="position: relative;"><button id="sbplhideshow" onclick="(function () {' + "var self = document.getElementById('sbplhideshow'); var helpers = Array.prototype.slice.call(document.getElementsByClassName('sbplhelper')); if (self.innerHTML === 'HIDE') {self.innerHTML = 'SHOW'; helpers.forEach(function (helper) {helper.style.display = 'none';});} else {self.innerHTML = 'HIDE'; helpers.forEach(function (helper) {helper.style.display = 'inline-block';});}" + '})()" style="width: 100px; height: 25px; border: 1px solid black; cursor: pointer;">HIDE</button><button class="sbplhelper" onclick="(function () {' + "document.getElementById('sbpleditor').value = '';" + '})()" style="width: 100px; height: 25px; border: 1px solid black; cursor: pointer;">CLEAR</button><button class="sbplhelper" onclick="(function () {' + "var sbpleditor = document.getElementById('sbpleditor'); try {eval(sbpleditor.value); sbpleditor.value = '';} catch (e) {showlog(e); sbpleditor.value = 'ERROR'}" + '})()" style="width: 100px; height: 25px; border: 1px solid black; cursor: pointer;">RUN</button><br /><textarea id="sbpleditor" class="sbplhelper" style="display: inline-block; width: 500px; height: 200px; padding: 4px; background-color: white; border: 1px solid black; font-size: 16px;"></textarea><div id="sbpllogs" class="sbplhelper" style="overflow: auto; display: inline-block; width: 500px; height: 200px; padding: 0px 4px 0px 4px; background-color: white; border: 1px solid black; font-size: 16px;"></div></div>'; document.body.appendChild(d);

3) Paste this line into address bar.

4) Press together on keyboard buttons "Ctrl" and "A" to select all pasted line in the borwser address bar.

5) After that press "Left arrow" on keyboard. In result cursor in the address bar will move to the start of the line.

6) Now type on keyboard (with colon and without space);

javascript:

Result in your address bar will look like that:

[javasript:function showlog (string) {var pre = docu...]

7) Then just press "Enter" on your keyboard. This will execute JavaScript code in address bar.

8) This code will show on your page custom debug window with buttons: "HIDE", "CLEAR", "RUN" and two panels. 

In left panel you can enter your JavaScript code. 

In right panel you can see logs that will show there if you execute in the left panel custom function "showlog()".

For example.

Type in left panel this code:

var a = 10;

showlog("a equals " + a);

Then click on button "RUN".

In right panel you will see:

a equals 10

If you click on button "CLEAR" you will erase all code in the left panel.

If you click on button "HIDE" you will minimize custom debug window. 

After that you can click on button "SHOW" to show custom debug window in full size.





вторник, 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();
*/