вторник, 18 июля 2017 г.

Асинхронная функция - краткий пример

// Асинхронная функция

// Обратите внимание, что async function всегда возвращает промис
// и ключевое слово await может использоваться только в функциях с ключевым словом async.

function fetchTextByPromise () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("ES 8");
    }, 2000);
  });
}

async function sayHello () {
  const externalFetchedText = await fetchTextByPromise();
  console.log(`Hello, ${externalFetchedText}`); // Hello, ES 8
}

sayHello();

// Вызов sayHello выведет Hello, ES 8 через 2 секунды.

console.log(1);

sayHello();

console.log(2);

// Напечатает:
// 1 // сразу
// 2 // сразу
// Hello, ES 8 // через 2 секунды

вторник, 11 июля 2017 г.

Node.js Шпаргалка по API FS Open, Read, Write, Close

const fs = require('fs');

// Read file

const filePath1 = 'test-file1.txt';

fs.open(filePath1, 'r', function (error, fd) {
    if (error) {throw error;}
    console.log('File 1 opened');
    fs.fstat(fd, function (error, stat) {
        if (error) {throw error;}

        const fileSize = stat.size;
        let chunkSize = 2; // 512; // in bytes

        let fileBuffer = Buffer.alloc(fileSize);
        let totalBytesRead = 0;

        function readDataFromFile () {
            if (totalBytesRead < fileSize) {
                if ((totalBytesRead + chunkSize) > fileSize) {
                    chunkSize = fileSize - totalBytesRead;
                }
                fs.read(fd, fileBuffer, totalBytesRead, chunkSize, totalBytesRead, function (error, chunkBytesRead, buffer) {
                    if (error) {throw error;}
                    totalBytesRead += chunkSize;
                    console.log(buffer.toString('utf8', 0, fileSize));
                    readDataFromFile();
                });
            } else {
                fs.close(fd, function (error) {
                    if (error) {throw error;}
                    console.log('File 1 closed');
                });
            }
        }
        readDataFromFile();
    });
});

// Write file from string

const filePath2 = 'test-file2.txt';

fs.open(filePath2, 'w', function (error, fd) {
    if (error) {throw error;}
    console.log('File 2 opened');
    fs.fstat(fd, function (error, stat) {
        if (error) {throw error;}

        const data = 'abcdefghijklmnopqrstuvwxyz';
        let chunkSize = 1; // in characters

        const dataSize = data.length;
        let totalCharactersWritten = 0;

        let chunk = data.slice(0, chunkSize);

        function writeDataToFile () {
            if (totalCharactersWritten < dataSize) {
                fs.write(fd, chunk, totalCharactersWritten, 'utf8', function (error, chunkCharactersWritten, string) {
                    if (error) {throw error;}
                    totalCharactersWritten += chunk.length;
                    console.log(string);
                    if ((totalCharactersWritten + chunkSize) > dataSize) {
                        chunkSize = dataSize - totalCharactersWritten;
                    }
                    chunk = data.slice(totalCharactersWritten, totalCharactersWritten + chunkSize);
                    writeDataToFile();
                });
            } else {
                fs.close(fd, function (error) {
                    if (error) {throw error;}
                    console.log('File 2 closed');
                });
            }
        }
        writeDataToFile();
    });
});

// Write file from buffer

const filePath3 = 'test-file2.txt';

fs.open(filePath3, 'w', function (error, fd) {
    if (error) {throw error;}
    console.log('File 3 opened');
    fs.fstat(fd, function (error, stat) {
        if (error) {throw error;}

        const data = Buffer.from('abcdefghijklmnopqrstuvwxyz');
        let chunkSize = 2; // in bytes

        const dataSize = data.length;
        let totalBytesWritten = 0;

        let chunk = data.slice(0, chunkSize);

        function writeDataToFile () {
            if (totalBytesWritten < dataSize) {
                fs.write(fd, chunk, 0, chunkSize, totalBytesWritten, function (error, chunkBytesWritten, buffer) {
                    if (error) {throw error;}
                    totalBytesWritten += chunkSize;
                    console.log(buffer.toString('utf8'));
                    if ((totalBytesWritten + chunkSize) > dataSize) {
                        chunkSize = dataSize - totalBytesWritten;
                    }
                    chunk = data.slice(totalBytesWritten, totalBytesWritten + chunkSize);
                    writeDataToFile();
                });
            } else {
                fs.close(fd, function (error) {
                    if (error) {throw error;}
                    console.log('File 3 closed');
                });
            }
        }
        writeDataToFile();
    });
});

среда, 5 июля 2017 г.

Node.js Шпаргалка по API Stream

// Потоки предназначены для обработки данных в виде строк или двоичных данных в виде буферов, но также можно обрабатывать и данные в виде объектов
// Работа streams основана на работе EventEmitter.
// При создании события on('data') поток чтения работает в автоматическом режиме
// При создании события on('readable') поток чтения работает в пошаговом режиме и требует ручного вызова метода read() для чтения данных
// Поток чтения в режиме 'readable' изначально поставлен на паузу и работает в пошаговом режиме, но его можно переключить в автоматический режим
// Для этого необъодимо добавить обработчик события on('data') и снять поток чтения с режима паузы с помощью метода resume() или просто использовать метод pipe()
// Для обратного переключения из автоматического режима в пошаговый необходимо вызвать метод pause(), удалить обработчик события on('data') или просто использовать метод unpipe()
// Для дуплексных потоков необходимо определить и метод _read и метод _write
// События клиента и сервера это тоже streams.
// Используются 4 основных события:
// on('data') или on('readable') - когда идет поток чтения данных - 'data' - считывает данные в автоматическом режиме, 'readable' считывает данные в ручном пошаговом режиме
// on('end') -  когда поток чтения данных завершен
// on('error') - если во время чтения или записи данных происходит ошибка
// on('finish') - когда завершен поток записи данных, вызывается после вызова метода writeStream.end()
// И дополнительное событие
// on('drain') - когда данные записаны из буфера чтения
// on('close') - кода поток завершен
// Методы:
// readStream.push('data') - для помещения данных в буфер потока чтения
// readStream.unshift() - заталкивает (возвращает) часть данных обратно во внутренний буфер, если они не были приняты, например, потоком чтения, и потому могут быть переданы в другой поток
// readStream.read(10) - для считывания данных из потока чтения только в пошаговом режиме, когда поток чтения поставлен на паузу
// readStream.read(0) - всегда возвращает null и используется только для обновления (refresh) потока чтения данных
// writeStream.write('data') - для записи данных в потоке записи,возвращает true, если внутренний буфер еще не переполнен и false, если внутренний буфер уже переполненн данными для записи
// writeStream.end() - для завершения записи данных в потоке записи
// readStream.pause() - для остановки чтения данных из потока чтения и перехода в пошаговый режим чтения данных, если мы находимся в режиме чтения с помощью событий
// readStream.resume() - для запуска потока чтения с помощью событий, если мы находимся в пошаговом ражиме чтения данных
// readStream.isPaused() - проверяет поставлен ли поток чтения на паузу, используется в основе метода pipe() и в других случаях обычно не используется
// readStream.pipe(writeStream) - осуществляет передачу данных из потока в поток без прерывания, возвращает последний переданный ей поток, что позволяет создавать цепочки потоков
// readStream.unpipe(writeStream) - останавливает передачу данных из потка чтения в поток записи
// writeStream.cork() - записывает все данные для записи из внутреннего буфера в память
// writeStream.uncork() - вынимает все данные для записи из внутреннего буфера памяти
// writeStream.setDefaultEncoding() - устанаdливает кодировку по умолчанию для трансформации частей с буферами в строки
// readStream.setEncoding() - устанавливает кодировку для данных потока чтения
// readStream.destroy() - уничтожает поток чтения и вызывает событие с передачей ему сообщения об ошибке. При использовании необходимо внутри класса создать метод _destroy()
// writeStream.destroy() - уничтожает поток записи и вызывает событие с передачей ему сообщения об ошибке. При использовании необходимо внутри класса создать метод _destroy()
// transformStream.destroy() - уничтожает поток трансформации и вызывает событие с передачей ему сообщения об ошибке. При использовании необходимо внутри класса создать метод _destroy()
// Для определения момента завершения чтения и записи данных можно указать в pipe опцию {end: false} и задать свой слушатель события on('end')
// readStream.pipe(writeStream, {end: false});
// readStream.on('end', function () {
//     writeStream.end('Goodbye');
// });

const stream = require('stream');

// Readable stream

(function(){

class ReadableStream extends stream.Readable {
    // Опции, которые можно передать в поток чтения:
    // {
    //        highWaterMark: 16 // максимальное число байт, которое можно хранить во внутреннем буфере до прекращения чтения или число объектов
    //      , encoding: 'utf8' // null или строка с кодировкой, согласно которой буферы будут декодированы в строки во время чтения
    //      , objectmode: false // вместо передачи данных в виде буферов или строк можно передавать для читения объекты, в этом случае данная опция должна быть равна true
    // }
    constructor(content, options) {
        super(options); // Опции потока чтения
        this.content = content; // Переданное в поток чтения содержимое
    }
    // Для создания потока чтения обязательно нужно реализовать свой внутренний метод _read()
    // Метод _read() внутри себя должен обязательно вызывать метод this.push() для передачи данных во внутренний буфер потока чтения
    _read (size) { // size - размер части данных в байтах, может быть не задан, если передаются данные в виде объектов
        if (this.content) { // Если осталось еще необработанное содержимое
            this.push(this.content.slice(0, size)); // Заталкиваем новую часть данных во внутренний буфер потока чтения
            this.content = this.content.slice(size); // Отрезаем часть уже обработанного содержимого
        } else {
            this.push(null); // Заталкиваем null во внутренний буфер потока чтения для обозначения того, что поток чтения завершен
        }
        // Если в процессе чтения данных возникнет ошибка, то лучше её выбросить с помощью события
        // if (this.content.slice(0, size) !== 'abc') {
        //     process.nextTick(function () {process.emit('error', 'Read stream error')});
        //     return;
        // }
    }
}

const readStream = new ReadableStream("The quick brown fox jumps over the lazy dog."); // Передаем в поток чтения исходные данные для обработки
readStream.setEncoding('utf8'); // устанваливает кодировку для данных в потоке чтения
const chunkSize = 10; // Устанавливаем размер части данных для обработки
let chunk = readStream.read(chunkSize); // Считываем часть данных
while (chunk !== null) { // Если считанные данные не равны null, то поток чтения еще не закончен
    console.log(chunk.toString()); // Выводим считанную часть данных в конксоль
    chunk = readStream.read(chunkSize); // Считываем следующую часть данных
}

// The quick
// brown fox
// jumps over
//  the lazy
// dog.

const otherReadStream = new ReadableStream("How now brown cow?");
otherReadStream.pipe(process.stdout); // Автоматически выводим все считанные данные в консоль

// How now brown cow

// Для чтения данных с помощью потока чтения можно использовать следующие функции

// Для управляемого пошагового чтения данных из потока чтения
// let chunk = readStream.read(chunkSize);
// while (chunk !== null) {
//     console.log(chunk.toString());
//     chunk = readStream.read(chunkSize);
// }

// Для автоматического чтения данных из потока чтения с помощью событий
// readStream.on('data', function (chunk) {console.log(chunk.toString());});
// readStream.on('end', function () {console.log('Read stream end');});

// Для передачи данных из потока чтения в другой поток
// readStream.pipe(target);

// Для определения момента завершения чтения и записи данных можно указать в pipe опцию {end: false} и задать свой слушатель события on('end')
// readStream.pipe(writeStream, {end: false});
// readStream.on('end', function () {
//     writeStream.end('Goodbye');
// });

// Дополнительные события для потока чтения

// readStream.on('close', function () {console.log('Read stream closed');});

// readStream.on('error', function (error) {console.log('Read stream error: ' + error.message);});

})();

// Пример чтения потока данных, состоящих из объектов

(function(){

const data = [
      {id: 1, name: 'object 1', value: 1}
    , {id: 2, name: 'object 2', value: 2}
    , {id: 3, name: 'object 3', value: 3}
]

class ReadStream extends stream.Readable {
    constructor (content, options) {
        super(options);
        this.content = content;
        this.curIndex = 0;
    }
    // Метод _read() определяет какие данных надо положить в очередь для чтения с помощью метода push() и дальнейшей передачи их потребителю данных
    // Когда данные для чтения заканчиваются данный метод должен положить в очередь чтения null с помощью метода push(null), который сообщит потребителю данных, что поток чтения завершился
    _read () {
        if (this.curIndex < this.content.length) {
            const chunk = this.content[this.curIndex++];
            this.push(chunk);
        } else {
            this.push(null);
        }
    }
}

// Для чтения данных в виде объектов вместо строк и буферов обязательно надо прописать опцию {objectMode: true}
const readStream = new ReadStream(data, {objectMode: true});
/*
// При возникновении события 'readable' мы значем, что данные уже находятся во внутреннем буфере и готовы для пошагового чтения с помощью метода read()
readStream.on('readable', function() {
    let chunk = readStream.read();
    while (chunk !== null) {
        console.log('Read received: ' + JSON.stringify(chunk));
        chunk = readStream.read();
    }
});
*/
// При добавлении события 'data' данные автоматически считываются и передаются потребителю
// В данном режиме можно ставить поток на паузу, а потом его возобновлять
// Как только будет вызван метод pause() поток чтения перестанет принимать данные по событию 'data' до тех пор, пока не будет вызван метод resume()
// Однако поток чтения продолжит в этом время заталкивать данные в очередь буфера чтения
readStream.on('data', function (chunk) {
    console.log('Received chunk: ' + JSON.stringify(chunk));
    console.log('Pausing read stream for 2 seconds');
    readStream.pause();
    setTimeout(function () {
        console.log('Resuming stream');
        readStream.resume();
    }, 2000);
});

readStream.on('end', function() {
    console.log('Read done');
});

})();

// Writable stream

(function(){

const readStream = new stream.Readable(); // Создаем поток чтения
readStream.push('the quick brown fox '); // Заталкиваем во внутренний буфер потока чтения части данных
readStream.push('jumps over the lazy dog.');
readStream.push(null); // Заталкиваем null во внутренний буфер потока чтения для обозначения того, что поток чтения завершен

class WritableStream extends stream.Writable {
    // Опции, которые можно передать в поток записи:
    // {
    //        highWaterMark: 16 // максимальное число байт, которое можно хранить во внутреннем буфере до прекращения чтения или число объектов
    //      , decodeStrings: true // cледует ли декодировать строки в буфер, прежде чем передавать их во внутренний метод _write()
    // }
    constructor (options) {
        super(options);  // Опции потока записи данных
    }
    // Для создания потока записи обязательно нужно реализовать свой внутренний метод _write
    // Метод _write определяет куда надо направить данные
    // chunk - часть данных, которую надо записать во время потока записи
    // encoding - кодировка части данных
    // next - функция, которую нужно будет вызывать каждый раз для перехода к следующей порции данных для записи, она сообщает, что текущая часть данных записана и можно брать следующую
    _write (chunk, encoding, next) {
        console.log(chunk.toString()); // Выводим переданные части данных в консоль
        console.log('Waiting 2 seconds');
        setTimeout(function() {
            console.log('Finished waiting');
            next(); // Вызов функции next() сообщает, что можно переходить к записи следующей части данных
            // Поток записи не будет получать новые данные, пока функци next() не будет вызвана
            // Функцию next можно вызывать с ошибкой, если в данных обнаружена ошикба: next(new Error('chunk is invalid'));
            // Пример:
            // if (chunk.toString().indexOf('a') >= 0) {
            //     next(new Error('chunk is invalid'));
            // } else {
            //     next();
            // }
        }, 2000);
    }
}

const writeStream = new WritableStream(); // Создаем поток записи

readStream.pipe(writeStream); // Автоматически передаем данные из потока чтения в поток записи

// Для записи данных с помощью потока записи можно использовать следующие функции

// Для управляемого пошаговой записи данных из потока чтения
// let chunk = readStream.read(chunkSize);
// while (chunk !== null) {
//     writableStream.write(chunk, 'utf8');
//     chunk = readStream.read(chunkSize);
// }
// writableStream.end();

// Для автоматической записи данных из потока чтения с помощью событий
// readStream.on('data', function (chunk) {writableStream.write(chunk);});
// readStream.on('end', function () {writableStream.end();});

// Для передачи данных из потока чтения в поток записи
// readStream.pipe(writableStream);

// Дополнительные события для потока записи

// writableStream.on('close', function () {console.log('Write stream closed');});

// Во время работы потока записи данные поступают в его внутренний буфер
// Данный буфер в какой-то момент может быть заполнен данными до предела
// На этот случай поток поступаления данных для записи приостанавливают
// Возобновить дальнейшее получение данных можно будет по событию 'darain' как только буфер потока записи очистится
// writableStream.once('drain', function () {readableStream.resume();});

// writableStream.on('error', function (error) {console.log('Write stream error: ' + error.message);});

 // Это событие для потока записи аналогично событию on('end') для потока чтения
// writableStream.on('finish', function () {console.log('Write stream end');});

})();

// Transform stream

(function(){

const readStream = new stream.Readable(); // Создаем поток чтения
readStream.push('the quick brown fox '); // Заталкиваем во внутренний буфер потока чтения части данных
readStream.push('jumps over the lazy dog.');
readStream.push(null); // Заталкиваем null во внутренний буфер потока чтения для обозначения того, что поток чтения завершен

class TransformStream extends stream.Transform {
    constructor (options) {
        super(options); // Опции потока трансформации данных
    }
    // Для создания потока трансформации обязательно нужно реализовать свой внутренний метод _transform
    // Метод _transform() внутри себя должен обязательно вызывать метод this.push() для передачи данных потребителю данных
    // next - функция, которую нужно будет вызывать каждый раз для перехода к следующей порции данных для трансформации
    _transform (chunk, encoding, next) {
        const data = chunk.toString().toUpperCase(); // Трансформируем переданные в поток трансформации части данных
        this.push(data); // Заталкиваем трансформированную часть данных во внутренний буфер потока трансформации
        next();
    }
    // Опционально можно реализовать внутренний метод  _flush()
    // Данный метод будет вызван в конце потока, когда вся передача данных завершится
    // Его можно использовать для очистки буферов после выполнения все работы
    _flush (next) {
        next();
    }
}

const transformStream = new TransformStream(); // Создаем поток трансформации

readStream.pipe(transformStream).pipe(process.stdout); // Автоматически передаем данные из потока чтения в поток трансформации и выводим их в консоль

// THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.

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

// Для автоматической трансформации данных из потока чтения с помощью событий
// readStream.on('data', function (chunk) {...);
// readStream.on('end', function () {...});

// Для передачи данных из потока чтения в поток трансформации и далее в поток записи
// readStream.pipe(transformStream).pipe(writableStream);

})();

// Пример трансформации потока данных, состоящих из объектов

(function(){

const data = [
      {id: 1, name: 'object 1', value: 1}
    , {id: 2, name: 'object 2', value: 2}
    , {id: 3, name: 'object 3', value: 3}
]

class ReadStream extends stream.Readable {
    constructor (content, options) {
        super(options);
        this.content = content;
        this.curIndex = 0;
    }
    _read () {
        if (this.curIndex < this.content.length) {
            const chunk = this.content[this.curIndex++];
            this.push(chunk);
        } else {
            this.push(null);
        }
    }
}

class WritableStream extends stream.Writable {
    constructor (options) {
        super(options);
    }
    _write (chunk, encoding, next) {
        console.log('After transform: ' + JSON.stringify(chunk));
        next();
    }
}

class TransformStream extends stream.Transform {
    constructor (options) {
        super(options);
    }
    _transform (chunk, encoding, next) {
        console.log('Transform before: ' + JSON.stringify(chunk));
        if (typeof chunk.originalValue === 'undefined') {
            chunk.originalValue = chunk.value;
            chunk.value++;
        }
        console.log('Transform after: ' + JSON.stringify(chunk));
        if (chunk.originalValue !== 1) {
            this.push(chunk);
        }
        next();
    }
}

const readStream = new ReadStream(data, {objectMode: true});

const writeStream = new WritableStream({objectMode: true});

// Для трансформации данных в виде объектов вместо строк и буферов обязательно надо прописать опцию {objectMode: true}
const transformStream = new TransformStream({objectMode: true});

readStream.pipe(transformStream).pipe(writeStream);

})();

// Duplex stream

(function(){

class DuplexStream extends stream.Duplex {
    constructor (source, options) {
        super(options);
        this.source = source;
    }
    _read (size) {
        this.source.fetchSomeData(size, function (data, encoding) {
            this.push(Buffer.from(data, encoding));
        });
    }
    _write (chunk, encoding, next) {
        // Данный пример с данными обрабытывает только строки
        if (Buffer.isBuffer(chunk)) {
            chunk = chunk.toString();
        }
        this.source.writeSomeData(chunk);
        next();
    }
}

})();

// Пример дуплексного потока данных, состоящих из объектов

(function(){

// Все Transform streams являются Duplex Streams
const myTransform = new stream.Transform(
    {
          writableObjectMode: true
        , transform: function (chunk, encoding, next) {
            // При необходимости привести часть данных к числу
            chunk |= 0;
            // Трансформировать часть данных во что-то ещё
            const data = chunk.toString(16);
            // Затолкнуть данные в очередь для чтения
            next(null, '0'.repeat(data.length % 2) + data);
          }
    }
);

myTransform.setEncoding('ascii');

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

myTransform.write(1); // Prints: 01
myTransform.write(10); // Prints: 0a
myTransform.write(100); // Prints: 64

})();

// Пример генерации данных в потоке чтения и записи их в потоке записи

(function(){

const readableSream = stream.Readable();
const writableStream = stream.Writable();

let char = 97;

readableSream._read = function () { // Данный метод вызывается в цикле чтения потока данных
    readableSream.push(
        String.fromCharCode(char++) // Помещение в буфер чтения данных
    );
    if (char > 'z'.charCodeAt(0)) {
        readableSream.push(null); // Помещение в буфер чтения метки null, сообщающей о завершении потока чтения
    }
};

writableStream._write = function (chunk, encoding, next) { // Данный метод вызывается в цикле записи потока данных
    console.log(chunk.toString('utf8')); // Вывод данных для записи в консоль
    next();
};

readableSream.on('data', function (chunk) {
    writableStream.write(chunk);
});

readableSream.on('end', function () {
    writableStream.end();
});

writableStream.on('finish', function () {
    console.log('Writable stream end');
});

// Помещение данных в буфер потока чтения
readableSream.push('1');
readableSream.push('2');
readableSream.push('3');
// Передача данных в поток чтения, минуя бувер чтения с помощью событий
readableSream.emit('data', '4');
readableSream.emit('data', '5');
readableSream.emit('data', '6');
// Помещение в буфер потока чтения пометки null, говорящей потоку чтения о том, что передача данных завершена
readableSream.push(null); // Readable stream end
console.log('Readable stream end');

// readableSream.pipe(writableStream); // Всё выше перечисленное можно записать в одну эту строку.

})();

// Потоковое чтение и потоковая запись данных из файлов

(function(){

const fs = require('fs');

const readStream = fs.createReadStream('./file1.txt');
const writeStream = fs.createWriteStream('./file2.txt');

readStream.setEncoding('utf8');

// Первый вариант чтения данных с помощью события 'data'
// Отличается от события 'readable' тем, что переводит поток в автоматический режим чтения данных
readStream.on('data', function (chunk) {
    console.log('Read stream data: ' + chunk.toString());
    const ready = writeStream.write(chunk, 'utf8'); // Если внутренниц буфер потока записи уже заполнен до конца, то write() вернет false и процесс поступления новых данных на время остановится
    if (ready === false) {
        this.pause();
        writeStream.once('drain', this.resume.bind(this)); // событие 'drain' сообщает, что запись части данных завершена и можно переходить к чтению и записи следующей части данных
    }
});

// Альтернативный вариант чтения данных с помощью события 'readable'
// Отличается от события 'data' тем, что переводит поток в ручной режим чтения данных
readStream.on('readable', function () {
    let chunk = readStream.read();
    while (chunk !== null) {
        console.log('Read stream data: ' + chunk.toString());
        writeStream.write(chunk, 'utf8');
        chunk = readStream.read();
    }
});

readStream.on('end', function () {
    console.log('Read stream end');
    writeStream.end();
});

writeStream.on('finish', function () {
    console.log('Write stream end');
});

readStream.on('error', function (error) {
    console.log('Read stream error: ' + error.message);
});

writeStream.on('error', function (error) {
    console.log('Write stream error: ' + error.message);
});

// readStream.pipe(writeStream); // Всё выше перечисленное можно записать в одну эту строку.

})();

вторник, 4 июля 2017 г.

Node.js Шпаргалка по API HTTP Server и Client

const http = require('http');
const net = require('net');
const url = require('url');

// ============== COMMON ==============

console.log(http.METHODS);
console.log(http.STATUS_CODES);
console.log(http.globalAgent);

// ============== Server ==============

const server = http.createServer();

server.maxHeadersCount = 2000;

server.keepAliveTimeout = 5000;

server.timeout = 120000;
server.setTimeout(120000, function () {
    console.log('Server timeout');
});

server.on('connection', function (socket) {
    console.log('Client connected');
});

// Используется только, если запрос с типом GET или POST
server.on('request', function (request, response) {
    console.log(request.headers);
    console.log(request.rawHeaders);
    console.log(request.httpVersion);
    console.log(request.method);
    console.log(request.trailers);
    console.log(request.url);
    // console.log(request.socket.getPeerCertificate()); // Use with HTTPS support only
    request.setTimeout(120000, function () {
        console.log('Client request timeout');
    });
    // request.destroy('my error'); // Работает вместе с request.on('aborted') и request.on('close')
    request.on('aborted', function () {
        console.log('Request has been aborted by the client');
    });
    request.on('close', function () {
        console.log('Underlying connection was closed');
    });
    let clientData = '';
    request.on('data', function (chunk) {
        clientData += chunk;
    });
    request.on('end', function () {
        console.log(request.rawTrailers);
        console.log(clientData);
        response.setTimeout(1200000, function () {
            console.log('Server sockt timeout');
        });
        response.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']); // Will be merged with any headers passed to response.writeHead()
        console.log('Cookie Header: ' + response.getHeader('Set-Cookie'));
        console.log('Header names: ' + response.getHeaderNames());
        console.log(response.getHeaders());
        if (response.hasHeader('Set-Cookie')) {console.log('Header Set-Cookie set');}
        response.removeHeader('Set-Cookie');
        response.writeContinue(); // Server sends a "100 Continue" HTTP response
        // response.statusCode = 404; // Задачется, если не испоьзуется response.writeHead()
        // response.statusMessage = 'Not found'; // Задачется, если не испоьзуется response.writeHead()
        response.writeHead(200, 'OK', {'Content-Type': 'text/html'}); // Note that 'Content-Length' is given in bytes not characters.
        if (response.headersSent) {console.log('Headers sent');}
        if (response.sendDate) {console.log('Date header sent');}
        response.write('Response from server');
        response.addTrailers({'Content-MD5': '7895bf4b8828b55ceaf47747b4bca667'});
        response.end(); // MUST be called on each response.
        server.close();
    });
    response.on('close', function () {
        console.log('Underlying connection was terminated before response.end() was called or able to flush');
    });
    response.on('finish', function () {
        console.log('Server responce ended');
        if (response.finished) {console.log('Responce finished');}
    });
});

// Используется только, если запрос с типом CONNECT вместо GET или POST
server.on('connect', function (request, clientSocket, head) {
    // Connect to an origin server
    const urlFromClient = url.parse('http://' + request.url);
    const serverSocket = net.connect(urlFromClient.port, urlFromClient.hostname, function () {
        clientSocket.write(
               'HTTP/1.1 200 Connection Established\r\n'
            + 'Proxy-agent: Node.js-Proxy\r\n'
            + '\r\n'
        );
        serverSocket.write(head);
        serverSocket.pipe(clientSocket);
        clientSocket.pipe(serverSocket);
    });
});

// Используется только, если запрос с заголовками 'Connection': 'Upgrade' и 'Upgrade': 'websocket'
server.on('upgrade', function (request, socket, head) {
    socket.write(
           'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
        + 'Upgrade: WebSocket\r\n'
        + 'Connection: Upgrade\r\n'
        + '\r\n'
    );
    socket.pipe(socket); // echo back
});

// Нужно только, если клиент устанавливает заголовок Expect с 100-continue
server.on('checkContinue', function (request, response) {
    console.log('Server get 100 Continue');
    response.writeContinue();
});

// Нужно только, если клиент устанавливает заголовок Expect не с 100-continue
server.on('checkExpectation', function (request, response) {
    console.log('Emitted each time a request with an HTTP Expect header is received, where the value is not 100-continue');
});

// Нужен если клиент посылает ошибку
server.on('clientError', function (error, socket) {
    console.log('Client emited error');
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

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

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

server.listen(8080, '127.0.0.5', function (error) {
    if (error) {throw error;}
    console.log('Server started at 127.0.0.5:8080');
    if (server.listening) {
        console.log('Server is listening');
    }
});

// ============== Client ==============

const options = {
      method: 'GET' // 'POST' // 'CONNECT'
    , protocol: 'http:'
    , auth: 'username:password'
    , hostname: '127.0.0.5'
    , port: 8080
    , path: '/' // 'www.google.com:80'
    , agent: new http.Agent({
                      keepAlive: true // Соединение с сервером должно сохраняться и не разрываться для отправки через него следующих запросов request
                    , keepAliveMsecs: 1000
                    , maxSockets: Infinity
                    , maxFreeSockets: 256
                })
    , headers: {
          'My-Header': 'Hello'
        // , 'Connection': 'Upgrade' // Это заголовок нужен, если нужен upgrade подключения
        // , 'Upgrade': 'websocket' // Это заголовок нужен, если нужен upgrade подключения
      }
    , timeout: 120000
};

const client = http.request(options);

client.once('response', function (response) {
    console.log(response.headers);
    console.log(response.rawHeaders);
    console.log(response.httpVersion);
    console.log(response.statusCode);
    console.log(response.statusMessage);
    console.log(response.trailers);
    response.setEncoding('utf8');
    let serverData = '';
    response.on('data', function (chunk) {
        serverData += chunk;
    });
    response.on('end', function () {
        console.log(serverData);
    });
});

// Используется только, если запрос с типом CONNECT
client.on('connect', function (response, socket, head) {
    console.log('Connect emitted each time a server responds to a request with a CONNECT method.');
    // Make a request over an HTTP tunnel
    socket.on('data', function (chunk) {
        console.log(chunk.toString());
    });
    socket.on('end', function () {
        console.log('Connection to Google closed');
        server.close();
    });
    socket.write(
           'GET / HTTP/1.1\r\n'
        + 'Host: www.google.com:80\r\n'
        + 'Connection: close\r\n'
        + '\r\n'
    );
    socket.end();
});

client.on('socket', function () {
    console.log('Emitted after a socket is assigned to this request.');
});

client.on('abort', function () {
    console.log('Request has been aborted by the client');
});

client.on('aborted', function () {
    console.log('Request has been aborted by the server');
});

// Используется только, если сервер выполнил response.writeContinue()
client.on('continue', function () {
    console.log('Server sends a "100 Continue" HTTP response');
});

// Используется только, если запрос с заголовками 'Connection': 'Upgrade' и 'Upgrade': 'websocket'
client.on('upgrade', function (response, socket, upgradeHead) {
    console.log('Client upgraded');
    socket.end();
    process.exit(0);
});

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

client.setHeader('My-Other-Header', 'Hi');
console.log(client.getHeader('My-Header')); // 'Hello'
client.removeHeader('My-Header');

// client.setHeader('Expect', '100-continue'); // Нужно только, если сервер ожидает событие checkContinue
// client.setHeader('Expect', 'not-100-continue'); // Нужно только, если сервер ожидает событие checkExpectation

client.setNoDelay(true);
client.setSocketKeepAlive(true, 1000);
client.setTimeout(10, function () {
    console.log('Client timeout');
});

client.flushHeaders(); // Обычно заголовки и данные упаковываются в единый пакет для отправки. flushHeaders() не деалет эту оптимизацию.

client.write('Client data');

// client.abort();

if (client.aborted) {
    console.log('Client aborted');
}

client.end(); // MUST always call