Глобальные модули NPM записывает в папку /usr/local/lib/node_modules
npm install -g sax@>=0.1.0 <0.3.1 // Установить модуль определенной версии
npm update -g sax // Обновить модуль до последней версии
npm unistall -g sax // Удалить установленный модуль
Файл package.json записывается в корневую папку проекта
package.json
{
'name': 'MyApp',
'version': 1.0.0,
'dependecies': {
'sax': '0.3.x',
'nano': '*',
'request': '>0.2.0'
}
}
Для установки зависимостей из файла package.json в корневой папке проекта выполняются команды
npm install
npm update // может заменять команду npm install
//------------------------------------------------------------------
Загрузка модулей через функцию require() не меняет состояние глобального простарнства имен.
Поэтому модули можно загружать свободно не боясь что-то повредить в коде.
Module Import
var module = require('module_name_or_path');
Module Export
function printA() {
console.log('A');
}
function printB() {
console.log('B');
}
function printC() {
console.log('C');
}
exports.printA = printA;
exports.printB = printB;
exports.pi = Math.PI;
module.export - это объект, который будет эксортирован в другие сценарии через функцию require().
Экспортировать можно все что угодно: объекты, функции, массивы, переменные.
var myModule = require('./my_module.js');
myModule.printA(); // -> A
myModule.printB(); // -> B
console.log(myModule.pi); // -> 3.141592653589793
//------------------------------------------------------------------
Виды модулей:
- core модули Node.js
- модули, установленные через NPM
- собственные локальные модули
Core модули Node.js загружаются через require() только по имени (и не загружаются по пути до файла).
Core модули всегда загружаются приоритетно, даже ели в системе существуют модули с таким же именем.
var http = require('http');
Модули, установленные через NPM и собственные модули загружаются только по абсолютному или относительному пути относительно того файла, в код которого они будут загружены.
var myModule = require('/home/user/my_modules/my_module.js'); // Использование расширения .js в название файла необязательно
var myModule = require('./lib/my_module.js'); // Файл находится в текущей директории
var myModule = require('../top/my_module.js'); // Файл находится в директории выше
Содержимое модулей кэшируется. Поэтому повторные загрузки модулей через функцию require() загружают тоже самое содержимое файла, даже если оно уже изменилось.
//------------------------------------------------------------------
Node.js использует специальный класс Buffer для работы с бинарными файлами, такими как картинки и базы данных, для того чтобы вы могли оперировать бинарными данными в памяти, изменяя их.
Создание буфера.
var buffer = new Buffer('Hello world!');
var buffer = new Buffer('8b76fde713ce', 'base64'); // Вторым аргументом указывается формат кодировки строки, которая помещается в буфер.
Буфер может работать со следующим кодировками строк:
- ascii
- utf-8
- base64
Можно выводить содержимое буфера методом .toString();
console.log(buffer.toSting()); // -> Hello world!
Вы может создать буфер определенного размера в байтах без сохранения пока в нем содержимого
var buffer = new Buffer(1024); // Создан буфер размером 1024 байта
Можно получить длину буфера.
console.log(buffer.length); // 1024
Получение значения хранимого в буфере по его позиции
var buffer = new Buffer('my buffer content');
console.log(buffer[10]); // -> 99
Можно изменять содержимое в буфере с любой позиции.
buffer[99] = 125;
Можно пройтись в цикле по всем элементам буфера и изменить каждое его значение.
var buffer = new Buffer(100);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = i;
}
Из буфера можно вырезать значения slice() и вставлять их в другие буферы.
var buffer = new Buffer('this is content of my buffer');
var smallBuffer = buffer.slice(8, 19);
console.log(smallBuffer.toString()); // -> the content
Однако новый буфер при этом не создается, а старый не изменяется. Просто новый буфер ссылается в памяти на старый буфер и берет из него содержимое по соотвествующим индексам среза.
Поэтому изменение родительского буфера повлияет на изменение буфера потомка, что может привести к багам.
Кроме того теперь родительский буфер не будет удален сборщиком мусора и приведет к утечке памяти.
Поэтому лучше просто копировать буфер и потом уже изменять копию.
Копирование буфера
var buffer1 = new Buffer('this is the content of my buffer');
var buffer2 = new Buffer(11); // Создаем пустой буфер размером в 11 символов.
var targetStart = 0;
var sourceStart = 8;
var sourceEnd = 19;
buffer1.copy(buffer2, targetStart, sourceStart, sourceEnd);
console.log(buffer2.toString()); // -> the content
Декодирование буфера из байтового содержимого в строку в формате UTF-8.
var str = buffer.toSrting();
var b64str = buffer.toString('base56'); // Если для содержимого буфера была определена определенная кодировка.
Через буфер можно конвертировать строки из одной кодировки в другую.
var utf8string = 'my string';
var buffer = new Buffer(utf8string);
var base64string = buffer.toString('base64');
//------------------------------------------------------------------
Event Emitters and Event Listeners
В Node.js помимо имеющихся вы можете создавать свои собственные излучатели событий.
Вы всегда должны прослушивать события типа Error чтобы избежать сбоя всей программы целиком.
Методы Event Emitter
.addListener и .on - добавить слушателя определенного типа событий (.on - это просто сокращенное название метода .addListener)
.once - добавить слушателя определенного типа событий, который будет удален после первого срабатывания события
.removeEventListener - удалить слушателя определенного события
.removeAllEventListeners - удалить все слулшатели опеределенного события
Примеры .addListener и .on
function recieveData(data) {
console.log('Data loaded');
}
readStream.addListener('data', recieveData);
readStrem.on('data', recieveData);
readStream.addListener('data', function (data) {console.log('Data loaded');});
К одному элементу можно привязывать множество слушателей событий
readStream.addListener('data', function (data) {console.log('Data loaded once');});
readStream.addListener('data', function (data) {console.log('Data loaded twice');});
События будут вызваны по порядку из регистрации (записи в коде).
Примеры .once
function recieveData(data) {
console.log('Data loaded');
}
readStrem.once('data', recieveData);
Примеры .removeEventListener
function recieveData(data) {
console.log('Data loaded');
}
readStream.addListener('data', recieveData);
readStream.removeEventListener('data', recieveData);
Примеры .removeAllEventListeners
readStream.addListener('data', function (data) {console.log('Data loaded once');});
readStream.addListener('data', function (data) {console.log('Data loaded twice');});
readStream.removeAllEventListeners('data');
Создание Event Emitter
var util = require('util');
var EventEmitter = require('events').EventEmitter;
// Собственный кастомный класс, наследующий все функции Event Emitter
var MyClass = function() {}
util.inherits(MyClass, EventEmitter);
MyClass.prototype.someMethod = function() {
this.emit("custom event", "argument 1", "argument 2");
};
Новый метод someMethod позволяет излучать события под названием "custom event". Данное событие передает данные в формате двух строк: "argument 1" и "argument 2".
Эти строки будут переданы в качестве аргументов в функции слушатели данного типа события eventListeners.
Пример работы нового излучателя событий.
var myEventEmitter = new MyClass();
myEventEmitter.on('custom event', function (str1, str2) {console.log('You got str1 %s and str2 %s', str1, str2);});
Рабочий пример класса излучателя событий Ring
var events = require('events');
var eventEmitter = new events.EventEmitter();
var ringBell = function () {
console.log('ring ring ring');
}
eventEmitter.on('doorOpen', ringBell);
eventEmitter.emit('doorOpen');
Вы можете создавать свои излучатели событий наследуя класс EventEmitter() и внутри своего класса далее просто использовать метод .emit()
var events = require('events');
var eventEmitter = new events.EventEmitter();
var ringBell = function () {
console.log('ring ring ring');
}
function MyEventEmitter () {
var self = this;
setInterval(function(){
self.emit('doorOpen');
}, 1000);
}
MyEventEmitter.prototype = eventEmitter;
var myEmitter = new MyEventEmitter();
myEmitter.on('doorOpen', ringBell);
//------------------------------------------------------------------
TIMEOUT и INTERVAL
Запуск функций внутри таймаута с нулевым интервалом позволяет запустить функция после того, как будут обрабдотаны все ивенты событий.
setTimeout(function(){doSomethingAfterAllEvents();}, 0);
Например это используется перед запуском анимации или для запуска какиъ-либо вычислений после того, как пользователь совершил все взаимодействия с интерфейсом.
В Node.js все события обрабатываются в цикле по очереди. Когда завершается очередной цикл обработки может быть вызвана функция tick перед началом следующего цикла обработки событий.
Для этих целий существует специальная функция proccess.nextTick(callbackFunction); которая является аналогом функции setTimeout(function(){callbackFunction();}, 0);
Используя функцию proccess.nextTick(callbackFunction); вместо setTimeout(function(){callbackFunction();}, 0); вы запускаете вашу колбак-функция сразу после того, как все ивенты в очереди будут обработаны.
Процесс выполнения функции происходит быстрее, чем при использовании функции setTimeout(function(){callbackFunction();}, 0);
JavaScript в Node.js как и в браузере выполняется в одном потоке. На каждом цикле выполнения потока следующее событие вызывает связанную с ним колбак-функцию.
Когда только эта функция будет выполнена запускается следующее событие и его функция. Так продолжается до тех пор пока все вызванные события в очереди не закончатся.
Если колбак-функция какого-либо события будет выполняться долго, то следующее событие не будет вызвано пока она не выполнится до конца. Это приведет к притормаживанию работы приложения в браузере или сервиса на сервере.
Вот хороший пример события которое заблокирует выполнение всех последующих событий и приведет к затормаживанию всей системы.
process.nextTick(function(){
var a = 0;
while (true) {
a++;
}
});
process.nextTick(function(){
console.log('next tick'); // Данное событие не сможет быть обработано, поскольку предыдущее событие не даст перейти на него, пока оно не завершится.
});
setTimeout(function(){console.log('timeout');}, 1000); // Данное событие не сможет быть обработано, поскольку первое событие не даст перейти на него, пока оно не завершится.
Поэтому выполнение больших расчетов в колюак-функциях событий может существенно замедлить всю программу.
Использование process.nextTick() полехно, если вы хотите отложить выполнение некритичных операций до следующего цткла обработки событий, особождая текущий цикл для обработки и выполнения более важных событий в программе.
Например вам нужно удалить временный файл, который вы создали ранее, но вам не нужно делать это до того, как сервер отослал информацию из него в браузер.
stream.on('data', function(){
stream.end('my response');
process.nextTick(function(){
fs.unlink('/path/to/file');
});
});
Используйте setTimeout() вместо setInterval() для ускорения серилизации данных.
Представьте, что вам нужно выполнять функцию, которая периодически осуществляет открытие и считывание данных из log-файла.
setInterval(function(){
myAsyncParseFile(function(){
console.log('parsing finished');
});
}, 1000);
Однако нужно быть уверенным, что выполнении нескольких таких функций не произойдет в одно и тоже время.
Но этого нельзя гарантировать, если вы используете функцию setInterval().
Для того, чтобы быть уверенным, что выполнение следующей функции не совпадет с выполнением предыдущей используется следующий код с функцией setTimeout внутри замыкания:
(function shedule (){
setTimeout(function(){
myAsyncParseFile(function(){
console.log('parsing finished');
shedule();
});
}, 1000);
})();
Здесь мы декларируем функцию-замыкание shedule(), которую сразу же запускаем.
Функция shedule запускает внутри себя функцию setTimeout().
Через 1 секунду функция setTimeout выполняет асинхронную функцию myAsyncParseFile.
В асинхронную функцию myAsyncParseFile передается колбак-функция, которая будет выполнена в конце успешног завершения работы асинхронной функции.
В заключении своего выполнения колбак-функция вызывает внешнюю функцию shedule(), которая запускает опять всю цепочку выполнения через 1 секунду.
Таким образом данный код имитирует поведение функции setInterval(), но при этом гарантирует, что выполнение асинхронного кода не наложится и не произойдет одновременно.
По-другому этот код можно записать так:
function shedule () {
setTimeout(function(){
myAsyncParseFile(function(){
console.log('parsing finished');
shedule(); // Рекурсивный вызов функции самой себя.
});
}, 1000);
}
shedule ();
//------------------------------------------------------------------
Работа с файлами
Node.js осуществляет работу с файлами асинхронно, как если бы это были сетевые интернет-потоки.
Как и Linux Node.js поддерживает Stendart Input, Standart Output и Standart Error.
Работа с путями до файлов.
Пути до файлов могут быть абсолютными и относительными относительно данного исполняемого файла.
Вы можете объединять пути до файлв, вырезать из них информацию о нахвании файла и определять существует ли файл на жестком диске.
Пути до файлов могут быть строками. Разделители в путях до файлов зависят от операционной системы, в которой работает Node.js (/ или \).
Однако в Node.js есть модуль path, который упрощает работу с путями до файлов.
Нормализация путей до файла - приведение пути до файла к одному формату.
var path = require('path');
path.normalize('/foo/bar/baz//asdf/quux/..'); // -> /foo/bar/baz/asdf
Объединени путей до файла
var path = require.path('path');
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); // -> /foo/bar/baz/asdf
Итоговое объединение путей до файла с выводом конечного пути
var path = require('path');
path.resolve('/foo/bar', './baz'); // -> /foo/bar/baz
path.resolve('/foo/bar', '/tmp/file/'); // -> /tmp/file
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); // Если путь указан не абсолютный и мы находимся в папке /home/myself/node, то метод вернет /home/myself/node/wwwroot/static_files/gif/image.gif'
Найти относительный путь между двух абсолютных путей
var path = require('path');
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); // -> ../../impl/bbb
Получение отдельных компонентов пути до файла
var path = require('path');
path.dirname('/foo/bar/baz/asdf/quux.txt'); // -> /foo/bar/baz/asdf (путь до файла)
path.basename('/foo/bar/baz/asdf/quux.txt'); // -> quux.txt (имя файла)
path.basename('/foo/bar/baz/asdf/quux.txt', '.txt'); // -> quux (имя файла без раширения)
path.extname('/a/b/index.html'); // -> '.html' (расширение файла)
path.extname('/a/b.c/index'); // -> '' (расширение файла)
path.extname('/a/b.c/.'); // -> '' (расширение файла)
path.extname('/a/b.c/d.'); // -> '.' (расширение файла)
Определение существование файла на жестком диске
var path = require('path');
path.exists('/etc/passwd', function(value){
console.log('file exists: ' + value); // true или false
});
В новой версии Node.js path.exists был замене на fs.exists
var fs = require('fs');
fs.exists('/etc/passwd', function(value){
console.log('file exists: ' + value); // true или false
});
Метод .exists() является асинхронным.
Для синхронного кода используйте метод path.existsSync()
var path = require('path');
path.existsSync('/etc/passwd'); // true или false
Работа с файлами через модуль fs
fs.open()
fs.read()
fs.write()
fs.close()
Получение статистических данных о файле, такие как размер файла, время создания, права доступа
var fs = require('fs');
fs.stat('/etc/passwd', function(err, stats){
if (err) {
throw err;
} else {
console.log(stats); // Все данные о файле в виде объекта {}
console.log(stats.isDirectory()); // true, если файл - это директория
console.log(stats.isBlockDevice()); // true, если файл - это устройство
console.log(stats.isCharacterDevice()); // true, если файл - это устройство типа character
console.log(stats.isSymbolicLink()); // true, если файл - это символическая ссылка
console.log(stats.isFifo()); // true, если файл - это именованная труба типа Firest In First Out
console.log(stats.isSocket()); // true, если файл - это сокет
}
});
Открытие файла для чтения его содержимого или записи
var fs = require('fs');
fs.open('/path/to/file', 'r', function(error, fd){
// got fd - file descriptor
});
Первый аргумент - это путь до файла.
Второй аргументы - это флаг режима в котором будут работать с файлом
r - открыть файл только для чтения. Поток установлен в начало файла.
r+ - открыть файл для чтения и записи. Поток установлен в начало файла.
w - открыть файл для записи (с чистого листа). Содержимое исходного файла удалить или создать новый пустой файл. Поток установлен на начало файла.
w+ - открыть файл для чтения и записи. Содержимое исходного файла удалить или создать новый пустой файл. Поток установлен на начало файла.
a - открыть файл для записи и дополнительной записи данных в файл. Содержимое исходного файла не удалять. Создать новый пустой файл, если исходного файла нет. Поток установаить на конец файла.
a+ - открыть файл для чтения, записи и дополнительной записи данных в файл. Содержимое исходного файла не удалять. Создать новый пустой файл, если исходного файла нет. Поток установаить на конец файла.
r - read, w - write (rewrite), a - append.
Чтение содержимого из файла
var fs = require('fs');
fs.read('./file.txt', 'r', function (error, fd) {
if (error) {throw error;}
var readBuffer = new Buffer(1024);
var bufferOffset = 0;
var bufferLength = readBuffer.length;
var filePosition = 100;
fs.read(fd,
readBuffer,
bufferOffset,
bufferLength,
filePosition,
function read (error, readBytes) { // Колбак-функция, которая будет выполнена после чтения данных из файла в буфер.
if (error) {throw error;}
console.log('just read ' + readBytes + ' bytes');
if (readBytes > 0) {
console.log(readBuffer.slice(0, readBytes));
}
}
);
});
Здесь мы открываем файл. Как только он будет открыт будет вызвана функция, которая считает 1024 байта его содержимого, начиная с позиции 100 в буфере.
Далее проверяется возможность возникновения ошибки и чтение данных из файла.
Запись данных в файл
var fs = require('fs');
fs.open('./file.txt', 'a', function(error, fd){
if (error){throw error;}
var writeBuffer = new Buffer('writing this string');
var bufferPosition = 0;
var bufferLength = writeBuffe.length;
var filePosition = null; // запись будет в текущую позицию курсора в файле.
fs.write(fd,
writeBuffer,
bufferPosition,
bufferLength,
filePosition,
function wrote(error, written) { // Колбак-функция, которая будет выполнена после записи данных в файла из буфера.
if (error) {throw error;}
console.log('wrote ' + written + ' bytes');
}
);
});
Закрытия файла после взаимодействия с ним.
После того, как вы поработали с файлом его обязательно необходимо закрыть.
fs.close(fd[,callback]);
Пример.
var fs = require('fs');
function openAndWriteToSystemLog(writeBuffer, callback) {
fs.open('./my_file', 'a', function opened(err, fd) {
if (err) { return callback(err); }
function notifyError(err) {
fs.close(fd, function() { // Закрытие файла после завершения работы с ним.
callback(err);
});
}
var bufferOffset = 0,
bufferLength = writeBuffer.length,
filePosition = null;
fs.write(fd,
writeBuffer,
bufferOffset,
bufferLength,
filePosition,
function wrote(err, written) {
if (err) { return notifyError(err); }
fs.close(fd, function() {
callback(err);
});
}
);
});
}
openAndWriteToSystemLog(new Buffer('writing this string'), function done(err) {
if (err) {
console.log("error while opening and writing:", err.message);
return;
}
console.log('All done with no errors');
});
//------------------------------------------------------------------
Запуск дочерних процессов и команд для консоли командной строки
Запуск внешней команды через командную строку или запуск через командную строку внешнего исполняемого файла.
var child_process = require('child_process');
var exec = child_process.exec;
exec(command, callback);
command - это строка - команда для консоли.
callback - это функция, которая будет вызвана, если консольная команда будет выполнена или возникнет ошибка.
Функция callback должна принимать следующие аргументы error, stdout, stderror.
Пример.
var child_process = require('child_process');
var exec = child_process.exec();
exec('ls', function (error, stdout, stderror){
... some code ...
});
error - содержит объект класса Error.
stdout - содержит вывод из консоли.
stderror - содержит вывод ошибки из консоли.
var child_process = require('child_process');
var exec = child_process.exec;
exec('cat *.js | wc -l', function (error, stdout, stderror){
if (error) {
console.log('child process exited with error code: ' + error.code);
return;
} else {
console.log(stdout);
}
});
Также метод exec() может принимать набор опций options, которые будут использованы перед выполнение колбак-функции.
var exec = require('child_process').exec;
var optoons = {
timeout: 10000,
killSignal: 'SIGKILL'
};
exec('cat *.js | wc -l', options, function (error, stdout, stderror){
... some code ...
});
Доступные варианты содержимого options
cwd - current working directory
encoding - кодировка вывода информации в дочернем процессе. По умолчанию используется кодировка utf8. Node.js поддерживает вывод информации в кодировках: ascii, utf8, ucs2, base64.
timeout - время ожидания выполнения консольной команды. Задается в миллисекундах. По умолчанию рано 0, что означает - ждать бесконечно завершения дочерненго процесса.
maxBuffer - максимальный размер в байтах допустимых для вывода информации через потоки stdout и stderror. Если размер буфера будет исчерпан, то дочерний процесс будет убит. По умолчанию рано 200 * 1024.
killSignal - сингнал, посылаемый дочернему процессу, если время ожидания timeout истекло или буфер maxBuffer переполнен. По умолчанию равно SIGTERM.
env - переменные среды операционной системы будут переданы в дочерний процесс. По умолчанию равно null, что означает, что дочерний прочцесс наследует все родительские переменные среды операционной системы, которые были определены перед вызовом дочернего процесса.
Пример запуска стороннего файла
var exec = require('child_process').exec;
var options = {
env: {number: 123}
};
exec('node child.js', options, function (error, stdout, stderror) {
if (error) {throw error;}
console.log('stdout: ' + stdout);
console.log('stderror: ' + stderror);
});
Запуск множества дочерних процессов.
Использование функции exec() не позволяет взаимодействовать с дочерним процессом.
Вывод output дочернего процесса буферизируется, поэтому вы не можете передать его в потоке и он моэет занять много памяти.
Но вы можете в дочернем процессе создать свой дочерний процесс, который вызовет родительский процесс и черзе него осуществлять комуникацию двух процессов через посылку и получение строк.
Вы можете создать новый дочерний процесс используя функцию child_process.spawn()
var spawn = require('child_process').spawn;
var child = spawn('tail', ['-f', '/var/log/system.log']); // команда "tail -f /var/log/system.log"
Прослушивание данных в дочернем процессе.
Для каждой передачи данных из дочернего вывода output можно вызвать колюбак-функцию.
child.stdout.on('data', function(data){
console.log('tail output: ' + data);
});
Каждый раз, когда дочерний процесс будет выводить данные в standart output, родительский процесс получит сообщение вы выведет эти данные в консоль.
То же самое аналогично можно стедать и для standart error.
child.stderr.on('data', function(data) {
console.log('tail error output: ', data);
});
Пересылка данных в дочерний процесс.
Помимо получения данных из дочернего процесса родительский процесс может передавать данные в дочерний процесс через запись значений в standart input stream дочернего процесса посредством свойства childProcess.stdin
Дочерний процесс может прослушивать передачу данных через функцию process.stdin
Но вы должны сделать ему resume, так как по умолчанию он стоит на паузе.
Получение сообщения о том, что дочерний процесс вышел (завершился).
Когда дочерний процесс выходит (завершается), то родительский процесс получает уведомление об этом событии.
var spawn = require('child_process').spawn;
var child = spawn('ls', ['-la']);
child.stdout.on('data', function (data) {
console.log('data from child: ' + data);
});
child.on('exit', function(code){
console.log('child process terminated with code: ' + code);
});
Другой пример.
var spawn = require('child_process').spawn;
var child = spawn('sleep', ['10']);
setTimeout(function() {
child.kill();
}, 1000);
child.on('exit', function(code, signal) {
if (code) {
console.log('child process terminated with code ' + code);
} else if (signal) {
console.log('child process terminated because of signal ' + signal);
}
});
Передача сигналов в процесс и убийство дочерних процессов
Сигналы - наиболее простой путь для родительского процесса общаться с дочерними процессами и их удалением.
Если дочерний процесс получит сигнал, который не будет знать как обработать, то он убъется.
Вы можете убить дочерний процесс с помощью метода child.kill
var spawn = require('child_process').spawn;
var child = spawn('sleep', ['10']);
setTimeout(function(){
child.kill();
}, 1000);
Но вы можете и переопределить значение сигнала передаваемого в функции kill().
child.kill('SIGUSR2');
process.on('SIGUSR2', function() {
console.log('Got a SIGUSR2 signal');
});
//------------------------------------------------------------------
Потоки Stream
Node.js имеет удобную абстракцию stream.
Пример потока stream - это TCP socket из которого вы читаете и записываете выходные данные или файл, в который вы записываете или из которого вы выводите данные.
Потоки могут пересылать данные по частям. Потоки можно ставитьна паузу м возобновлять. Вы может прослушивать события когда пооток доставляет очередную порцию данных.
Поток может передавать данные в форме байтов или строки в указанном формате кодировки.
Потоки могут быть readable и writable.
var readable stream1 = ...;
readable stream1.on('data', function(data){ // Данные передаются через буфер байтов по умолчанию, так как мы не определили кодировку.
console.log('got this data: ' + data);
});
var readable stream2 = ...;
readable stream2.setEncoding('utf8');
readable stream2.on('data', function(data){ // Данные передаются в формате UTF-8 строки, поскольку мы определили кодировку.
console.log('got this data: ' + data);
});
Остановка потока на паузу
Поступающие данные из readable stream можно поставитьна паузу
stream.pause();
После этого вы не будете больше получать данные. Например, если вы читаете в потоке данные из файла, то Node.js остановит чтение файла.
Если поток идет через TCP socket, то Node.js остановится читать новые пакеты данных, что остановит отсылку пакетов на другом конце.
Если вы захотите продолжить получать данные, то просто снимите поток с паузы
stream.resume();
Передача данных через поток может завершиться, например, когда закончится чтение файла. Когда поток завершится, то сработает событие потока 'end', которое вы можете прослушивать.
var readable stream = ...;
readable stream.on('end', function(){
console.log('the stream has ended');
});
Использование writable stream
writable stream позволяет пересылать данные. Вы можете пересылать данные в файл или через TCP network connection.
Запись данных в поток
Вы можетете пересылать в потоке буфер байтов или строки.
var writable stream = ...;
writable stream.write('this is an UTF-8 string');
По умолчанию используется кодировка UTF-8, но вы можете вторым аргументом указать в функции другую кодировку.
var writable_stream = ...;
writable_stream.write('7e3e4acde5ad240a8ef5e731e644fbd1', 'base64');
Так же вы можете передать в поток буфер байтов
var writable stream = ...;
var buffer = new Buffer('this is a buffer with some string');
writable stream.write(buffer);
Как только вы запишите данные в поток Node.js немедленно очистит буфер или очистит его на следующем шаге цикла.
Функция .write() возвращает true, когда буфер очищен и false, когда буфер поставлен в очередь на очищение.
Если буфер будет поставлен в очередь на очищение, то вы может прослушивать событие 'drain', чтобы узнать когда буфер будет очищен.
var writable stream = ...;
writable stream.on('drain', funciton (){
console.log('drain emitted');
});
Создание потоков чтения-записи файлов на жестком диске.
Вы можете создать поток чтения из файла
var fs = require('fs');
var rs = fs.createReadStream('/path/to/file', options);
Вторым аргументом вы можете передать следующие опции:
encoding - кодировка строки передаваемой в data при возникновении событий или null, если вы передаете значения в байтах
fd - file descriptor, если вы имеете уже открытый файл. По умолчанию равно null.
byfferSize - размер в байтов каждой части передаваемых данных при чтении файла. По умолчанию равно 64KB.
start - позиция в байтах с которой будет прочитан файл. Используется для чтения части файла вместо всего файла.
end - позиция в байтах до которой будет прочита файл. Используется для чтения части файла вместо всего файла.
Если вы уже имеете открытый файл, то можете создать поток чтания для него вот так
var fs = require('fs');
var path = '/path/to/file';
fs.open(path, 'r', function(error, fd){
fs.createReadableStream(null, {fd: fd, encoding: 'urf8', start: 10});
fs.on('data', function(data){console.log(data);});
});
Вы можете создать поток записи в файл
var fs = require('fs');
var ws = fs.createWriteStream('/path/to/file', options);
Вторым аргументом вы можете передать следующие опции:
flags - флаги r, r+, w, w+, a, a+
encoding - кодировка строки передаваемой в data при возникновении событий или null, если вы передаете значения в байтах
mode - установка значений прав доступа к чтению, записи, выполнению, если файл был создан с нуля.
Пример
var options = {
flags: 'w',
encoding: null,
mode 0666
};
Сеттевые потоки Network stream
TCP соединени является одновременно и потоком чтения, и потоком записи.
HTTP request - это поток чтения.
HTTP response - это поток записа.
Эти 3 потока имеют общий интерфейс.
Node.js не блокируется, когда делает операции ввода-вывода информации через потоки чтения-записи в файлы или сетевые потоки request-response.
Если передача данных замедляется, то Node.js записывает данные в буфер. В этом случае память может переполниться.
Чтобы избежать этого необходимо поставить поток чтения данных на паузу.
require('http').createServer(function(request, response){
var rs = fs.createReadStream('/path/to/very/big/file');
rs.on('data', function(data) {
var writeResult = response.write(data);
if (writeResult === false) {
rs.pause();
}
});
rs.on('drain', function(){
rs.resume();
});
re.on('end'm function(){
response.end();
});
}).listen(8080);
Такой паттерн управления потоками при медленном интернет-соединении реализован в функции stream.pipe()
Вот тот же пример с использованием функции pipe()
require('http').createServer(function(request, response){
var rs = fs.createReadStream('/path/to/very/big/file');
rs.pipe(response);
}).listen(8080);
Если необходимо, чтобы передача данных от сервера к браузеру не завершалась после того, как файл будет полностью отправлен в качестве второго аргумента можно добавитьопцию end: false
require('http').createSrever(function(request, response){
var rs = fs.createReadStream('/path/to/very/big/file');
rs.pipe(response, {end: false});
rs.on('end', function(){
response.write('That is all');
response.end();
});
}).listen(8080);
//------------------------------------------------------------------
Создание TCP server
Вы можете создать сервер, используя модуль net
require('net').createServer(function(socket){
// new connection
socket.on('data', function(data){
// got data
});
socket.on('end', function(data){
// connection closed
});
socket.write('some string');
});
}).listen(8080);
Сервер может прослушивать и излучать следующие события
listening - когда сервер слушает определенный порт и адрес
connection - когда устанваливается новое соединение. Функция колбак получает соотвествующий объект socket.
close - когда сервер закрывает соединение и не связан больше ни с одним портом
error - когда на уровне сервера происходит ошибка, например когда вы пытаетесь занять уже заняты порт.
Данный пример показывает жизненный цикл сервера.
var server = require('net').createServer();
var port = 8080;
server.on('listening', function(){
console.log('Server is listening on port: ' + port);
});
server.on('connsection', function(socket) {
console.log('Server has a new connection');
socket.end();
server.close();
});
server.on('close', function (){
console.log('Server is now closed');
});
server.on('error', function(error){
console.log('Error occured: ' + error.message);
});
server.listen(port);
Результат
// Server is listening on port 8080
// Server has a new connection
// Server is now closed
Объект socket - это поток чтения и записи. Это значит, что он излучает событие 'data', когда получает данные и излучает событие 'end', когда соединение закрывается.
Через метод socket.write() вы можете записывать буфер байтов или строки.
Вы можете завершить передачу данных вызвав метод socket.end()
Пример.
var server = require('net').createServer(function(socket){
console.log('new connection');
socket.setEncoding('utf8');
socket.write('Hello');
socket.on('data', function(data){
console.log('got: ' + data.toString());
if (data.trim().toLowerCase() === 'quit') {
socket.write('Bye!');
socket.end();
return;
}
socket.write(data);
});
socket.on('end', function(){
console.log('Client connection ended');
});
}).listen(8080);
Поскольку socket это также поток чтения, то вы можете ставить его на паузу socket.pause() и возобновлять sokcet.resume()
var ws = require('fs').createWriteStream('mysocketdump.txt');
require('net').createServer(function(socket){
socket.pipe(ws);
}).listen(8080);
Другой пример
require('net').createServer(function(socket){
var rs = require('fs').createReadStream('hello.txt');
rs.pipe(socket);
}).listen(8080);
Соединения через сокет разрываются, когда клиент или сервер разрывают соединения.
Но, если соединения не разорвано, то оно может поддерживаться вечно.
Поэтому, если данные не передаются значительное время, то выможете разрывать соеденение по таймауту.
Пример.
socket.setTimeout(1000 * 60); // 1 минута
socket.on('timeout', function(){
socket.write('Time is out. Disconnecting!');
socket.end(); // Разрыв соединения
});
Или можно тоже самое записать короче
socket.setTimeout(1000 * 60, function () {
socket.end('Time is out. Disconnecting!');
});
Node.js может удерживать соединение браузера с сервером, пересылая пакеты с пустыми данными.
Для включения этого режима используется функция
socket.setKeepAlive(true);
Вы можете также указать временную задержку между пересылками пустых пакетов для поддержания соединения.
socket.setKeepAlive(true, 1000 * 10); // 10 секунд
Node.js записывает данные в буфер перед отправкой их по TCP соединению.
Если вы хотите, чтобы данные отсылались немедленно после каждой функции socket.write(), то выполните эту команду
socket.setNoDelay(true);
Для отмены этой команды напишите
socket.setNoDelay(false);
Прослушивание порта и адресу, по которому браузер будет соединяться с сервером.
var port = 8080;
var host = '0.0.0.0';
server.listen(port, host); // Второй аргумент host передавать необязательно.
Закрытие сервера.
Закрытие сервера запрещает принимать ему новые соединения. Эта функция асинхронная и сервер излучает событие 'close', когда закрывается.
var server = ...;
server.close();
server.on('close', function(){
console.log('server closed');
});
Обработка ошибок, возникающих на сервере.
require('net').createServer(function(socket){
socket.on('error', function(error){
// do something
});
});
Если вы не обработаете ошибку, то Node.js вызовет исключение и убъет текущий процесс!!!
Вы моэете перехватывать все неперехваченные исключения так
process.on('uncaughtException', function(error){
// do something
});
Но так ошибки лучше не обрабатывать.
//------------------------------------------------------------------
Создание простого TCP chat server
var net = require('net');
var server = net.createServer();
var clients_sockets = [];
server.on('connection', function(socket){
console.log('Got new connection');
clients_sockets.push(socket);
socket.on('data', function(data){
console.log('Got data: ' + data);
clients_sockets.forEach(function(otherSocket){
if (otherSocket !== socket) {
otherSocker.wtrite(data);
}
});
});
socket.on('close', function(){
console.log('Socket connection closed');
var index = clients_sockets.indexOf(socket);
client_sockets.splice(index, 1);
});
});
server.on('error', function(error){
console.log('Server error: ' + error.message);
});
server.on('close', function(){
console.log('Server closed');
});
server.listen(8080);
//------------------------------------------------------------------
Создание HTTP server
var http = require('http');
var server = http.createServer();
server.on('request', function(request, response){
response.writeHead(200, {'Contetn-Type': 'text/plain'});
response.write('Hello, World!');
response.end();
});
server.listent(8080);
Соединение с вервером происходит через браузер по адресу http://localhost:8080
Данный пример можно записать короче
require('http').createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write('Hello, World!');
response.end();
}).listen(8080);
Функция .end() может принимать строку или буфер байтов, который будет отослан в браузер перед закрытием соединения с ним.
Методы объекта request, передаваемые серверу при установке соединения.
request.url - строка, содержащая URL, по которому перешел пользователь. Эта строка не содержит shema, hostname и порт, но содержит все остальное.
request.method - содержит метод, который использовался в запросе: HEAD, GET, POST, DELETE.
request.headers - содержит заголовки переданные брайзером серверу.
Посмотреть переданные данные на странице можно так
require('http').createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end(request.url);
}).listent(8080);
var util = require('util'); // Анализирует свойства объектов
require('http').createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end(util.inspect(request.headers)); // Ключи заголовков выводятся в формате lowerCase, а значения не изменяются!!!
}).listent(8080);
Для отправки ответа в браузер вы можете использовать write stream
var writeStream = ...;
require('http').createServer(function(request, response){
request.on('data', function(data){
writeStream.write(data);
});
}).listen(8080);
Запись нескольких заголовков для отправки от сервера к браузеру
require('http').createServer(function(request, response){
response.writeHead(200, {
'Content-Type': 'text/plain',
'Cache-Control': 'max-age=3600'
});
response.end('Hello, World!');
}).listen(8080);
Вы можете изменить заголовок, который уже установили или установить новый заголовок с помощью функции setHeader()
response.setHeader(name, value);
но это сработает только, если вы еще не отослали чать body, используя метод response.write() или response.end()
Это также не сработает, если вы уже написали response.writeHead() (из-за того, что заголовки на данном этапе уже будут отправлены).
Вы можете удалить уже установленные заголовки, используя метод response.removeHeader()
response.removeHeader('Cache-Control');
но это сработает только, если заголовки еще не отправлены через метод response.writeHead(), response.write() или response.end()
Сервер отправляет часть body после того, как он отправляет заголовки.
Часть body отправляется сразу после выполнения функции response.write()
response.write('Hello');
через write также можно отправлять буфер байтов
var buffer = new Buffer('Hello, World!');
response.write(buffer);
Node.js позволяет отправлять данные от сервера к браузеру потоком, разбивая данные по частям. Пока в заголовке не указан Content-Length сервер отсылает следующий заголовок браузеру
Transfer-Encoding: chuncked
Сервер будет передавать в браузер данные до тех пор пока не пошлет часть данных длиной 0. В этом случае клиент закроет соединение с сервером.
Передача данных по частям удобна для стриминга текст, аудио и видео в браузер.
Пример стриминга данных из файла в браузер.
var fs = require('fs');
require('http').createServer(function(requrest, response){
response.writeHead(200, {'Content-Type': 'video/mp4'});
var rs = fs.createReadStream('/video/test.mp4');
rs.pipe(response); // Передать по частям содержимое видеофайла в браузер
}).listen(8080);
При переходе в браузере по адресу http://127.0.0.1:8080 видео начнет сразу проигрываться несмотря на то, что оно еще не загружено полностью.
Вывод в браузер результата выполнения другого процесса
var spawn = require('child_process').spawn;
require('http').createServer(function(request, response){
var child = spawn('tail', ['-f', '/var/log/system.log']);
child.stdout.pipe(response);
response.on('end', function(){
child.kill();
});
}).listen(8080);
Выключение сервера
Вы можете остановить прослушивание сервером новых подключений к нему со стороны браузера просто отсоединив его от порта так
server.stop();
Если вы захотите, чтобы сервер стал прослушивать подключения по порту снова, то простое выполните следующий код
server.listen(port[, hostname]);
Для создания простых серверов вам не потребуются дополнительные модули.
Пример сервера, обслуживающего выдачу статитчных файлов.
var path = require('path');
var fs = require('fs');
require('http').createServer(function(request, response){
var file = path.normalize('.' + request.url);
console.log('Trying to serve: ' + file);
function reportError(error) {
console.log(error);
response.writeHead(500);
response.end('Internal Server Error');
}
path.exists(file, function(exists){
if (exists) {
fs.stat(file, function (error, stat){
var rs;
if (error) {
reportError(error);
return;
}
if (stat.isDirectory()) {
response.writeHead(403);
response.end('Forbidden');
} else {
rs = fs.createReadStream(file);
rs.on('error', reportError);
response.writeHead(200);
rs.pipe(response);
}
});
} else {
response.writeHead(404);
response.end('Not found');
}
});
}).listen(8080);
При переходе в браузеру по адресу http://localhost:8080/path/to/my/file.txt вы загрузите содержимое этого файла на страницу.
Пример сервера, отправляющего данные по частям по таймеру
Данный сервер будет отправлять текст состоящий из 100 строк со временными метаками каждую секунду.
require('http').createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
var left = 10;
var interval = setInterval(function(){
for (var i = 0, i < 100; i++) {
response.write((new Date()).toString());
}
if (--left === 0) {
clearInterval(interval);
response.end();
}
}, 1000);
}).listen(8080);
//------------------------------------------------------------------
Создание TCP client
По TCP вы можете получать данные по readable stream и отправлять данные по writable stream.
TCP устанваливает двунаправленное соединение клиента с сервером и гарантирует правильный парядок передачи пакетов с данными по сети.
Соединение клиента с сервером.
var net = require('net');
var port = 8080;
var connection = net.createConnections(port); // address = 'localhost'
или так
var net = require('net');
var port = 8080;
var host = 'www.acme.com';
var connection = net.createConnection(port, host);
Вы можете определять когда установлено соединение, передавая в качестве третьего аргумента функцию колбак.
var net = require('net');
var port = 8080;
var host = 'www.acme.com';
function connectionListener(connection) {
console.log('We have a new connection.');
}
var connection = net.createConnection(port, host, connectionListener);
Если вы подсоединяетесь к localhost, то второй аргумент можно пропустить
var connection = net.createConnection(port, connectionListener);
Также вместо этого вы можете слушать событие 'connect' излучаемого объектом connect для определения установления соединения с сервером.
connection.once('connect', connectionListener);
Поскольку устанавливается потоковое соединение, то вы можете отправлять и получать данные через соединение.
connection.write('here is a string for you');
connection.write('SGVsbG8gV29ybGQh', 'base64'); // Передача строки в определенной кодировке.
var buffer = new Buffer('here is a string for you');
connection.write(buffer); // Передача строки в виде буфера байтов.
Вы также можете передавать колбак функцию, котороя будет выполнена сразу, как только данные будут отправлены из клиента на сервер.
connection.write('Hey', function(){console.log('data sent');});
Вы можете получать данные от сервера, прослушивая событие 'data', излучаемое объектом connection каждый раз, как данные будут получены.
connection.on('data', function(data){
console.log('some data has arrived: ' + data);
});
Если вы не прописали кодировку потока, то данные от сервера будут получены в виде буфера байтов.
Поэтому, если вы хотите, чтобы буфер был переведен в определенную кодировку перед излучением, то пропишите setEncoding()
connection.setEncoding('base64');
Вы можете прописать следующие типы кодировок:
ascii
urf8
base64
Вы можете закрыть соединение со стороны клиента, используя метод .end()
connection.end();
Это закроет соединение после того, как все данные на сервер будут отправлены.
Во время закрытия соединения с сервером вы можете послать прощальное сообщение в виде буфера байтов или строки
connection.end('Bye bye!', 'utf8');
Эта строка будет эквивалентна этому
connection.write('Bye bye!', 'utf8');
connection.end();
Поскольку соединение не ращрывается мгновенно, то вы можете получить данные от сервера, даже после того, как закрыли соединение с ним.
При установке и во время работы соединения могут произойти различные ошибки. Для обработки этих ошибок вы должны прослушивать событие 'error' объекта connection
connection.on('error', function(error){
console.log('this is a error: ' + error.message + ' code: ' + error.code);
});
Помимо meassage объект error также имеет свойство code, значение которого является строкой вида 'ECONNREFUSED'
Создание примера клиента-командной строки, соединяющегося с сервером
Сперва создадим сервер, к которому будет подсоединяться наш клиент. Как это сделать смотри выше.
Теперь создадим клиент, который будет подсоединяться к запущенному серверу.
var net = require('net');
var port = '8080';
var connection = net.createConnection(port);
connection.on('connect', function(){
console.log('connected to server');
});
connection.on('error', function(error){
console.log('Error in connection: ' + error);
});
Отправку данных на сервер из клиента вы можете осуществлять через readable stream:
process.stdin.resume(); // Однако перед началом вы должны снять поток с паузы.
Теперь вам доступны readable stream и writable stream для получения и отсылки данных. Вы можете связать 2 потока, используя метод .pipe()
process.stdin.pipe(connection);
Теперь как только поток process.stdin будет очищен данные будут оправлены на сервер через writable stream соединения connection.
Вы можете выводить в консоль высе данные, присланные сервером. Проще всего сделать это с помощью метода pipe()
connection.pipe(process.stdout);
Однако тут есть проблема, которая заключается в том, что если соединение с сервером закроется, то и standart output stream тоже закроется.
Избежать этого поможет опция {end: false} метода pipe()
connection.pipe(process.stdout, {end: false}); // теперь поток вывода не закроется после закрытия соединения с сервером
Автоматическое повторное соединение клиента с сервером, если соединение разорвалось.
var net = require('net');
var port = 8080;
var connection;
process.stdin.resume();
(function connect(){
connection = net.createConnection();
connection.on('connect', function(){
console.log('connected to server');
});
connection.on('error', function(error){
console.log('Error in connection: ' + error);
});
connection.on('close', function(){
console.log('Connection closed. Will try to reconnect.');
connect(); // Запуск внешней функци connect, в которую обернут весь код, чтобы процесс соединения начался с начала
});
connection.pipe(process.stdout, {end: false});
process.stdin.pipe(connection);
})();
После отсоединения сервер будет недоступен несколько миллисекунд, поэтому крайне нежелеательно стараться мгновенно подключиться повторно после разрыва соединения.
Опять же количество попыток повторного соединения тоже стоит ограничить.
var net = require('net');
var port = 8080;
var connection;
var retryInterval = 3000; // 3 секунды
var retriedTimes = 0;
var maxRetries = 10;
process.stdin.resume();
(function connect(){
function reconnect(){
if (retriedTimes >= maxRetries) {
throw new Error('Max retries have been exceeded');
}
retriedTimes++;
setTimeout(connect, retryInteval);
}
connection = net.createConnection(port);
connection.on('connect', function(){
console.log('connected to server');
retriedTimes = 0;
});
connection.on('error', function(error){
console.log('Error in connection: ' + error);
});
connection.on('close', function(){
console.log('connection closed. will try to reconnect');
reconnect(); // запуск функции reconnect, описанной выше
});
process.stdin.pipe(connection, {end: false});
})();
Для закрытия соединения с сервером вызовите метод .end()
connection.end();
Если пользователь наберет в консоли слово 'quit', то вы можете разорвать соединения клиента с сервером, использовав метод .end()
process.stdin.on('data', function(data){
if (data.toString().trim().toLowerCase() === 'quit') {
connection.end();
process.stdin.pause();
}
});
Обратите внимание, что поток ввода команд в консоли вы также ставите на паузу, поскольку больше не нуждаетесь в получении данных из него.
но жто не будет работать правильно, поскольку вы все еще будете пытаться отправить данные на сервер, включая слово 'quit'.
Поэтому вы попытаетесь отправить данные после закрытия соединения.
Чтобы этого не произошло вам надо отменить команду process.stdin.pipe(connection);
Поэтому вы перепишите метрд отсылки данных так, чтобы он остылал данные только, если не набрана команда quit
process.stdin.on('data', funciton (data){
if (data.toString().trim().toLowerCase() === 'quit') {
console.log('quiting...');
connection.end();
process.stdin.pause();
} else {
connection.write(data);
}
});
Если соединение разорвется, то сценарий попытается переконектится. Поэтому надо установить флаг, что пользователь вышел сам и не возобновлять соединение.
var quiting = false;
// ... some connetion code ...
process.stdin.on('data', function(data){
if (data.toString().trim().toLowercase() === 'quit') {
quiting = true;
console.log('quiting...');
connection.end();
process.stdin.pause();
} else {
console.log(data);
}
// ... some connetion code ...
connection.on('close', function(){
if (!quiting) {
console.log('connection closed. will try to reconnect');
reconnect(); // запуск функции reconnect, описанной выше
}
});
});
Пример полного кода соединения клинета с сервером, восстановления разорванного соединения и разрыва соединения по желанию пользователя
var net = require('net');
var port = 4000;
var conn;
var retryTimeout = 3000; // 3 секунды
var retriedTimes = 0;
var maxRetries = 10;
var quitting = false;
process.stdin.resume();
process.stdin.on('data', function(data) {
if (data.toString().trim().toLowerCase() === 'quit') {
quitting = true;
console.log('quitting...');
conn.end();
process.stdin.pause();
} else {
conn.write(data);
}
});
(function connect() {
function reconnect() {
if (retriedTimes >= maxRetries) {
throw new Error('Max retries have been exceeded, I give up.');
}
retriedTimes += 1;
setTimeout(connect, retryTimeout);
}
conn = net.createConnection(port);
conn.on('connect', function() {
retriedTimes = 0;
console.log('connected to server');
});
conn.on('error', function(err) {
console.log('Error in connection:', err);
});
conn.on('close', function() {
if (!quitting) {
console.log('connection got closed, will try to reconnect');
reconnect();
}
});
conn.pipe(process.stdout, {end: false});
}());
//------------------------------------------------------------------
Создание HTTP request
Запросы request на сервер осуществляются с помощью URL-адреса и метода HEAD, GET, POST, PUT, DELETE.
Создание GET-запроса
Получаем содержимое главной страницы Google
var http = require('http');
var options = {
host: 'www.google.com',
port: 80,
path: '/index.html'
};
http.get(options, funciton(response){
console.log('Got response: ' + resposen.statusCode);
});
Метод http.get() это сокращенная версия метода http.request()
Метод http.request() принимает следующие опции
host - hostname сайта или IP-адрес сервера, на который будет оправлени запрос на получение данных от сервера.
port - порт сервера
method - метод оправки запроса на сервера: HEAD, GET, POST, PUT, DELETE. По умолчанию равен GET.
path - путь до страницы, включая search query string вида /index.html?page=2
headers - заголовки, которые будут оправлены на сервер. Пример {'Accept': 'text/json', 'If-Modified-Sincce': 'Sat, 28 Jan 2012 00:00:52 GMT'}
Метод http.request() возвращает объект http.ClientRequest, котороый является потоком wriatable stream. Вы можете использовать этот поток для отправки данных на сервер.
Когда вы завершите передачу данный, выполните end stream и удалите объект request.
Пример отправки данных на сервер.
var http = require('http');
var options = {
host: 'www.google.com',
port: 80,
path: '/upload',
method: 'POST'
};
var request = http.request(options, function(response){
console.log('Status: ' + response.statusCode);
console.log('Headers: ' + response.headers);
response.setEncoding('utf8');
response.on('data', function(chunk) {
console.log('Body: ' + chunk);
});
});
request.write('This is a piece of data.');
request.write('This is another piece of data.');
requres.end();
Если вы не напишите request.end(), то удаленный сервер не сможет определить, что отправка данныъ уже завершена и не ответит клиенту.
Вы можете прослушивать ответы сервера с помощью привязки к событию 'response'
function responseHandler(response){
console.log('I got a response: ' + response);
}
request.on('response', responseHandler);
Вы также можете подставит колбак функцию в качестве второго аргумента в метод http.request(), которая будет вызвана, когда сервер пришлет ответ response клиенту.
Ответ сервера будет хранится в объекте response.
Объект ответа сервера response имеет следующие свойства:
response.statusCode - номер статус-кода ответа сервера
response.httpVersion - HTTP версия серверного протокола вида 1.1 или 1.0
response.headers - заголовки, присланные сервером в формате lowercase для ключей. Пример
{
date: 'Wed, 01 Feb 2012 16:47:51 GMT',
expires: '-1',
'cache-control': 'private, max-age=0',
'content-type': 'text/html; charset=ISO-8859-1',
'set-cookie': [ 'NID=56=rNJVRav-ZW1Sd4f9NPsjLhaybMSzTOfbMPNHCqLeYXwKAvs4_f...],
p3p: 'CP="This is not a P3P policy! See http://www.google.com/support/accou...',
server: 'gws',
'x-xss-protection': '1; mode=block',
'x-frame-options': 'SAMEORIGIN',
connection: 'close'
}
Получение тела response ответа
Тело ответа не представлено, котгда request запускает событие 'response'. Если вам надо получить тело response ответа, то вам надо зарегистрировать слушателя события 'data'.
http.request(options, funciton(response){
response.setEncoding('utf8');
response.on('data', funciton(){ // Данная функция запустится, как только будет получени ответ от сервера.
console.log('I have a piece of response body here: ' + data); // Ответ от сервера может быть в формате буфера байтов или в формате строки с определенной кодировкой.
});
});
Потоковый вывод ответа от сервера в клиент - Streaming response body
Http response - это readable stream, который представляет из себя response body data stream.
Этот readable stream вы можете направить с помощью метода pipe() во writable stream в файл или далее в другой HTTP request.
Пример отправки потока, идущего в ответе сервера, для записи в файл.
var http = require('http');
var options = {
host: 'www.google.com',
port: 80,
path: '/',
method: 'GET'
};
var fs = require('fs');
var file = fs.createWritableStream('/tmp/test.txt');
http.request(options, funciton(response){
response.pipe(file);
}).end();
Агент по управлению пулом отправки запросов на сервер через сокеты.
Когда выполняются HTTP запросы Node.js использует agetn. Agentделает HTTP запросы завас.
Он отвечает за управление пулом TCP сокетов.
Когда происходит новое соединение agent поддерживает его живым. Когда соединение завершается socket освобождается и закрывается.
Это значит что вам не неужно вручную заурывать HTTP client.
Когда вы делаете новый запрос и доступный сокет выбирается то http.ClientRequrest излуает событие socket event.
После того, как запрос выполнится сокет удалится из пула. В этом случае сокет излучит событие close или agentRemove.
Если вы хотите сохранить HTTP request открытым долгое время вы можете удалить его из пула так
function handleResponseCallback (response){
console.log('Got response: ' + response);
}
var request = http.request(options, handleResponseCallback);
request.on('socket', function(socket){
socket.emit('agentRemove');
});
Node.js позволяет иметль максимум 5 открытых сокетов на пару хост-порт для одного процесса.
Это значит, что под большой нагрузкой запросо Node.js будет серилизовать запросы на тот-же хост-порт для повторного использования сокетов.
Если такой процесс неоптимален для вашей задачи, то вы можете отключить agent через передачу опции agent: false
var options = {
host: "my.server.com",
port: 80,
path: "/upload",
method: "POST",
agent: false
};
http.request(options, handleResponseCallback);
Вы также можете измениььмаксимальное количество открытых сокетов в пуле сокетов для одной пару хост-порт изменив http.Agent.defaultMaxSockets
var http = require('http');
http.Agent.defaultmaxSockets = 10;
http.Agent.defaultmaxSockets изменится глоабльно, то есть каждый HTTP agent будет использовать при своем создании эти значения.
Но эти изменения не затронут уже тсозданные объекты agent.
Когда вы делаете запрос request вы также можете определить настройки HTTP agent так
var http = require('http');
var agentOptions = {
maxSockets: 10
};
var agent = new Agent(options);
var requestOptions = {
host: 'www.google.com',
port: 80,
agent: agent
};
var request = http.request(requestOptions);
// ... some code ...
request.end();
//------------------------------------------------------------------
Использование сторонних модулей для упрощений HTTP запросов request.
Установка модуля Request
npm install request
Работа с модулем Request
var request = require('request');
request('http://www.acme.com/4001/something?page=2', function(error, response, body){
// ...some code...
});
Этот код выполняет HTTP запрос GET по адресу URL и возвращает response и body в одной колбак функции.
Вы также можете указать метод запроса
request.head(url);
request.get(url);
request.post(url);
request.put(url);
request.del(url);
Вместо использования строки, содержащей URL вы можете передавать в функцию объект options вида
{
url: 'http://www.acme.com:4001/something',
method: 'DELETE',
headers: {Accept: 'application/json'},
body: new Buffer('Hello world!')
}
В options могут содержаться следующие свойства
uri или url - полный URI или объект URL из url.parse(). Например https://my.server.com/some/path?a=1&b=2
method - метод HEAD, GET, POST, PUT, DELETE. По умолчанию равен GET.
headers - заголовки передаваемые серверу при отправке запроса. По умолчанию пустой объект {}.
qs - search query string - строка поиска в URL, состоящая из пар ключ-значение. Например {a:1, b:2}
body - тело запроса для методов POST и PUT. Должго быть буфером байтов или строкой.
form - устанваливает тело отобюражение value of query string и добавляет content-type application/x-www-form-urlencoded; charset=utf-8 в заголовок header.
json - устанваливает отоюражение данных в формате JSON и добавляет application/json в заголовок header
followRedirect - обрабатывает ответы с кодом HTTP 3xx как редиректы. По умолчагию равно true.
maxRedirects - устанваливает максимальное число разрешенных редиректов. По умолчанию равно 10.
onResponse - если onResponse равно true, то функция колбак будет вызвана на событие response вместо события end. Если onResponse - это функция, то она будет вызвана на response и не затронит обычную семантику колюак функции вызываемой на событие end.
encoding - кодировка, которая будет использована в методе setEncoding для определения кодировки response data. Если encoding установлена как null, то тело ответа будет обработано в качестве буфера байтов.
pool - объект, содержащий объекты agent для этих запросов. Если данная опция не указана, то будет ииспользован глобальный пул Node.js, который установлен по умолчанию в maxSockets.
pool.maxSockets - максимальное число сокетов в пуле.
timeout - время в миллисекундах оэидания ответа от сервера перед разрывом соединения.
Создание простого HTTP server с использованием модуля Request.
В начале создадим и запусти сервер, к которому будет подсоединяться.
require('http').createSrever(function(request, response){
function printBack() {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end(JSON.stringify({
url: request.url,
method: request.method,
headers: request.headers
}));
}
switch(request.url){
case '/redirect':
response.writeHead(301, {'Location': '/'});
response.end();
break;
case /print/body':
request.setEncoding('utf8');
var body = '';
request.on('data', function(data){
body += data;
});
request.on('end', function(){
response.end(JSON.stringify(body));
});
break;
default:
printDefault();
break;
}
}).listen(8080);
Тепрь напишем код клиента, который будет подсоединяться к нашему серверу, используя модуль Request.
var request = require('request');
var inspect = require('util').inspect;
request('http://localhost:8080/abc/def', function(error, response, body){
if (error) {throw error;}
console.log(inspect({
error: error,
response: {
statusCode: response.statusCode
},
body: JSON.parse(body)
}));
});
После соединения клиента с сервером вы получите следующий вывод на экран
{ err: null,
res: { statusCode: 200 },
body:
{ url: '/abc/def',
method: 'GET',
headers:
{ host: 'localhost:4001',
'content-length': '0',
connection: 'keep-alive' } } }
Для отправки данных на сервер подобно тому, как это делается через URL в браузере вы можете установить значение объекта body
var request = require('request');
var inspect = require('util').inspect;
var body = {
a: 1,
b: 2
};
var options = {
url: 'http://localhost:4001/print/body',
form: body
};
request(options, function(err, res, body) {
if (err) { throw err; }
console.log(inspect({
err: err,
res: {
statusCode: res.statusCode,
headers: res.headers
},
body: JSON.parse(body)
}));
});
В результате вы получите следующий вывод в консоль
{
err: null,
res: { statusCode: 200,
headers: { connection: 'keep-alive', 'transfer-encoding': 'chunked' }
},
body: 'a=1&b=2'
}
Потоковый вывод информации через модуль Requeset - Streaming
var request = require('request');
var fs = require('fs');
var file = fs.createWriteStream('/path/to/my/file');
request.get('http://www.acme.com/tmp/test.html').pipe(file);
Вы также можете перенаправить поток записи в другой запрос
var request = require('request');
var source = request.get('http://my.server.com/images/some_file.jpg');
var target = requuest.post('http://other.server.com/images/some_file.jpg');
source.pipe(target);
Использование Cookie Jar
По умолчанию модуль Request хранит все куки в глобальной cookie jar. Это значит, что все запросы посылают cookie, которые были получены от данного hostname.
Однако возможно вы не хотите собирать все куки. В этом случае вы можете отключить кукм глобально с помощью этой функции
request.defaults({jar: false});
Вы также можете отключить сбор куки через options
var options = {
url: 'http://www.example.com/',
jar: false
};
request(options, callbackFunction);
Вы также можете определить отдельное место записи куки.
var jar = request.jar();
var options = {
url: 'http://www.example.com/',
jar: jar
};
request(options, callbackFunction);
//------------------------------------------------------------------
Использование датаграмм UDP
Передача данных в формате UDP используется в DNS (Domain Name System), интернет-телевидении IPTV (Internet Protocol Television), VoIP (Voice over IP), IP tunneling, TFTP (Trivial File Transfer Protocol), SNMP (Simple Network Managment Protocol), DHCP (Dynamic Host Configuration Protocol) и других.
Создание Datagram server
Для создание Datagram server в Node.js используется модуль dgram
var dgram = require('dgram');
var server = dgram.createSocket('udp4'); // upd4 для IPv4 и udp6 для IPv6
UDP сервер излучает событие 'message', когда данные поступают, поэтому на прослушивание этого событие вы можете вызвать колбак-функцию.
server.on('message', function(message){
console.log('Server got message: ' + message);
});
Теперь надо связать сервер с UDP портом
var port = 8080;
server.on('listening', funciton(){
var address = server.address();
console.log('Peer listening on ' + address.address + ' : ' + address.port);
});
server.bind(port);
Вы также можете прослушивать событие 'listening', которое запускаетс после того как сервер начнет прослушивать messages.
Полый код прослушивания сервера.
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on('message', function(message){
console.log('server got message: ' + message);
});
var port = 8080;
server.on('listening', function(){
var address = server.address();
console.log('server listening on ' + address.address + ':' + address.port);
});
server.bind(port);
Выполнение данного сценрия выведет в коносль
server listening on 0.0.0.0:8080
Поскольку UDP сервера могут принимать сообщения от любых посылателей, то вы можете получать адрес посылателя.
server.on('message', function(message, rinfo){
console.log('server got message: %s from %s:%d', message, rinfo.address, rinfo.port);
});
Эту информацию можно использовать для отправки обратного сообщения посылателю.
Создание простого Datagram echo server
Данный сервер будет отсылать полученные сообщения обратно посылателю.
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
var port = 8080;
server.on('message', function(message, rinfo){
console.log('server got message: %s from %s:%d', message, rinfo.address, rinfo.port);
server.send(message, 0, message.length, rinfo.port, rinfo.address); // Отсылаем данные обратно посылающему
});
server.bind(port);
Первые 3 аргумента в методе send() описывают сообщение: буфер с содержимым сообщения, смещение в байтах буфера с которого начнется сообщения и в конце указана длина сообщения в байтах.
Оставшиеся 2 аргумента указывают порт и хост куда будет отправлено сообщение.
В конце отправки сообщения вы можете запустить колбак функцию
var message = new Buffer('blah blah');
client.send(message, 0, message.length, 4000, 'localhost', function() {
// you can reuse the message buffer
});
Создание Datagram client
Для создание клиента, который просто посылает UDP пакеты вы можете создать простой UDP socket и послать сообщение из него
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
var message = new Buffer('this is a message');
client.send(message, 0, message.length, 4000, 'localhost', function(error, bytes) {
if (error) {throw error;}
client.close();
});
// client.bind(port); вы можете прописать, если хотите послеть сообщения с определенного порта.
Когда вы не заотите больше посылать сообщения вы можете закрыть datagram socket методом .close()
Создание простого Datagram command-line client
#! /usr/bin/env node
var host = process.argv[2]; // Получение второго аргумента из командной строки
var port = parseInt(process.argv[3], 10);
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
process.stdin.resume();
process.stdin.on('data', function(data){
client.send(data, 0, data.length, port, host);
});
client.on('message', function(message){
console.log('Got message back: ' + message.toString());
});
console.log('Start typing to send messages to %s:%s', host, port);
Отправка сообщение множеству серверов.
UPD позволяет отправлять сообщение множеству сервеорв через одно message. Это необъходимо когда вы не знаете адресо получателей.
Получатели же в свою очередь постоянно прослушивают канал отправителя сообщений.
Получение множественных сообщений
var server = require('dgram').createSocket('udp4');
server.on('message', function(message, rinfo){
console.log('Server got meaasge: ' + message + ' from ' + rinfo.address + ':' + rinfo.port);
});
server.bind(8080);
server.addMembership('230.1.2.3');
Посылка множества сообщений
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
var message = new Buffer('this is a multicast message');
client.bind(8080);
client.setMulticastTTL(10); // через сколько роутеров датаграмма может пройти прежде чем будет отклонена
client.send(message, 0, message.length, 8080, '230.1.2.3');
client.close();
//------------------------------------------------------------------
Создание защищенного сервера с помощью TLS/SSL
TLS (Transport Layer Security) и SSL (Secure Socket Layer) позволяет серверу и клиенту общаться по зашифрованному каналу.
Создание TLS server
var tls = require('tls');
var fs = require('fs');
var serverOptions = {
key: fs.readFileSync('./my_key.pem');
cert: fs.readFileSync('./my_certificate.pem');
};
var server = tls.createServer(serverOptions);
Помимо key и cert вы можете передать в options:
requestCert - если рано true, то сервер запросит сертификат от клиента для проверки сертификата. Значение по умолчанию равно false.
rejectUnauthorized - если рано true, то сервер отклонит соединение не подтвержденное поддерживаемыми сертификатами. Эта опиция работает, если requestCert равен true. По умолчанию значение равно false.
Прослушивание события установления защищенного соединения.
var port = 8080;
server.listen(port);
function connectionListener(stream){
console.log('got secure connection');
}
server.on('secureConnection', connectionListener);
Чтение данных, присланных клиентом
functino secureConnectionListener(cleintStream){
clientStream.on('data', function(data){
console.log('Got some data from client: ' + data);
});
}
server.on('secureConnection', secureConnectionListener);
Посылка данных от сервера клиенту
server.on('secureConnection', function(clientStream){
clientStream.write('Hello!');
});
Завершение соединения
server.on('secureConnection', function(clientStream){
clientStream.on('data', funciton(data){
if (data.toString().trim().toLowerCase() === 'quit') {
clientStream.end('Bye bye!'); // вы можете передать строку или буфер байтов
}
});
});
Создание TLS client
var fs = require('fs');
var options = {
key: fs.readFileSync('/path/to/my/private_key.pem'),
cert: fs.readFileSync('/path/to/my/certificate.pem')
};
var tsl = require('tsl');
var host = 'locathost';
var port = 8080;
var client = tsl.connect(port, host, options, function(){
console.log('connected');
console.log('authorized: ' + client.authorized);
if (!client.authorized) {
console.log('client denied access: ' + client.authorizationError);
} else {
client.write('Hello!');
}
client.on('data', function(data) {
console.log('Got some data from server: ' + data);
});
client.end('Bye bye'); // Закрытие соединения с сервером. Поскольку вы закрыли соединения со своего конца, то вы все еще можете получать сообщения от сервера.
});
Создание TLS chart server
var tls = require('tls');
var fs = require('fs');
var port = 8080;
var clients = [];
var options = {
key: fs.readFileSync('server_key.pem'),
cert: fs.readFileSync('server_cert.pem')
};
function distribute (from, data) {
var socket = from.socket;
clients.forEach(function(client){
if (client !== from) {
client.write(socket.remoteAddress + ':' + socket.remotePort + ' said: ' + data);
}
});
}
var server = tls.createServer(options, function(client){
clients.push(client);
client.on('data', function(data){
distribute(client, data);
});
client.on('close', function(){
console.log('closed connection');
clients.slice(clients.indexOf(client), 1);
});
});
server.listen(port, function(){
console.log('listening on port ' + server.address().port);
});
Создание TLS Command-line Chat Client
// Клиент
var tls = require('tls');
var fs = require('fs');
var port = 8080;
var host = '0.0.0.0';
var options = {
key: fs.readFileSync('client_key.pem'),
cert: fs.readFileSync('client_cert.pem')
};
process.stdin.resume();
var client = tls.connect(port, host, options, function(){
console.log('connected');
process.stdin.pipe(client, {end: false});
client.pipe(process.stdout);
});
// Сервер
var options = {
key: fs.readFileSync('server_key.pem'),
cert: fs.readFileSync('server_cert.pem'),
requestCert: true,
rejectUnauthorized: true
};
var server = tls.createServer(options, function(client){
console.log('client authorized: ' + client.authorized);
// ...
});
//------------------------------------------------------------------
Создание защищенного HTTPS server
var fs = require('fs');
var https = require('https');
var options = {
key: fs.readFileSync('server_key.pem'),
cert: fs.readFileSync('server_cert.pem'),
requestCert: true,
rejectUnauthorized: true
};
var server = https.createServer(option, function(request, response){
console.log('authorized: ' + request.socket.authorized);
console.log('client certificate: ' request.socket.getPeerCertificate());
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World!');
});
var port = 8080;
var address = '192.168.1.100';
server.listen(port, address, function(){
console.log('Server is listening on port ' + server.address().port());
});
Синхронная загрузка файлов сертификата не блокирует основной цикл Node.js, поскольку происходит на этапе загрузки модуля, то есть до того, как Node.js начнет выполнять главный поток цикла и отслеживать события.
Метод listen() является асинхронным. Это значит, что он не запускается мгновенно. Для того, чтобы определить когда он сработает вы можете использовать колбак функцию.
Создание клиента, для обращения к защищенному HTTPS sever
var fs = require('fs');
var https = require('https');
var options = {
host: '0.0.0.0',
port: 8080,
methos: 'GET',
path: '/'
};
var request = https.request(options, function(response){
console.log('response status code: ' + response.statusCode);
response.on('data', function(data){
console.log('got some data from server: ' + data);
});
});
requrest.write('Hey!');
requrest.end();
Запрос клиента - это writable stream поэтому вы можете пересылать через него данные через методе write и делать pipe() через readable stream.
Для перехода к ожиданию ответа от сервера вы должны выполнить метод end()
При получении ответа от сервера сработает событие on('data')
//------------------------------------------------------------------
Тестирование кода в Node-Tap
Установка Node-Tap через package.json
{
'name': 'MyApp',
'version': '0.1.0',
'devDependecies': {'tap': '*'}
}
Установка зависимостей через консоль
npm intall
Установленные модули через devDependecies не будут установлены в production версию. Для установки production версии наберите в консоли
npm install -production
Тесты создаются в отдельных директориях tests и содержат по одному файлу с тестами на каждый файл-модуль.
Создадим файл с тестами и напишем в него следующий код
var test = require('tap').test;
test('addition of 2 and 2 works', funciton(t){
t.equal(2 + 2, 4, '2 + 2 should be 4');
t.end();
});
test('truthyness of numbers', funciton(t){
t.ok(1, '1 should be true');
t.notOk(0, '0 should not be true');
t.end();
});
test('sem works', function(t){
var a = 2 + 2;
t.equal(a, 4, '2 + 2 should be equal 4');
t.notEqual(a, '4', '2 + 2 should not be equal to string "4" ');
t.end();
});
test('object equality', function(t){
var = {a: 1};
t.equivalent(a, {a: 1});
t.end();
});
test('object similarity', function(t){
var a = {a: 1, b: 2};
t.similar(a, {a: 1});
t.similar('abc', 'abc');
t.similar(10, 10);
t.end();
});
test('object type', function(t){
t.type(1, 'number');
t.type('abc', 'string');
t.type({}, Object);
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
t.type(emitter, EventEmitter);
t.type(emitter, Object);
t.end();
});
Node-Tap поддерживает асинхронные тесты, посзовляющие динамачески вводить информацию через консоль. Поэтому нужно в конце выполнить метод end()
Запуск тестов через консоль
node my_test.js
Использование модуля assert при тестировании
var assert = requie('assert');
var a = true;
assert(a);
assert(a, 'a should be true');
assert.ok(a, 'a should be true');
assert.equal(a, 10, 'a should be 10');
assert.notEqual('10', 10);
assert.strictEqual('10', 10, 'string 10 should be equal to number 10');
assert.notStrictEqual('10', 10, 'string 10 should be equal to number 10');
assert.deepEqual({a:1}, {a:1});
assert.deepEqual(new Date(), new Date());
assert.deepEqual(/a/gi, /a/gi);
В JavaScript false - это
- false
- null
- undefined
- пустая строка ""
- пустой массив []
- 0
- NaN
все остально е будет true
Создание асинхронных тестов с Node-Tap.
Сперва создадим сервер для тестирования
var parser = require('url').parse;
require('http').createServer(function(requrest, response){
var params = parse(request.url, true).query;
var a = parseInt(params.a, 10);
var b = parseInt(params.b, 10);
var result = a + b;
response.end(JSON.stringify(result));
}).listen(8080);
Теперь создадим клиента, который будет подключаться к серверу
var request = require('request');
function sum (a, b, callback) {
var options = {
uri: 'http://localhost:8080/',
qs: {
a: a,
b: b
}
};
request(options, function(error, response, body){
var result;
if (error) {return callback(error);}
try {
result = JSON.parse(body);
} catch (error) {
return callback(error);
}
return callback(null, result);
});
}
module.exports = sum;
Далее создайте тестовую папку и установите Node-Tap через package.json
{
"name": "sum-tests",
"version": "1.0.0",
"devDependencies": {
"tap": "*"
}
}
Напишем код в файле tests/sum.js
var sum = require('../client/sumclient.js');
var test = require('tap').test;
test('sums 1 and 2', function(t){
sum(1, 2, function(error, result){
t.notOk(error, 'not error');
t.equal(result, 3, '1 + 2 should be equal to 3');
t.end();
});
});
test('sums 5 and 0' function(t){
sum(5, 0, function(error, result){
t.notOk(error, 'not error');
t.equal(result, 5, '5 + 0 should be equal to 5');
t.end();
});
});
test('sums 5 and -2', function(t){
sum(5, -2, function(error, result){
t.notOk(error, 'not error');
t.equal(result, 3, '5 + -2 shoul be equal to 3');
t.end();
});
});
Запустим тесты
node tests/sum.js
//------------------------------------------------------------------
Console.log
Использование console.log - это синхронная опереция. Во время своего выполнения она блокирует основной поток выполнения кода Node.js.
Поэтому вы должны избегать использование console.log на production, чтобы не вызвать торможения.
//------------------------------------------------------------------
Использование Chrome Web Inspector для тестирования программы в Node.js
Установите Node Inspector
npm instal -g node-inspector
Запустите Node Inspector
node-inspector &
Запустите вашу программу с ключем ---debug или --debug-brk (прерывание на перой линни кода)
node --debug-brk my_app.js
Когда вы дебажите сервера, вы должны использовать только --debug, для другого кода можете использовать --debug-brk
Теперь вы можете отурыть браузер Chrome и перейти по адресу localhost:8080
В Web Inspector во время тестирования вы можете изменять код вживую дважды кликнув по нему.
В Web Inspector вы увидите дебаггер кода вашей программы.
//------------------------------------------------------------------
Одновременное выполнение нескольких асинхронных функкций через модуль Async
Установка модуля Async
npm install async
Пример работы модуля async
Создадим простой сервер
var port = process.argv[2] && parseInt(process.argv[2], 10) || 8080;
require('http').createServer(function(request, response){
var body = '';
request.setEncoding('utf8');
request.on('data', function(data){
body += data;
});
request.once('end', function(){
var number = JSON.parse(body);
var squared = Math.pow(number, 2);
response.end(JSON.stringify(squared));
});
}).listen(port, function(){console.log('Squaring Server listening on port: ' + port);});
Запустим наш сервер.
node server.js
Теперь напишим код отправки асинхронных запросов на наш сервер, используя модуль async
Последовательное выполнение асинхронных функций.
var async = require('async');
var request = require('request');
function done (error, results){
if (error) {throw error;}
console.log('results: ' + results);
}
async.series([
function(next){
request.post({uri: 'http://localhost:8080/', body: '4'}, function(error, response, body){
next(error, body && JSON.parse(body));
});
},
function(next){
request.post({uri: 'http://localhost:8080/', body: '5'}, function(error, response, body){
next(error, body && JSON.parse(body));
});
}
], done);
Запустим наши асинхронные запросы
node serises.js
Результат в консоли
results: [16, 25]
Данные асинхронные функции будут выполняться последовательно друг за другом. Следующая функция будет запущена после того, как успешно завершит работу предыдущая.
Колбак-функция done() запускается после всех функций, описанных в массиве.
Параллельное выполнение асинхронных функций.
var async = require('async');
var request = require('request');
function done (error, results){
if (error) {throw error;}
console.log('results: ' + results);
}
async.parallel([
function(next){
request.post({uri: 'http://localhost:8080/', body: '4'}, function(error, response, body){
next(error, body && JSON.parse(body));
});
},
function(next){
request.post({uri: 'http://localhost:8080/', body: '5'}, function(error, response, body){
next(error, body && JSON.parse(body));
});
}
], done);
Запустим наши асинхронные запросы
node serises.js
Результат в консоли
results: [16, 25]
Обе функции будут выполнены параллельно. После того, как все они будут завершены успешно будет запущена функция done()
Каскадный запуск асинхронных функций.
Вы можете запускать следующую по очереди асинхронную функцию только если выполнение предыдущей асинхронной функции завершилось успешно.
var async = require('async');
var request = require('request');
function done(error, response, body) {
if (error) {throw error;}
console.log("3^4 = " + body);
}
async.waterfall([
function(next){
request({uri: 'http://localhost:8080/', body: '3'}, next);
},
function(next){
request({uri: 'http://localhost:8080/', body: body}, next);
}
], done);
Каждая функция будет запускаться, только если предыдущая функция завершилась успешно. В конце запустится функция done()
Каждая колбак-функция next() получает все колбак аргументы из предыдущей асинзронной функции минус первый аргумент error.
Если на каком-то этапе происходит ошибка, то последяя колбак-функция, которой здесь является функция done будет вызвана с аргументом error.
Выполнение множества асинхронных функций в составе очереди.
Вы можете выполнять несколько конкурирующих между собой асинхронных функций в порядке очереди, контролируя число одновременно работающих асинхронных функций.
var async = require('async');
var request = require('request');
function done(error, response, body) {
if (error) {throw error;}
console.log("results: " + body);
}
var maximumConcurrency = 5;
function worker(task, callback) {
request.post({uri: 'http://localhost:8080', body: JSON.stringify(task)}, function (error, response, body){
callback(error, body && JSON.parse(body));
});
}
var queue = async.queue(worker, maximumConcurrency);
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
queue.push(i, function(error, result){
if (error){throw error;}
console.log(i + '^2 = ' + result);
});
});
Код выполняет задания по очереди, но одновременно не более 5 заданий одновременно.
Результат будет выдан неупорядоченно, поскольку функции выполняются асинхронно.
Функция worker получает задание и выполняет его. В данном случае задание - это число, которое сервер будет возводить в квадрат.
Определение максимльной concurrency может выполняться динамически через функцию и, что самое важное это число можно изменять после того, как очередь заданий уже создана и работает.
Измение concurrency делается так
queue.concurrecy = 10;
Если число одновременно выполяемых заданий уже превышено, то задания помещаются в очередь ожидания.
Об ээтом событии вы можете узнать так:
queue.sturated = function(){console.log('queue is saturated');}
Вы также можете узанть, когда последний элемент из очереди попадает в функцию worker
queue.empty = function(){console.log('queue is empty');}
Когда функция worker возвращает последний выполненный элемемент, то очередь очищается, вызывается событий drain
queue.drain = function(){'queue is drained, no more work!'}
Поскольку вы можете добавлять новые элементы в очередь в будущем, то эти события могу срабатывать не один раз.
Асинхронная итерация - проход по элементам.
Вы можете обходить все элементы в коллекции асинхронно.
var async = requre('async');
var reques = require('request');
var results = {};
function done(error){
if (error) {throw error;}
console.log('results: ' + results);
}
var collection = [1,2,3,4];
function iterator (value, callback) {
request.post({uri: 'http://localhost:8080', body: JSON.stringify(value)}, function(error, response, body){
if (error){return callback(error);}
results[value] = JSON.parse(body);
callback();
});
}
async.forEach(collection, iterator, done);
async.forEach возьмет каждый элемент из массива, выполнит с ним асинхронную функцию параллельно с остальными и затем выполнит функцию done() когда все асинхронные функции будут завершены.
Если вы хотите, чтобы асинхронные функции вызывались последовательно друг за другом, то используйте метод aync.forEachSeries()
async.forEachSeries(collection, iterator, done);
Вы таже можете запускать асинхронные функции параллельно, но контролировать maximum concurrnecy с помощью метода async.forEachLimit()
var maximumConcurrency = 5;
async.forEachLimit(collection, maximumConcurrency, iterator, done);
Вы также можете выполнять маппинг элементов массива асинхронно.
var async = require('async');
var request = require('request');
var collection = [1,2,3,4];
function done(error){
if (error) {throw error;}
console.log('results: ' + results);
}
function iterator (value, callback) {
request.post({uri: 'http://localhost:8080', body: JSON.stringify(value)}, function(error, response, body){
if (error){return callback(error);}
results[value] = JSON.parse(body);
callback();
});
}
async.map(collection, iterator, done);
Когда все асинхронные функции будут завершены будет вызвана функция done()
Асинхронно можно выполнять над массивами операцию reduce
var async = require('async');
var request = require('request');
var collection = [1,2,3,4];
function done(error){
if (error) {throw error;}
console.log('results: ' + results);
}
function iterator (memo, item, callback) {
request.post({uri: 'http://localhost:8080', body: JSON.stringify(item)}, function(error, response, body){
callback(error, body && (memo + JSON.parse(body)));
});
}
async.reduce(collection, 0, iterator, done);
Асинхронно можно выполнять над массивами операцию filter
var async = require('async');
var request = require('request');
var collection = [1,2,3,4];
function done(results){
console.log('results: ' + results);
}
function test(value){
return value > 10;
}
function filter(item, callback){
request.post({uri: 'http://localhost:8080', body: JSON.stringify(item)}, function(error, response, body) {
if (error) {throw error;}
callback(body && test(JSON.parse(body)));
});
}
async.filter(collection, filter, done);
Обратная функция filter это async.reject
Функции async.filter() и async.reject() выполняют функцию filter для каждого элемента параллельно.
Так же можно вызывать фильтрацию элементов сериями.
async.filterSeries(collection, filter, done);
и
async.rejectSeries(collection, filter, done);
Вы можете перекратить объод элементов, если уже достигли нужного значения, используя метод detect.
var async = require('async'),
var request = require('request');
var collection = [1, 2, 3, 4, 5];
function done(result) {
console.log('The first element on %j whose square value '+'is greater than 10: %j', collection, result);
}
function test(value) {
return value > 10;
}
function detect(item, callback) {
request.post({uri: 'http://localhost:8080', body: JSON.stringify(item)}, function(err, res, body) {
if (err) {throw err;}
callback(body && test(JSON.parse(body)));
});
}
async.detect(collection, detect, done);
Вы также можете выполнять функцию async .detect() для серии элементов
async.detectSeries(collection, detect, done);
Функция находит асинхронно первый подходящий элемент и найдя его сруз выполняте функцию done()
//------------------------------------------------------------------
Создание HTTP Middleware - Фреймворк Connect
Установка Connect
npm install connect
Напишем файл прослуйку, который будет обрабатывать запросы сервера hello_world.js
function hello (request, response) {
response.end('Hello world');
}
module.exports = hello;
Создадим сервер, использующий наш файл прослойку и Connect
var connect = require('connect');
var hello = require('./hello_world.js');
var server = connect.createServer(hello);
server.listen(8080);
Запустим наш сервер
node server.js
Перейдем в браузере по адресу http://localhost:8080 и увидим сообщение Hello world
На месте прослойки может быть любой код. Например такой
function replyText(text){
return function(request, response){
response.end(text);
}
}
module.exports.replyText;
Файл server.js
var connect = require('connect');
var replyText = require('./reply_text.js');
var server = connect.createServer(replyText);
server.listen(8080);
Создание асинхронной прослойки Middleware
Код прослойки Middleware может выполняться асинхронно. Когда её работа будет завершена, то будет вызвана функция колбак.
Код прослойки
function writeHeader(name, value) {
return function (request, response, next) {
response.setHeader(name, value);
next();
};
}
module.exports = writeHeader;
Код сервера
var connect = require('connect');
var writeHeader = require('./write_header.js');
var server = connect.createServer(writeHeader('X-Powered-By', 'Node'), replytext('Hello World!'));
server.listen(8080);
Вызов колбак функций внутри прослойки
var fs = require('fs');
var path = require('path');
var util = require('util');
function saveRequest(dir) {
return function(request, response, next){
var fileName = path.join(dir, Date.now().toStrinf() + '_' + Math.floor(Math.random() * 100000) + '.txt');
var file = fs.createWriteStream(fileName);
file.write(request.method + ' ' + request.url + '\n');
file.write(util.inspect(request.headers) + '\n');
requrest.pipe(file);
next();
};
}
module.exports = saveRequest;
Код сервера
var connect = require('connect');
var saveRequest = require('./save_request');
var writeHeader = require('./write_header');
var replyText = require('./reply_text');
var server = connect.createServer(saveRequest(__dirname + '/requests'), writeHeader('X-Powered-By', 'Node'), replyText('Hello World!'));
sever.listen(8080);
Обработка ошибок внутри кода прослойки Middleware
function errorCreator(){
return function(request, response, next){throw new Error('This is an error');}
}
module.exports = errorCreator;
Код сервера
var connect = require('connect');
var errorCreator = require('./error_creator');
var saveRequest = require('./save_request');
var writeHeader = require('./write_header');
var replyText = require('./reply_text');
var server = connect.createServer(errorCreator(), saveRequest(__dirname + '/requests'), writeHeader('X-Powered-By', 'Node'), replyText('Hello World!'));
server.listen(8080);
Создание обработчика ошибок внутри Middleware
function errorHandler(){
return function (error, request, response, next) {
if (error) {
response.writeHead(500, {'Content-Type': 'text/html'});
response.end('<h1>Oh no! We have an error!</h1>\n<pre>' + err.stack + '</pre>');
} else {
next();
}
};
}
module.exports = errorHandler;
Код сервера
var connect = require('connect');
var errorCreator = require('./error_creator');
var saveRequest = require('./save_request');
var writeHeader = require('./write_header');
var replyText = require('./reply_text');
var errorHandler = require('./error_handler');
var server = connect.createServer(errorCreator(), saveRequest(__dirname + '/requests'), writeHeader('X-Powered-By', 'Node'), replyText('Hello World!'), errorHandler());
server.listen(8080);
По факту Connect выполнит код функции errorHandler только при возникновении ошибки, поэтому код этой функции можно сократить так
function errorHandler() {
return function(err, req, res, next) {
res.writeHead(500, {'Content-Type': 'text/html'});
res.end('<h1>Oh no! We have an error!</h1>\n<pre>' + err.stack + '</pre>');
}
}
module.exports = errorHandler;
Connect позволяет логировать запросы к серверу так
var connect = require('connect');
var server = connect();
server.use(connect.logger());
server.use(function(req, res) {
res.end('Hello World!');
});
server.listen(8080);
Данная запись кода с использованием метода .use() равнозначна старой форме записи.
Вы можете указать функции connect.logger() формат логирования, напрмер так
server.use(connect.logger(tiny));
Вы можете также создать свой формат логирования, используя следующие переменные:
:req[header] — вы можете определить свой формат заголовка, например :req[Accept]. Вы можете также вывести столько заголовков, сколько вам нужно в формате строки.
:http-version — HTTP version.
:response-time — разница между временем когда запрос request достигает логера и временем когда запрос request завершается в миллисекундах.
:remote-addr — IP address клиента делающего запрос на сервер.
:date — дата создания данного лога.
:method — HTTP method (HEAD, GET, PUT, DELETE).
:url — запрашиваемый URL адрес.
:referrer — содержимое referrer header.
:user-agent — user agent (версия браузера).
:status — числовое знфчение HTTP response status code.
Пример создания собственного формата логирования
var format = ':method :url - :status - :response-time ms';
sever.use(conncet.logger(format));
Использования обработчика ошибок из модуля Connect
var connect = require('connect');
var server = connect();
server.use(function(request, response, next){
next(new Error('Hey!'));
});
server.use(function(request, response){
response.end('Hello World');
});
server.use(connect.errorHandler());
server.listen(8080);
Здесь мы умышленно спровоцироавли вызов ошибки, чтобы произошел вызов функции connect.errorHandler()
В резудльтате будет выведено сообщение об ошибке.
Вы можете изменить заголовок сообщения об ошибке так
connect.errorHandler.title = 'My application';
Connect может обслуживать загрузку статичных файлов таких как CSS, JavaScript и HTML
var connect = require('connect');
var server = connect();
server.use(connect.static(__dirname + '/public'));
server.use(function(request, response){
response.end('Hello World!');
});
server.listen(8080);
Обработка Query string из URL вида http://localhost:8080/?a=b&c=d с помощью модуля Connect
var connect = require('connect');
var server = connect();
server.use(connect.query());
server.use(function(request, response){
response.end(JSON.stringify(request.query));
});
server.listen(8080);
Разбор тела запроса с помощью модуля Connect
var connect = require('connect');
var server = connect();
server.use(connect.logger(':method :req[content-type]'));
server.use(connect.bodyParser());
server.use(function(request, response) {
response.end(JSON.stringify(req.body));
});
server.listen(8080);
Разбор Cookie с помощтю модуля Connect
var connect = require('connect');
var server = connect();
server.use(connect.cookieParser());
server.use(function(request, response) {
response.end(JSON.stringify(request.cookies));
});
server.listen(8080);
Использувание сессий с помощью модуля Connect
var connect = require('connect');
var format = require('util').format;
var server = connect();
server.use(connect.query());
server.use(connect.cookieParser('this is my secret string'));
server.use(connect.session({cookie: { maxAge: 24 * 60 * 60 * 1000 }}));
server.use(function(request, response) {
for (var name in request.query) {
request.session[name] = request.query[name];
}
response.end(format(request.session) + '\n');
});
server.listen(8080);
//------------------------------------------------------------------
Создание серверов с помощью модуля Express
Установка Express
npm install -g express@2.5.x
Установим зависимости в проект через файл package.json
{
"name": "application-name"
, "version": "0.0.1"
, "private": true
, "dependencies": {
"express": "2.5.11"
, "jade": ">= 0.0.1"
}
}
npm install
Модуль Express создаст для вас папки images javascripts stylesheets и файл app.js со следующим кодом
/**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes');
var app = module.exports = express.createServer();
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Routes
app.get('/', routes.index);
app.listen(3000, function(){
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});
Возможные варианты записи марштрутов URL в модуле Express
GET /users — показать списо пользователей.
GET /users/:username — показать провфиль пользователя с именем username.
GET /users/joe — показать профайл пользователя с именем "joe".
POST /users — создать новый профиль пользователя.
PUT /users/:username — обновить профиль пользователя с именем username.
PUT /users/joe — обновить профиль пользователя с именем "joe".
Пример связывания URL маршрута с контроллером
app.get('/', routes.index);
Пример енаписания нескоьких маршрутов
/*
* User Routes
*/
var users = require('../data/users');
module.exports = function(app) {
app.get('/users', function(req, res){
res.render('users/index', {title: 'Users', users: users});
});
app.get('/users/new', function(req, res) {
res.render('users/new', {title: "New User"});
});
app.get('/users/:name', function(req, res, next){
var user = users[req.params.name];
if (user) {
res.render('users/profile', {title: 'User profile', user: user});
} else {
next();
}
});
app.post('/users', function(req, res) {
if (users[req.body.username]) {
res.send('Conflict', 409);
} else {
users[req.body.username] = req.body;
res.redirect('/users');
}
});
app.del('/users/:name', function(req, res, next) {
if (users[req.params.name]) {
delete users[req.params.name];
res.redirect('/users');
} else {
next();
}
});
};
Использвание сессий с модулем Express
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('my secret string'));
app.use(express.session({
secret: 'my secret string',
maxAge: 3600000
}));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
/*
* Session Routes
*/
var users = require('../data/users');
module.exports = function(app) {
app.dynamicHelpers({
session: function(req, res) {
return req.session;
}
});
app.get('/session/new', function(req, res) {
res.render('session/new', {title: "Log in"});
});
app.post('/session', function(req, res) {
if (users[req.body.username] && users[req.body.username].password === req.body.password) {
req.session.user = users[req.body.username];
res.redirect('/users');
} else {
res.redirect('/session/new')
}
});
app.del('/session', function(req, res, next) {
req.session.destroy();
res.redirect('/users');
});
};
Использование Route Middleware
function notLoggedIn(req, res, next) {
if (req.session.user) {
res.send('Unauthorized', 401);
} else {
next();
}
}
module.exports = notLoggedIn;
/*
* Session Routes
*/
var users = require('../data/users');
var notLoggedIn = require('./middleware/not_logged_in');
module.exports = function(app) {
app.dynamicHelpers({
session: function(req, res) {
return req.session;
}
});
app.get('/session/new', notLoggedIn, function(req, res) {
res.render('session/new', {title: "Log in"});
});
app.post('/session', notLoggedIn, function(req, res) {
if (users[req.body.username] && users[req.body.username].password === req.body.password) {
req.session.user = users[req.body.username];
res.redirect('/users');
} else {
res.redirect('/session/new')
}
});
app.del('/session', function(req, res, next) {
req.session.destroy();
res.redirect('/users');
});
};
Express - это фреймворк для создания полноценных сайтов, как Django и Rails.
//------------------------------------------------------------------
Использование Socket.IO
Модуль Socket.IO позволяет создавать клиенту с сервером двунаправленные соединения для передачи данных друг другу через WebSocket
Установка Socket.IO
npm install socket.io
Создание простого WebSocket server server.js
var io = require('socket.io').listen(8080);
io.sockets.on('connection', function (socket){
socket.on('my event', funciton(content){
console.log(content);
});
});
Создание клиента для отправки запроса на сервер index.html
<html>
<head>
<title>Socket.IO example application</title>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8080');
socket.emit('my event', 'Hello world.');
</script>
</head>
<body>
</body>
</html>
Создание Socket-сервера на основе обычного сервера Node.js
var http = require('http').createServer(handler);
var io = require('socket.io').listen(http);
var fs = require('fs');
http.listen(8080);
function handler(request, response) {
fs.readFile(__dirnaem + '/index.html', function(error, data){
if (error) {
response.writeHead(500);
return response.end('Error loading index.html');
}
response.writeHead(200);
respones.end(data);
});
}
io.sockets.on('connection', function(socket){
socket.on('my event', function(content){
console.log(content);
});
});
Создание простого Chat server
var http = require('http').createServer(handler);
var io = require('socket.io').listen(http);
var fs = require('fs');
http.listen(8080);
function handler(request, response) {
fs.readFile(__dirnaem + '/index.html', function(error, data){
if (error) {
response.writeHead(500);
return response.end('Error loading index.html');
}
response.writeHead(200);
respones.end(data);
});
}
io.sockets.on('connection', function(socket){
socket.on('clientMessage', function(content){
socket.emit('serverMessage', 'You said: ' + content);
socket.broadcast.emit('serverMessage', socket.id + ' said: ' + content);
});
});
Создание простого Chat client, который будет общаться с нашим Chat server
<html>
<head>
<title>Node.js WebSocket chat</title>
<style type="text/css">
#input {
width: 200px;
}
#messages {
position: fixed;
top: 40px;
bottom: 8px;
left: 8px;
right: 8px;
border: 1px solid #EEEEEE;
padding: 8px;
}
</style>
</head>
<body>
Your message: <input type="text" id="input">
<div id="messages"></div>
<script src="http://localhost:4000/socket.io/socket.io.js"></script>
<script type="text/javascript">
var messagesElement = document.getElementById('messages');
var lastMessageElement = null;
function addMessage(message) {
var newMessageElement = document.createElement('div');
var newMessageText = document.createTextNode(message);
newMessageElement.appendChild(newMessageText);
messagesElement.insertBefore(newMessageElement, lastMessageElement);
lastMessageElement = newMessageElement;
}
var socket = io.connect('http://localhost:4000');
socket.on('serverMessage', function(content) {
addMessage(content);
});
var inputElement = document.getElementById('input');
inputElement.onkeydown = function(keyboardEvent) {
if (keyboardEvent.keyCode === 13) {
socket.emit('clientMessage', inputElement.value);
inputElement.value = '';
return false;
} else {
return true;
}
};
</script>
</body>
</html>
Расширение нашего чат-приложения.
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.broadcast.emit('serverMessage', username + ' said: ' + content);
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.emit('login');
});
<html>
<head>
<title>Node.js WebSocket chat</title>
<style type="text/css">
#input {
width: 200px;
}
#messages {
position: fixed;
top: 40px;
bottom: 8px;
left: 8px;
right: 8px;
border: 1px solid #EEEEEE;
padding: 8px;
}
</style>
</head>
<body>
Your message: <input type="text" id="input">
<div id="messages"></div>
<script src="http://localhost:4000/socket.io/socket.io.js"></script>
<script type="text/javascript">
var messagesElement = document.getElementById('messages');
var lastMessageElement = null;
function addMessage(message) {
var newMessageElement = document.createElement('div');
var newMessageText = document.createTextNode(message);
newMessageElement.appendChild(newMessageText);
messagesElement.insertBefore(newMessageElement, lastMessageElement);
lastMessageElement = newMessageElement;
}
var socket = io.connect('http://localhost:4000');
socket.on('serverMessage', function(content) {
addMessage(content);
});
socket.on('login', function() {
var username = prompt('What username would you like to use?');
socket.emit('login', username);
});
var inputElement = document.getElementById('input');
inputElement.onkeydown = function(keyboardEvent) {
if (keyboardEvent.keyCode === 13) {
socket.emit('clientMessage', inputElement.value);
inputElement.value = '';
return false;
} else {
return true;
}
};
</script>
</body>
</html>
Чат сервер обнаруживающий отключение пользователей от чата
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.broadcast.emit('serverMessage', username + ' said:' + content);
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.emit('login');
});
Разделение пользователей на Чат-комнаты
<html>
<head>
<title>Node.js WebSocket chat</title>
<style type="text/css">
#input {
width: 200px;
}
#messages {
position: fixed;
top: 40px;
bottom: 8px;
left: 8px;
right: 8px;
border: 1px solid #EEEEEE;
padding: 8px;
}
</style>
</head>
<body>
Your message:<input type="text" id="input">
<div id="messages"></div>
<script src="http://localhost:4000/socket.io/socket.io.js"></script>
<script type="text/javascript">
var messagesElement = document.getElementById('messages');
var lastMessageElement = null;
function addMessage(message) {
var newMessageElement = document.createElement('div');
var newMessageText = document.createTextNode(message);
newMessageElement.appendChild(newMessageText);
messagesElement.insertBefore(newMessageElement, lastMessageElement);
lastMessageElement = newMessageElement;
}
var socket = io.connect('http://localhost:4000');
socket.on('serverMessage', function(content) {
addMessage(content);
});
socket.on('login', function() {
var username = prompt('What username would you like to use?');
socket.emit('login', username);
});
function sendCommand(command, args) {
if (command === 'j') {
socket.emit('join', args);
} else {
alert('unknown command: ' + command);
}
}
function sendMessage(message) {
var commandMatch = message.match(/^\/(\w*)(.*)/);
if (commandMatch) {
sendCommand(commandMatch[1], commandMatch[2].trim());
} else {
socket.emit('clientMessage', message);
}
}
var inputElement = document.getElementById('input');
inputElement.onkeydown = function(keyboardEvent) {
if (keyboardEvent.keyCode === 13) {
sendMessage(inputElement.value);
inputElement.value = '';
return false;
} else {
return true;
}
};
</script>
</body>
</html>
Чат-сервер, позволяющий п ользователям присоединяться к чат-комнатам
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {
broadcast.to(room);
}
broadcast.emit('serverMessage', username + ' said: ' + message);
});
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.on('join', function(room) {
socket.get('room', function(err, oldRoom) {
if (err) { throw err; }
socket.set('room', room, function(err) {
if (err) { throw err; }
socket.join(room);
if (oldRoom) {
socket.leave(oldRoom);
}
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
});
socket.emit('serverMessage', 'You joined room ' + room);
socket.get('username', function(err, username) {
if (! username) {
username = socket.id;
}
socket.broadcast.to(room).emit('serverMessage', 'User ' + username + ' joined this room');
});
});
});
});
socket.emit('login');
});
Использование Namespacing для отделения кода
var socket = io.connect('http://localhost:4000/chat');
var chat = io.of('/chat');
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
// …
//------------------------------------------------------------------
Использование Redis совместно с Node.js и Socket.IO
Redis - это база данных, хранящая данные в формате ключ-значение в оперативной памяти.
Базы данных такие как Redis и Memcached предназначены для хранения небольших данных вида ключ-значение в оперативной памяти компьютера и используются в основном для хранения дланных сессий пользователей.
Установка модуля Redis
npm install redis
Установка самой Redis (См. на сайте redis.io/topics/quickstart)
Socket.IO поддерживает Redis из коробки
var redis = require('redis'),
RedisStore = require('socket.io/lib/stores/redis'),
pub = redis.createClient(),
sub = redis.createClient(),
client = redis.createClient();
io.set('store', new RedisStore({
redisPub : pub,
redisSub : sub,
redisClient : client
}));
Настройки redis
var redisPort = 6379;
var redisHostname = 'my.host.name';
var redis = require('redis');
var RedisStore = require('socket.io/lib/stores/redis');
var pub = redis.createClient(redisPort, redisHostname);
var sub = redis.createClient(redisPort, redisHostname);
var client = redis.createClient(redisPort, redisHostname);
Создание Socket.IO chat server использующий Redis store
var http = require('http').createServer(handler);
var io = require('socket.io').listen(http);
var fs = require('fs');
var redisPort = 6379;
var redisHostname = 'my.host.name';
var redis = require('redis');
var RedisStore = require('socket.io/lib/stores/redis');
var pub = redis.createClient(redisPort, redisHostname);
var sub = redis.createClient(redisPort, redisHostname);
var client = redis.createClient(redisPort, redisHostname);
io.set('store', new RedisStore({
redisPub: pub,
redisSub: sub,
redisClient: client
}));
http.listen(4000);
function handler(request, response){
fs.readFile(__dirnaem + '/index.html', function (error, data){
if (error) {
response.writeHead(500);
return response.end('Error loading index.html');
}
response.writeHead('200');
response.end(data);
});
}
var chat = io.of('/chat');
chat.on('connection', function(socket){
socket.on('clientMessage', function(content){
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(error, username){
if (! username) {
username = socket.id;
}
socket.get('room', function(error, room){
if (error) {throw error;}
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
broadcast.emit('serberMessage', username + ' said: ' + message);
});
});
});
socket.on('login', function(username){
socket.set('username', username, function (error){
if (error){throw error;}
socket.emit('serverMessage', 'Currently logged as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function(){
socket.get('username', function (error, username){
if (! username) {username = socket.id;}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.on('join', function(room) {
socket.get('room', function(error, oldRoom){
if (error) {throw error;}
socket.set('room', room, function(error){
if (error) {throw error;}
socket.join(room);
if (oldRoom) {socket.leave(oldRoom);}
socket.get('username', funciton(error, username){
if (! username) {username = socket.id;}
});
});
socket.emit('serverMessage', 'You joined room ' + room);
socket.get('username', function(error, username){
if (! username) {username = socket.id;}
socket.broadcast.to(room).emit('serverMessage', 'User ' + username + ' joined this room');
});
});
});
socket.emit('login');
});
//------------------------------------------------------------------
Работа с MySQL
Node.js позволяет запрашивать данные из MySQL асинхронно.
Установите базу данных MySQL
Затем установите модуль mysql для работы с базой данных через Node.js
npm install mysql
Создадим простое соединение с базой данных MySQL
var mysql = require('mysql');
var client = mysql.createClient({
host: 'localhost',
user: 'root',
password: 'root'
});
client.query('SELECT "Hello, World!"', function(error, results, fields){
console.log(results);
console.log(fields);
client.end();
});
Запрос к базе данных выполняется асинхронно. При получении данных выполняется колбак функция.
Для установки соединения с базой данных вы можете передавать следующие опции:
host - IP адрсе или DNS имя сервера базы данных. По умолчанию равно localhost
port - TCP порт сервера базы данных. По умолчанию равно 3306
user - логин пользователя базы данных. По умолчанию равно root
password - пароль пользователя базы данных. По умолчанию пусто.
database - имя базы данных, к котрой присоединяемся
debug - если равно true, то node-mysql будет выводить все входящие и выходящие в базу данных сообщения к консоль. По умолчанию равно false
flags - MySQL поддерживает разные протоколы. Поэтому список флагов надо смотреть на сайте.
Создание базы данных
client.query('CREATE DATABASE node', function(error){
if (error) {
client.end();
throw error;
}
});
Добавление данных в базу данных.
client.query('USE node');
client.query('CREATE TABLE test ' +
'(id INT(11) AUTO_INCREMENT,) ' +
'content VARCHAR(255), ' +
'PRIMARY KEY(if)');
client.query('INSERT INTO test (content) VALUES ("Hello")');
client.query('INSERT INTO test (content) VALUES ("World)');
client.end();
Чтобы не было возможности сделать SQL Injection можно использовать плейсхолдеры (?)
client.query('USE node');
var userInputSQLInjection = '"); DELETE FROM test WHERE id = 1; --';
client.query('INSERT INTO test (content) VALUES (?)', [userInputSQLInjection]);
client.end();
Так же данные в плейсхолдеры можно подставлять в виде массива
var data = [100, "the content"];
client.query('INSERT INTO test (id, content) VALUES (?, ?)', data);
Для обработки ошибок при вставке данных можно использовать коллюак функцию
client.query('INSERT INTO test (content) VALUES (?)', ['the content'], function(error, info) {
if (error) {return handle_error(error);}
console.log(info.insertId);
});
client.query('UPDATE test SET content = ?', ['new content'], function(error, info) {
if (error) {return handle_error(error);}
console.log(info.affectedRows);
});
Используя плейсхолдеры данные можно не только вставлять но и читать из базу данных
query = client.query('SELECT id, content FROM test WHERE id IN (?, ?)', [1, 100], function(error, results, fields) {
if (error) {throw error;}
console.log(results);
});
Поскольку Node.js сохраняет все результаты ответа базы данных в оперативной памяти, то выполнение множества запросов к базе данных может стать проблематичным.
Когда база данных возвращает ответ, то вы может реагировать на него прослушивая следующие типы событий:
- error
- field
- row
- end
Пример
var query = client.query('SELECT id, content FROM test WHERE id IN (?, ?)', [1, 100]);
query.on('error', function(error){throw error;});
query.on('field', function(filed){
console.log('Recieved field: ' + field);
});
query.on('row', function(row){
console.log('Recieved row: ' + row);
});
query.on('end', funciton (result){
console.log('Finished retrieving results');
});
Пример полного приложения работы с MySQL
var mysql = require('mysql');
var client = mysql.createClient({
host: 'localhost',
user: 'root',
password: 'root',
});
client.query('DROP DATABASE IF EXISTS node');
client.query('CREATE DATABASE node');
client.query('USE node');
client.query('CREATE TABLE test ' +
'(id INT(11) AUTO_INCREMENT, ' +
' content VARCHAR(255), ' +
' PRIMARY KEY(id))'
);
for (var i = 0; i < 10000; i++) {
client.query('INSERT INTO test (content) VALUES (?)', ['content for row ' + (i + 1)]);
}
client.query('UPDATE test SET content = ? WHERE id >= ?', ['new content', 9000], function(err, info) {
console.log('Changed content of ' + info.affectedRows + ' rows');
});
query = client.query('SELECT id, content FROM test WHERE id >= ? AND id <= ?', [8990, 9010]);
query.on('error', function(err) {
throw err;
});
query.on('row', function(row) {
console.log('Content of row #' + row.id + ' is: "' + row.content + '"');
});
query.on('end', function(result) {
console.log('Finished retrieving results');
});
client.end();
Поскольку операции в базе данных MySQL выполняются поочереди, то выполнение их всех последовательно может занять много времени.
Поэтому выгодно бывает создавать нексолько одновременнных соединений с базой данных для выполнения нескольких операций одновременно.
//------------------------------------------------------------------
Работа с CouchDB
Установите базу данных CouchDB
Затем установите модуль Nano для работы с CouchDB через Node.js
Используйте для этого установку через файл package.json
{
'name': 'couch-socketio-chat',
'version': '1.0.0',
'dependencies': {
'nano': '3.1.x',
'socket.io': '0.9.x'
}
}
Теперь выполните в консоли команду
npm install
Создадим чат сервер
var http = require('http').createServer(handler);
var io = require('socket.io').listen(http);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
var chat = io.of('/chat');
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
broadcast.emit('serverMessage', username + ' said: ' + message);
});
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.on('join', function(room) {
socket.get('room', function(err, oldRoom) {
if (err) { throw err; }
socket.set('room', room, function(err) {
if (err) { throw err; }
socket.join(room);
if (oldRoom) {socket.leave(oldRoom);}
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
});
socket.emit('serverMessage', 'You joined room ' + room);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.to(room).emit('serverMessage', 'User ' + username + ' joined this room');
});
});
});
});
socket.emit('login');
});
Создадим соединение с базой данных CouchDB
var nano = require('nano');
var couchdb = nano('https://myiriscouchserver.iriscouch.com');
или можно так var couchdb = nano('https://username:password@myiriscouchserver.iriscouch.com');
couchdb.db.create('chat', function(err) {
if (err) { console.error(err); }
var db = couchdb.use('chat');
// ...
});
Создадим полный The Socket.IO-based server который создает CouchDB client во время свой инициализации.
var nano = require('nano');
var couchdb = nano('https://myiriscouchserver.iriscouch.com');
couchdb.db.create('chat', function(err) {
if (err) { console.error(err); }
var chatDB = couchdb.use('chat');
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
var chat = io.of('/chat');
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
broadcast.emit('serverMessage', username + ' said: ' + message);
});
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.on('join', function(room) {
socket.get('room', function(err, oldRoom) {
if (err) { throw err; }
socket.set('room', room, function(err) {
if (err) { throw err; }
socket.join(room);
if (oldRoom) {
socket.leave(oldRoom);
}
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
});
socket.emit('serverMessage', 'You joined room ' + room);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.to(room).emit('serverMessage', 'User ' + username + ' joined this room');
});
});
});
});
socket.emit('login');
});
});
Вы можете проверить статус код ошибки в случае её возникновения.
if (err && err.status_code !== 412) {
throw err;
}
Обращаться к базе данных вы можете так
var chatDB = couchdb.use('chat');
Вставлять данные в базу данных CouchDB вы можете так
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
var messageDoc = {
when: Date.now(),
from: username,
room: room,
message: content
};
chatDB.insert(messageDoc, function(err) {
if (err) { console.error(err); }
});
broadcast.emit('serverMessage', username + ' said: ' + message);
});
});
});
CouchDB - это просто хранилище для значений типа ключ-значение.
Передавая правильный ID вы можете получить одно значение
chatDB.get(messageID, funciton(error, doc){
console.log('Got document: ' + doc);
});
Но для получения нескольких значений надо задавать более сложные запросы.
couchdb.db.create('chat', function(error){
if (error && error.status_code !== 422) {throw error;}
});
var designDoc = {
language: 'javascript',
views: {
by_room: {
map: couchMapreduce.toString()
}
}
};
var chatDB = couchdb.use('chat');
(function insertOrUpdateDesignDoc() {
chatDB.insert(designDoc, '_design/designdoc', function(err) {
if (err) {
if (err.status_code === 409) {
chatDB.get('_design/designdoc', function(err, ddoc) {
if (err) { return console.error(err); }
designDoc._rev = ddoc._rev;
insertOrUpdateDesignDoc();
});
} else {
return console.error(err);
}
}
startServer();
});
}());
});
Создадим Socket.IO server который создает базу данных в ChouchDB и view ждля получения данных
var nano = require('nano');
var couchdb = nano('https://myiriscouchserver.iriscouch.com');
var couchMapReduce = function (doc) {
emit([doc.room, doc.when], doc);
};
couchdb.db.create('chat', function(err) {
if (err && err.status_code !== 412) {throw err;}
var designDoc = {
language: "javascript",
views: {
by_room: {
map: couchMapReduce.toString()
}
}
};
var chatDB = couchdb.use('chat');
(function insertOrUpdateDesignDoc() {
chatDB.insert(designDoc, '_design/designdoc', function(err) {
if (err) {
if (err.status_code === 409) {
chatDB.get('_design/designdoc', function(err, ddoc) {
if (err) { return console.error(err); }
designDoc._rev = ddoc._rev;
insertOrUpdateDesignDoc();
});
} else {
return console.error(err);
}
}
startServer();
});
}());
});
function startServer() {
var chatDB = couchdb.use('chat');
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
fs.readFile(__dirname + '/index.html', function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
var chat = io.of('/chat');
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.emit('serverMessage', 'You said: ' + content);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
var messageDoc = {
when: Date.now(),
from: username,
room: room,
message: content
};
chatDB.insert(messageDoc, function(err) {
if (err) { console.error(err); }
});
broadcast.emit('serverMessage', username + ' said: ' + message);
});
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
socket.emit('serverMessage', 'Currently logged in as ' + username);
socket.broadcast.emit('serverMessage', 'User ' + username + ' logged in');
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.emit('serverMessage', 'User ' + username + ' disconnected');
});
});
socket.on('join', function(room) {
socket.get('room', function(err, oldRoom) {
if (err) { throw err; }
socket.set('room', room, function(err) {
if (err) { throw err; }
socket.join(room);
if (oldRoom) {socket.leave(oldRoom);}
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
});
socket.emit('serverMessage', 'You joined room ' + room);
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.broadcast.to(room).emit('serverMessage', 'User ' + username + ' joined this room');
});
});
});
});
socket.emit('login');
});
}
Полная версия чат сервера с поддержкой CouchDB и загрузкой файлов на сервер.
Установим новые зависимости через package.json для поддержки буферизации загруженного на сервер файла картинки.
package.json
{
"name": "couch-socketio-chat",
"version": "1.0.0",
"dependencies": {
"nano": "3.1.x",
"socket.io": "0.9.x",
"formidable": "1.0.x",
"bufferedstream": "1.0.x"
}
}
npm install
var url = require('url');
var nano = require('nano');
var couchdb = nano('https://myiriscouchserver.iriscouch.com');
var formidable = require('formidable');
var BufferedStream = require('bufferedstream');
var couchMapReduce = function (doc) {
emit([doc.room, doc.when], doc);
};
couchdb.db.create('chat', function(err) {
if (err && err.status_code !== 412) {throw err;}
couchdb.db.create('users', function(err) {
if (err && err.status_code !== 412) {throw err;}
var designDoc = {
language: "javascript",
views: {
by_room: {
map: couchMapReduce.toString()
}
}
};
var chatDB = couchdb.use('chat');
(function insertOrUpdateDesignDoc() {
chatDB.insert(designDoc, '_design/designdoc', function(err) {
if (err) {
if (err.status_code === 409) {
chatDB.get('_design/designdoc', function(err, ddoc) {
if (err) { return console.error(err); }
designDoc._rev = ddoc._rev;
insertOrUpdateDesignDoc();
});
return;
} else {
return console.error(err);
}
}
startServer();
});
}());
});
});
function startServer() {
console.log('starting server');
var chatDB = couchdb.use('chat');
var userDB = couchdb.use('users');
var httpd = require('http').createServer(handler);
var io = require('socket.io').listen(httpd);
var fs = require('fs');
httpd.listen(4000);
function handler(req, res) {
var username;
if (req.method === 'POST' && req.url.indexOf('/avatar') === 0) {
var currentUserDocRev;
console.log('got avatar');
var bufferedRequest = new BufferedStream();
bufferedRequest.headers = req.headers;
bufferedRequest.pause();
req.pipe(bufferedRequest);
// parse username
console.log(url.parse(req.url).query);
username = url.parse(req.url, true).query.username;
userDB.insert({username: username}, username, function(err, user) {
if (err) {
if (err.status_code === 409) {
userDB.get(username, function(err, user) {
if (err) {
console.error(err);
res.writeHead(500);
return res.end(JSON.stringify(err));
}
currentUserDocRev = user._rev;
bufferedRequest.resume();
});
return;
} else {
res.writeHead(500);
return res.end(JSON.stringify(err));
}
}
console.log('username inserted, rev = ', user.rev);
currentUserDocRev = user.rev;
console.log('currentUserDocRev:', currentUserDocRev);
bufferedRequest.resume();
});
// handle avatar upload
var form = new formidable.IncomingForm();
form.encoding = 'utf8';
form.parse(bufferedRequest);
form.onPart = function(part) {
if (part.name !== 'avatar') {return;}
var attachment = userDB.attachment.insert(username, 'avatar', null, part.mime, {rev: currentUserDocRev});
part.pipe(attachment);
attachment.on('error', function(err) {
console.error(err);
res.writeHead(500);
return res.end(JSON.stringify(err));
});
attachment.on('end', function() {
res.end();
});
};
} else if (req.url.indexOf('/avatar') === 0){
// serve the avatar
username = url.parse(req.url, true).query.username;
userDB.attachment.get(username, 'avatar').pipe(res);
} else {
// serve the index page
fs.readFile(__dirname + '/index.html',
function(err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
}
);
}
}
function sendBackLog(socket, room) {
var getOptions = {
start_key: JSON.stringify([room, 9999999999999]),
end_key: JSON.stringify([room, 0]),
limit: 10,
descending: true
};
chatDB.get('_design/designdoc/_view/by_room', getOptions, function(err, results) {
var messages = results.rows.reverse().map(function(res) {
return res.value;
});
socket.emit('backlog', messages);
});
}
var chat = io.of('/chat');
chat.on('connection', function (socket) {
socket.on('clientMessage', function(content) {
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
socket.get('room', function(err, room) {
if (err) { throw err; }
var broadcast = socket.broadcast;
var message = content;
if (room) {broadcast.to(room);}
var messageDoc = {
when: Date.now(),
from: username,
room: room,
message: content
};
socket.emit('serverMessage', messageDoc);
chatDB.insert(messageDoc, function(err) {
if (err) { console.error(err); }
});
broadcast.emit('serverMessage', messageDoc);
});
});
});
socket.on('login', function(username) {
socket.set('username', username, function(err) {
if (err) { throw err; }
var message = {
from: username,
message: 'Logged in',
when: Date.now()
};
socket.emit('serverMessage', message);
socket.broadcast.emit('serverMessage', message);
sendBackLog(socket, null);
});
});
socket.on('disconnect', function() {
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
var message = {
from: username,
message: 'disconnected',
when: Date.now()
};
socket.broadcast.emit('serverMessage', message);
});
});
socket.on('join', function(room) {
socket.get('room', function(err, oldRoom) {
if (err) { throw err; }
socket.set('room', room, function(err) {
if (err) { throw err; }
socket.join(room);
if (oldRoom) {socket.leave(oldRoom);}
socket.get('username', function(err, username) {
if (! username) {username = socket.id;}
var message = {
from: username,
message: 'joined room ' + room + '. Fetching backlog...',
when: Date.now()
};
socket.emit('serverMessage', message);
socket.broadcast.to(room).emit('serverMessage', message);
});
sendBackLog(socket, room);
});
});
});
socket.emit('login');
});
}
//------------------------------------------------------------------
Работа с MongoDB
Установите базу данных MongoDB
Установите модуль Mongoose для работы с базой данных MongoDB через Node.js
npm install mongoose
Используем для дальнейшей работы package.json
{
'name': 'application-name',
'version': '0.0.1',
'private': true,
'dependecies': {
'express': '2.5.11',
'jade': '>=0.0.1',
'mongoose': '>=2.7.0'
}
}
npm install
Соединение с базой данных MongoDB
var dbURL = 'mongodb://localhost/database';
var db = require('mongoose').connect(dbURL);
Создание Shema - схемы полей колонок базы данных
var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
username: String,
name: String,
password: String
});
module.exports = UserSchema;
Создание модели базы данных
var mongoose = require('mongoose');
var UserSchema = require('../schemas/user.js');
var User = mongoose.model('User', UserSchema);
module.exports = User;
Создание прослойки loadUser
var User = require('../../data/models/user');
function loadUser(request, response, next){
User.findOne({username: request.params.name}, function(error, user){
if (error){return next(error);}
if (! user){retunr response.send('Not found'm 404);}
request.user = user;
next();
});
}
module.exports = loadUser;
Напишем Routes
/*
* User Routes
*/
var User = require('../data/models/user');
var notLoggedIn = require('./middleware/not_logged_in');
var loadUser = require('./middleware/load_user');
var restrictUserToSelf = require('./middleware/restrict_user_to_self');
module.exports = function(app) {
app.get('/users', function(req, res, next){
var page = req.query.page && parseInt(req.query.page, 10) || 0;
User.find({})
.sort('name', 1)
.skip(page * maxUsersPerPage)
.limit(maxUsersPerPage)
.exec(function(err, users) {
if (err) {return next(err);}
res.render('users/index', {title: 'Users', users: users, page: page});
});
});
app.get('/users/new', notLoggedIn, function(req, res) {
res.render('users/new', {title: "New User"});
});
app.get('/users/:name', loadUser, function(req, res, next){
res.render('users/profile', {title: 'User profile', user: req.user});
});
app.post('/users', notLoggedIn, function(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) {
if (err) {return next(err);}
if (user) {return res.send('Conflict', 409);}
User.create(req.body, function(err) {
if (err) {return next(err);}
res.redirect('/users');
});
});
});
app.del('/users/:name', loadUser, restrictUserToSelf, function(req, res, next) {
req.user.remove(function(err) {
if (err) { return next(err); }
res.redirect('/users');
});
});
};
Сортировку полученных из базы MongoDB значений вы можете производить так
User.find({})
.sort('age', -1)
.sort('name', 1)
.exec(function(err, users) { ...
Напишем шаблон index.jade
h1 Users
p
a(href="/users/new") Create new profile
ul
- for(var username in users) {
li
a(href="/users/" + encodeURIComponent(username))= users[username].name
- };
- if (page > 0) {
a(href="?page=" + (page - 1)) Previous
- }
a(href="?page=" + (page + 1)) Next
MongoDB позволяет выполнять разные операции валидации, сортировки, подстановки данных по умолчанию, а также ссылаться на другие данные.