среда, 31 июля 2013 г.

Коротко о Backbone

Установка Backbone.js

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
         <script src="js/underscore.js"></script>
         <script src="js/jquery.js"></script>
         <script src="js/backbone.js"></script>
        <title>Backbone</title>
    </head>
    <body>
    </body>
</html>

Теперь откройте HTML-файл и наберите в консоли:

Backbone

Вы увидите следующий результат:
























Представление данных в JavaScript в виде объекта

var Person = function(config){
    this.name = config.name;
    this.age = config.age;
    this.occupation = config.occupation;
};

Person.prototype.work = function(){
    return this.name + ' is working.';
};

var person = new Person({name: "Mohit Jain", age: 25, occupation: "Software Developer"});

person.name;          // выведет значение name объекта person
person.age;             // выведет значение age объекта person
person.occupation;  // выведет значение occupation объекта person
person.work();        // выполнится функция work объекта person

При наборе данного кода в консоли вы увидите:















Создание Model

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 23,
        occupation: 'Worker'
    },
    work: function(){
        return this.get('name') + ' is working.';
    }
});

var person = new Person({name:"Taroon Tyagi", age: 26, occupation: "Graphics Designer"});

// Или var person = new Person(); для установки значений, прописанных по умолчанию в defaults

person.get('name');       // выведет значение name прописанное по умолчанию для объекта person
person.get('age');          // выведет значение age прописанное по умолчанию для объекта person
person.get('occupation'); // выведет значение occupation прописанное по умолчанию для объекта person

Установка новых значений для объекта Model

person.set('name', 'Taroon Tyagi');             // установит новое значение name для объекта person
person.set('age', 26);                                // установит новое значениеage для объекта person
person.set('occupation', 'Graphics Designer'); // установит новое значение occupation для объекта person

person.set({name:"Taroon Tyagi", age: 26, occupation: "Graphics Designer"}); // Установит сразу новый набор значений для объекта person

Вывод всех значений из объекта Model в формате объекта JSON

person.toJSON(); // вернет все свойства данного объекта





































Валидация данных перед вставкой в Model

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 23,
        occupation: 'worker'
    },

    validate: function(attributes){
        if ( attributes.age < 0){
            return 'Age must be positive.';
        }

        if ( !attributes.name ){
            return 'Every person must have a name.';
        }
    },

    work: function(){
        return this.get('name') + ' is working.';
    }
});

var person = new Person({name: "Mohit Jain", age: -1, occupation: "Software Developer"}) // Такой способ добавления данных при создании объекта модели не вызовет срабатывания метода validate. Метод validate будет вызван только при использовании метода set()
person.get('age') // вернет значение -1

var person = new Person();
person.set('age', -1) // Попытка установить значение -1 при прохождении валидации в методе validate вернет false и значение установлено не будет
person.set('age', 18) // Попытка установить значение 18 при прохождении валидации в методе validate вернет true и значение установлено будет

Как увидеть сообщение об ошибке при прохождении валидации?

Для этого необходимо добавить обработчик события on('error')

person.on('error', function(model, error){
    console.log(error); // Вывод сообщения возвращаемого через return функцией validate
});

Теперь установка неверного значения выведет сообщение в консоль.

person.set("age", -1) // Попытка установить значение -1 при прохождении валидации в методе validate вернет false, значение установлено не будет и в консоль будет выведено сообщение об ошибке возвращенное из функции validate

Создание View

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 23,
        occupation: 'worker'
    }
});

var PersonView = Backbone.View.extend({
   tagName: 'li',

   initialize: function(){
     this.render();
   },

   render: function(){
     this.$el.html( this.model.get('name') + ' ' + this.model.get('age') + ' ' + this.model.get('occupation') );
  }
});


var person = new Person(); // Создаем объект модели
var personView = new PersonView({ model: person }); // Создаем объект View и отрисовываем (рендерим) содержимое внутри элемента el.

$('body').html(personView.el); // Вставляем отрисованное (отрендеренное) содержимое в элементе el на страницу

// personView.el   // Выводит содержимое отрендеренное в данный момент в элементе el нашего объекта View

Использование шаблонов Template при рендеринге View

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest Worker',
        age: 23,
        occupation: 'worker'
    }
});

var PersonView = Backbone.View.extend({
    tagName: 'li',

    my_template: _.template('<strong><%= name %></strong> (<%= age %>) - <%= occupation %>'),

    initialize: function(){
        this.render();
    },

    render: function(){
        this.$el.html( this.my_template( this.model.toJSON() ) );
    }
});

var person = new Person(); // Создаем объект модели
var personView = new PersonView({ model: person }); // Создаем объект View и отрисовываем (рендерим) содержимое внутри элемента el, подставляя содержимое в шаблон  my_template
$('body').html(personView.el); // Вставляем отрисованное (отрендеренное) содержимое в элементе el на страницу

// personView.el   // Выводит содержимое отрендеренное в данный момент в элементе el нашего объекта View

Усовершенствуем использование шаблонов Template при рендеринге View

Поместим код шаблона в HTML-код страницы внутрь тэгов <script>

<script id="personTemplate" type="text/template">
    <strong><%= name %></strong> (<%= age %>) - <%= occupation %>
</script>

И будем брать содержимое для шаблона из этого кода

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest Worker',
        age: 23,
        occupation: 'worker'
    }
});

var PersonView = Backbone.View.extend({
    tagName: 'li',

    my_template: _.template( $('#personTemplate').html()),

    initialize: function(){
        this.render();
    },

    render: function(){
        this.$el.html( this.my_template( this.model.toJSON() ) );
    }
});

var person = new Person(); // Создаем объект модели
var personView = new PersonView({ model: person }); // Создаем объект View и отрисовываем (рендерим) содержимое внутри элемента el, подставляя содержимое в шаблон  my_template
$('body').html(personView.el); // Вставляем отрисованное (отрендеренное) содержимое в элементе el на страницу

// personView.el   // Выводит содержимое отрендеренное в данный момент в элементе el нашего объекта View

Создание Collection

// Person Model
var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 30,
        occupation: 'worker'
    }
});

// A List of People
var PeopleCollection = Backbone.Collection.extend({
    model: Person
});

// The View for a Person
var PersonView = Backbone.View.extend({
    tagName: 'li',

    template: _.template( $('#personTemplate').html()),

    initialize: function(){
        this.render();
    },

    render: function(){
        this.$el.html( this.template(this.model.toJSON()));
    }
});



var person = new Person(); // Создаем объект модели
var personView = new PersonView({ model: person }); // Создаем объект View
var peopleCollection = new PeopleCollection(); // Создаем объект Collection

peopleCollection.add(person); // Добавляем объект модели в коллекцию

var person2 = new Person({name: "Mohit Jain", age: 25, occupation: "Software Developer"}); // Создаем второй объект модели
var personView2 = new PersonView({ model: person2 }); / Создаем второй объект View

peopleCollection.add(person2); // Добавляем второй объект модели в коллекцию

console.log(peopleCollection); // Выводи в консоль все содержимое коллекции
console.log(peopleCollection.toJSON()); // Выводи в консоль все содержимое коллекции в формате JSON

































Другой способ добавления объектов Model в Collection

// Person Model
var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 30,
        occupation: 'worker'
    }
});

// A List of People
var PeopleCollection = Backbone.Collection.extend({
    model: Person
});

// The View for a Person
var PersonView = Backbone.View.extend({
    tagName: 'li',

    template: _.template( $('#personTemplate').html()),

    initialize: function(){
        this.render();
    },

    render: function(){
        this.$el.html( this.template(this.model.toJSON()));
    }
});

var peopleCollection = new PeopleCollection([
    {
        name: 'Mohit Jain',
        age: 26
    },
    {
        name: 'Taroon Tyagi',
        age: 25,
        occupation: 'web designer'
    },
    {
        name: 'Rahul Narang',
        age: 26,
        occupation: 'Java Developer'
    }
]);


Создание View для вывода данных из Collection

// Person Model
var Person = Backbone.Model.extend({
    defaults: {
        name: 'Guest User',
        age: 30,
        occupation: 'worker'
    }
});

// A List of People
var PeopleCollection = Backbone.Collection.extend({
    model: Person
});

// View for all people
var PeopleView = Backbone.View.extend({
    tagName: 'ul',
    render: function(){
        this.collection.each(function(person){
            var personView = new PersonView({ model: person });
            this.$el.append(personView.render().el); // calling render method manually..
        }, this);
        return this; // возвращаем this для создания цепочки вызовов
    }
});

// The View for a Person
var PersonView = Backbone.View.extend({
    tagName: 'li',
    template: _.template($('#personTemplate').html()),
    // initialize - данная функция отсюда исчезла, поэтому нам нужно вызвать метод render вручную
    render: function(){
        this.$el.html( this.template(this.model.toJSON()));
        return this;  // возвращаем this из метода render
    }
});

var peopleCollection = new PeopleCollection([
    {
        name: 'Mohit Jain',
        age: 26
    },
    {
        name: 'Taroon Tyagi',
        age: 25,
        occupation: 'web designer'
    },
    {
        name: 'Rahul Narang',
        age: 26,
        occupation: 'Java Developer'
    }
]);

var peopleView = new PeopleView({ collection: peopleCollection }); // Создание объекта PeopleView

$(document.body).append(peopleView.render().el);   // Добавление peopleView на страницу






















Упрощение загрузки Template во View из HTML-кода

Обычно шаблон Template располагается в HTML-коде внутри тэгов <script>

<script id="personTemplate" type="text/template">
    <strong><%= name %></strong> (<%= age %>) - <%= occupation %>
</script>

Данный шаблон поступает по View так:

template: _.template( $('#personTemplate').html()),

Однако мы можем создать некую независимую функцию-помощьник, которая позволит гораздо проще загружать код шаблона в любой View

var template = function(id) {
    return _.template( $('#' + id).html());
};

Таким образом в каждом View мы может использовать эту функцию для загрузки шаблонов так:

template: template('personTemplate'),

Использование Namespacing для организации кода

Person
PersonView
PeopleCollection
PeopleView

// Теперь тоже самое с использованием namespace

App.Models.Person
App.Views.PersonView
App.Collections.PeopleCollection
App.Views.PeopleView

// Данный код мы можем спокойно сократить до такого состояния

App.Models.Person
App.Views.Person
App.Collections.People
App.Views.People

Теперь используя Namespacing весь наш код мы можем записать так:

(function(){  // Весь код мы записываем в формате модуля, чтобы избежать засорения глобального пространства имен

    window.App = { // созданим namespce App для нашего приложения
        Models: {},
        Collections: {},
        Views: {}
    };

    window.template = function(id){
        return _.template( $('#' + id).html());
    };


    // Person Model
    App.Models.Person = Backbone.Model.extend({   // Модель Person помещается в пространство имен App нашего приложения
        defaults: {
            name: 'Guest User',
            age: 30,
            occupation: 'worker'
        }
    });

    // A List of People
    App.Collections.People = Backbone.Collection.extend({ // Коллекция People помещается в пространство имен App нашего приложения
        model: App.Models.Person  // Модель для коллекции берется из пространства имен App нашего приложения
    });


    // View for all people
    App.Views.People = Backbone.View.extend({ // View People помещается в пространство имен App нашего приложения
        tagName: 'ul',

        render: function(){
            this.collection.each(function(person){
                var personView = new App.Views.Person({ model: person });
                this.$el.append(personView.render().el);
            }, this);

            return this;
        }
    });

    // The View for a Person
    App.Views.Person = Backbone.View.extend({ // View Person помещается в пространство имен App нашего приложения
        tagName: 'li',

        template: template('personTemplate'),
        render: function(){
            this.$el.html( this.template(this.model.toJSON()));
            return this;
        }
    });

    var peopleCollection = new App.Collections.People([
        {
            name: 'Mohit Jain',
            age: 26
        },
        {
            name: 'Taroon Tyagi',
            age: 25,
            occupation: 'web designer'
        },
        {
            name: 'Rahul Narang',
            age: 26,
            occupation: 'Java Developer'
        }
    ]);


    var peopleView = new App.Views.People({ collection: peopleCollection });
    $(document.body).append(peopleView.render().el);

})();

Создание событий Events

// The View for a Person
App.Views.Person = Backbone.View.extend({
    tagName: 'li',
    template: template('personTemplate'),

    initialize: function(){
        this.model.on('change', this.render, this);
    },

    events: {
     'click' : 'showAlert',
     'click .edit' : 'editPerson',
     'click .delete' : 'DestroyPerson'
    },

    showAlert: function(){
        alert("You clicked me");
    },

    editPerson: function(){
        var newName = prompt("Please enter the new name", this.model.get('name'));
        this.model.set('name', newName);
    },

    DestroyPerson: function(){
        this.model.destroy();
    },


    render: function(){
        this.$el.html( this.template(this.model.toJSON()));
        return this;
    }
});

Связывание событий с элементами формы

<form id="addPerson" action="">
        <input type="text" placeholder="Name of the person">
        <input type="submit" value="Add Person">
</form>

App.Views.AddPerson = Backbone.View.extend({
    el: '#addPerson',  # referencing the form itself.

    events: {
        'submit': 'submit'
    },

    submit: function(e){
        e.preventDefault();
        var newPersonName = $(e.currentTarget).find('input[type=text]').val();
        var person = new App.Models.Person({ name: newPersonName });
        this.collection.add(person);
    }
});

// View for all people
App.Views.People = Backbone.View.extend({
    tagName: 'ul',

    initialize: function(){
        this.collection.on('add', this.addOne, this);
    },
    render: function(){
        this.collection.each(this.addOne, this);
        return this;
    },

    addOne: function(person){
        var personView = new App.Views.Person({ model: person });
        this.$el.append(personView.render().el);
    }
});

var addPersonView = new App.Views.AddPerson({ collection: peopleCollection });
peopleView = new App.Views.People({ collection: peopleCollection });
$(document.body).append(peopleView.render().el);

Создание Router

(function(){

    window.App = {
        Models: {},
        Collections: {},
        Views: {},
        Router: {}
    };

    App.Router = Backbone.Router.extend({
        routes: {
            '': 'index',
            'show/:id': 'show',
            'download/*random': 'download',
            'search/:query': 'search',
            '*default': 'default'
        },

        index: function(){
            $(document.body).append("Index route has been called..");
        },

        show: function(id){
            $(document.body).append("Show route has been called.. with id equals : "   id);
        },

        download: function(random){
            $(document.body).append("download route has been called.. with random equals : "   random);
        },

        search: function(query){
            $(document.body).append("Search route has been called.. with query equals : "   query);
        },

        default: function(default){
            $(document.body).append("This route is not hanled.. you tried to access: "   default);

        }
       
    });

    new App.Router;
    Backbone.history.start();

})();

вторник, 30 июля 2013 г.

Для чего может использоваться данная функция? Можно ли ее улучшить и как?

Для чего может использоваться данная функция? Можно ли ее улучшить и как?

function magic(object) {

function F() {}

F.prototype = object;

return new F();

}

Решение.

// Функция magic используется для создания нового объекта, которы содержит ссылку на все свойства объекта родителя. Таким образом объект потомок наследует все свойства родительского объекта.

function magic(object) { // Функция magic принимает объект в качестве аргумента.

function F() {} // Создается функция-конструктор объектов F.

F.prototype = object; // Прототип функции-конструктора F ссылается на переданный в качестве аргумента объект object

return new F(); // Функция magic возвращает новый объект, созданный функцией-конструктором F, который содержит ссылки на все свойства объекта object.

}

На странице определена глобальная пользовательская функция addEventListener, которая не позволяет добавлять обработчики событий на window, а вам необходимо отслеживать события именно на window (например, onresize). Ваши действия?

Ваш скрипт подключается на страницу, к которой вы не имеете доступа. На странице определена глобальная пользовательская функция addEventListener, которая делает что угодно, но не позволяет добавлять обработчики событий на window, а вам необходимо отслеживать события именно на window (например, onresize). При этом синтаксис вида window.onresize напрямую использовать нельзя, т.к. это может нарушить работу пользовательских скриптов. Ваши действия?

Решение.

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

Element.prototype.addEventListener.call(window, args);
Window.prototype.addEventListener.call(window, args);
Document.prototype.addEventListener.call(window, args);

Чем отличаются операторы сравнения "==" и "==="?

Чем отличаются операторы сравнения "==" и "==="?

Оператор "==" производит сранение двух значений с автоматически приведением типа двух значений к единому общему типу.
Таким образом значения 1 == "1" будут равны.

Оператор "===" производит сранение двух значений и по типу и по значению.
Таким образом значения 1 === "1" будут неравны, поскольку тип первого значения - это число, а второго - это строка.

Объясните, для чего предназначена и каким образом работает следующая функция:

Объясните, для чего предназначена и каким образом работает следующая функция:

function bind(method, context) {
      var args = Array.prototype.slice.call(arguments, 2);
      return function() {
            var a = args.concat(
                               Array.prototype.slice.call(arguments, 0));
            return method.apply(context, a);
      }
}

Решение.

 // Функция bind предназначена для связывания метода с объектом, для того, чтобы этот метод можно было вызвать применительно к данному объекту.
function bind(method, context) { // Функция bind принимает 2 аргумента: method - функция или метод, какого-либо объекта и context - объект для которого быдет вызвана данная функция или метод.
      var args = Array.prototype.slice.call(arguments, 2); // Поскольку функция bind может принимать более двух аргументов, то мы вырезаем помещаем все аргументы, идущие после аргументов method и context в отдельный массив args. Поскольку arguments не являются обычным массивом и потому не имеет метода slice, то мы заимствуем метод slice из Array.prototype.
      return function() { // Функция bind возвращает безымянную (анонимную) функцию, которая в случае своего вызова выполнит вызов переданной функции method применительно к переданному объекту context.
            var a = args.concat(Array.prototype.slice.call(arguments, 0)); // Полученный ранее массив аргументов, идущих после method и context мы объединяем со всем массивом аргументов, переданных в возвращаемую безымянную функцию, если они вдруг будут переданы. Поскольку arguments не являются обычным массивом, то мы опять заимствуем метод slice из Array.prototype.
            return method.apply(context, a); // Безымянная функция при своем вызове возвратит результат от выполнения переданной в качестве аргумента функции method, вызванной применительно к переданному объекту context. При вызове функции method в нее будут переданы полученные ранее аргументы в формате массива args, поскольку используется метод apply.
      }
}

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

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

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

Решение.

Вариант 1.

Вызов функции 1 раз в несколько секунд.

function delayBeforeNextCall (func, delayTime) {
    var isFunctionReadyForNextCall = true;
    return function () {
        if (isFunctionReadyForNextCall) {
            isFunctionReadyForNextCall = false;
            func();
            setTimeout(function(){
                isFunctionReadyForNextCall = true;
            }, delayTime);
        }
    }
}

var frequentFunction = function () {
    console.log('OK');
}

frequentFunction = delayBeforeNextCall(frequentFunction, 5000);

document.body.onclick = frequentFunction;


Вариант 2.

Вызов функции N раз в несколько секунд.

function delayBeforeNextCall (func, delayTime, maxNumberOfCalls) {
    var isFunctionReadyForNextCall = true;
    var numberOfCalls = 0;
    return function () {
        if (isFunctionReadyForNextCall) {
            if (numberOfCalls < maxNumberOfCalls) {
                numberOfCalls++;
                func();
            } else {
                isFunctionReadyForNextCall = false;
                numberOfCalls = 0;
                setTimeout(function(){
                    isFunctionReadyForNextCall = true;
                }, delayTime);
            }
        }
    }
}

var frequentFunction = function () {
    console.log('OK');
}

frequentFunction = delayBeforeNextCall(frequentFunction, 5000, 5);

document.body.onclick = frequentFunction;

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

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

Вариант 1. Создание картинки, в результате загрузки которой на сервер будут переданы наши данные.

var image = document.createElement('img');
img.id = 'dummyImage';
img.src = '/send/dummy.gif?data1=one&data2=two';
img.onload = function(){
    document.body.removeChild(document.getElementById('dummyImage'));
    alert('Данные на сервер отправлены.');
};
document.body.appendChild(img);

Вариан 2. Создание iFrame, в результате загрузки содержимого которой на сервер будут переданы наши данные.

var iframe = document.createElement('iframe');
iframe.src = '/send/dummy.gif?data1=one&data2=two';
document.body.appendChild(iframe);

Вариант 3. Создание тэга script, загружающего JavaScript-файл с сервера.

var script = document.createElement('script');
script.src = '/send/dummy.gif?data1=one&data2=two';
document.head.appendChild(script);

Вариант 4. Отправка AJAX-запрос через создание объекта XMLHttpRequest.

var data = 'data1=one&data2=two';
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://site.com/', true);
xhr.onreadystatechange =  function(){
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            alert('Данные на сервер отправлены.');
        } else {
            alert('При отправке запроса на сервер произошла ошибка.');
        }
    }
}
xhr.send(data);

Load content while Scrolling - Загрузка содержимого в процессе скролинга страницы

$(window).scroll(function(){

        if  ($(window).scrollTop() === $(document).height() - $(window).height()) {

           console.log('scroll');
         
           var div = document.createElement('div');
                 div.style.width = '100px';
                 div.style.height = '100px';
                 div.style.backgroundColor = '#ffffff';  
           document.body.appendChild(div);

        }

});

понедельник, 29 июля 2013 г.

Напишите код функции (reversePrint), которая выведет значения переданного ей односвязного списка в обратном порядке (4,3,2,1). Для вывода значений используйте конструкцию console.log.

Дано.

function reversePrint (linkedList) {
                   // ...
         }
       
         var someList = {
                   value: 1,
                   next: {
                            value: 2,
                            next: {
                                      value: 3,
                                      next: {
                                               value: 4,
                                               next: null
                                      }
                            }
                   }
         };
         reversePrint(someList);

Напишите код функции (reversePrint), которая выведет значения переданного ей односвязного списка в обратном порядке (4,3,2,1). Для вывода значений используйте конструкцию console.log.

Решение.

function reversePrint(linkedList) {
    var values = [];
    function getValue (obj) {
        if (obj.next === null) {
            values.push(obj.value);      
            return;
        } else {
            values.push(obj.value);
            getValue(obj.next);
        }    
    }
   
    getValue(linkedList);
   
    values = values.reverse();
    values = values.join();
     
    console.log(values);

}

var someList = {
    value: 1,
    next: {
        value: 2,
        next: {
            value: 3,
            next: {
                value: 4,
                next: null
            }
        }
    }
};
       
reversePrint(someList);

Напишите JS-функцию, которая будет принимать строку и возвращать отсортированные в алфавитном порядке символы этой строки.

Напишите JS-функцию, которая будет принимать строку и возвращать отсортированные в алфавитном порядке символы этой строки.

Решение.

function sortString (str) {
    var arr = str.split('');
    arr = arr.sort();
    str = arr.join('');
    return str;
}

var str = 'string';

console.log(sortString (str));

С бэкенда приходит массив. Необходимо вывести список источников, сгруппированный по типу. Как вы решите эту задачу?

С бэкенда приходит массив:

  [
      {name: 'Вести.Ру', type: 'tv'},
      {name: 'Итоги', type: 'smi'},
      {name: 'Вести.ФМ', type: 'radio'},
      {name: 'НТВ', type: 'tv'},
      {name: 'Огонёк', type: 'smi'},
      {name: 'Аргументы и факты', type: 'smi'},
      {name: 'ТВ Культура', type: 'tv'},
      {name: 'Коммерсант ФМ', type: 'radio'} ]

Необходимо вывести список источников, сгруппированный по типу. Как вы решите эту задачу?

Решение.

var arr = [
      {name: 'Вести.Ру', type: 'tv'},
      {name: 'Итоги', type: 'smi'},
      {name: 'Вести.ФМ', type: 'radio'},
      {name: 'НТВ', type: 'tv'},
      {name: 'Огонёк', type: 'smi'},
      {name: 'Аргументы и факты', type: 'smi'},
      {name: 'ТВ Культура', type: 'tv'},
      {name: 'Коммерсант ФМ', type: 'radio'}
];

arr = arr.sort(function(a, b){
    if (a.type > b.type) {
        return 1;
    } else if (a.type < b.type) {
        return -1;  
    } else {
        return 0;
    }
});

console.log(arr);

Реализуйте конструктор RandomNumber, экземпляры которого при каждом обращении будут возвращать случайное число от 0 до 100.

Реализуйте конструктор RandomNumber, экземпляры которого при каждом обращении будут возвращать случайное число от 0 до 100. Пример:

var random = new RandomNumber();

alert(random); // 46
alert(random); // 87
alert('First value: ' + random + ', second value: ' + random); // First value: 64, second value: 5

Решение.

function RandomNumber (){}

RandomNumber.prototype.valueOf = RandomNumber.prototype.toString = function(){
    return parseInt(Math.random() * 100);
};

var random =  new RandomNumber();

alert(random);
alert(random);
alert('First value: ' + random + ', second value: ' + random);

среда, 24 июля 2013 г.

Require JS with Backbone File Structure

File Structure
├── imgs
├── css
│   └── style.css
├── templates
│   ├── projects
│   │   ├── list.html
│   │   └── edit.html
│   └── users
│       ├── list.html
│       └── edit.html
├── js
│   ├── libs
│   │   ├── jquery
│   │   │   ├── jquery.min.js
│   │   ├── backbone
│   │   │   ├── backbone.min.js
│   │   └── underscore
│   │   │   ├── underscore.min.js
│   ├── models
│   │   ├── users.js
│   │   └── projects.js
│   ├── collections
│   │   ├── users.js
│   │   └── projects.js
│   ├── views
│   │   ├── projects
│   │   │   ├── list.js
│   │   │   └── edit.js
│   │   └── users
│   │       ├── list.js
│   │       └── edit.js
│   ├── router.js
│   ├── app.js
│   ├── main.js  // Bootstrap
│   ├── order.js //Require.js plugin
│   └── text.js  //Require.js plugin
└── index.html

вторник, 23 июля 2013 г.

Command Pattern

(function(){
 
  var CarManager = {

      requestInfo: function( model, id ){
        return "The information for " + model + " with ID " + id + " is foobar";
      },

      buyVehicle: function( model, id ){
        return "You have successfully purchased Item " + id + ", a " + model;
      },
     
      arrangeViewing: function( model, id ){
        return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
      }
   
    };
   
})();


CarManager.execute = function ( name ) {
    return CarManager[name] && CarManager[name].apply( CarManager, [].slice.call(arguments, 1) );
};


CarManager.execute( "arrangeViewing", "Ferrari", "14523" );
CarManager.execute( "requestInfo", "Ford Mondeo", "54323" );
CarManager.execute( "requestInfo", "Ford Escort", "34232" );
CarManager.execute( "buyVehicle", "Ford Escort", "34232" );

JavaScript Cheat Sheet

Types of variables

var i = 123; integer
var f=0.2 real number
var t="texte" string
var a=[1, "deux", 'trois'] array
var m={1:"un", "deux":2} map, associative array
function x() {} object

Event handlers

onAbort loading stopped
onBlur focus lost
onChange content modified
onClick clicked
onDblClick clicked twice
onDragDrop moved
onErrornot loaded
onFocus focus entered
onKeyDown key depressed
onKeyPress key pressed
onKeyUp key released
onLoad just after loading
onMouseDown mouse button depressed
onMouseMove mouse moved
onMouseOut mouse exited
onMouseOver mouse on the element
onMouseUp mouse button released
onReset reset form button clicked
onResize size of page changed
onSelect element selected
onSubmit submit form button clicked
onUnload page exited

Methodes of object (inherited by all objects)

toString() convert to a string
toLocaleString() convert to a localized string
valueOf() get the value

Date methods

new Date() constructor, arguments: milliseconds, string, list
getDate() day of the month
getDay() day of the week
getTime() number of milliseconds since 1/1/1970
getYear() and getMonth/Hour/Minutes/Seconds

String methods

charAt() character at the given position
charCodeAt() code of a character
concat() concatenate with the argument
indexOf() position of a character
lastIndexOf() position from the end
localeCompare() localized comparison
matchapply a regular expression()
replace() replace a substring
search() search a substring
slice() extract a part
split() cut to build an array with parts
substring() extract a part
toLowerCase() convert to lowercase
toUpperCase() convert to uppercase
toLocaleLowerCase() localized lowercase
toLocaleUpperCaselocalized uppercase ()

Array, index and methods

a["one"]=1 assignment by indice
a.one=1 assignment by attribute
delete a["one"] deletion by indice
delete a.one deletion by attribute
for(var k in a) {} iteration on the content
concat() add a second array
join() concatenate the elements into a string
push() add an element
pop() get and remove the last element
reverse() invert the order of elements
shift() insert an element at start
slice() extract a sub-array
spliceinsert an array ()
sort() sort the elements
toString() return the array as a string
unshift() get and remove the first element

Number methods

new Number() constructor with a decimal/hexa/string argument
toString() convert to a string
toExponential() exponential form
toPrecision() convert to a given number of decimals

Function (is also an object)

function x(a, b) { return y; } declaration
y = x(1, "two") call
var y = new x(1, "two") declaring a instance
x.prototype.methodx =
function() { } adding a method

Built-in functions

eval() evaluate an expression
parseInt() convert a string to an integer
parseFloat() convert a string to a floating number
isNaN() check if the content of a variable is valid
ifFinite() check for overflow
decodeURI() convert to a string
decodeURIComponent()decode a component of the URL
encodeURI() convert to file name
encodeURIComponent()encode a component to URL
escape() convert to URL parameters
unescape() convert parameters to normal string

Regular expressions, suffixes

g global
i case-insensitive
s single line
m multi-lines

Regular expressions, masks


^ start of string
$end of string
(...) grouping
!() but this group
. any character
(x|y) either x or y
[xyz] among x y or z
[^xyz] any but x y or z
a? may holds a once
a+ at least a once
a* zero or several times a
a{5} five times a
a{5,} at least five times a
a{1, 4} a between 1 and 4 times

Module Pattern

;(function(window, someVar, undefined){
    console.log(someVar);
})(window, 'OK');

пятница, 19 июля 2013 г.

Observer Library

function Observer () {
    this.eventList = {};
}

Observer.prototype = {
   
      _isInt: function (x) {
            var y = parseInt(x, 10);
            if (isNaN(y)) {
              return false;
            }
            return x === y && x.toString() === y.toString();
      }
   
    , addEvent: function (eventName, callbackFunction) {      
            // TEST BEGIN =================================================
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of addEvent method must be a string.');
            }
            if (Object.prototype.toString.call(callbackFunction) !== '[object Function]') {
                throw new Error('Error! Observer\'s second argument of addEvent method must be a function.');
            }
            // TEST END =================================================
           
            if (!this.eventList[eventName]) {
                this.eventList[eventName] = [];
            }
            this.eventList[eventName].push(callbackFunction);
            return this;        
     }
   
   , addEventOnceThenRemoveItAfterEmission: function (eventName, callbackFunction) {
            // TEST BEGIN =================================================
            var self = this
                , removeEventAfterEmission;
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of addEventOnceThenRemoveItAfterEmission method must be a string.');
            }
            if (Object.prototype.toString.call(callbackFunction) !== '[object Function]') {
                throw new Error('Error! Observer\'s second argument of addEventOnceThenRemoveItAfterEmission method must be a function.');
            }
            // TEST END =================================================
           
            if (!this.eventList[eventName]) {
                this.eventList[eventName] = [];
            }
            removeEventAfterEmission = function () {
                callbackFunction.apply(this, arguments);
                self.removeEventByIndex(eventName, this.eventList[eventName].indexOf(removeEventAfterEmission));
            }
            this.eventList[eventName].push(removeEventAfterEmission);
            return this;
     }
   
   , removeEventByIndex: function (eventName, index) {
            // TEST BEGIN =================================================
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of removeEventByIndex method must be a string.');
            }
            if (Object.prototype.toString.call(index)  !== '[object Number]') {
                throw new Error('Error! Observer\'s first argument of removeEventByIndex method must be a number.');
            }
            if (!this._isInt(index)) {
                throw new Error('Error! Observer\'s first argument of removeEventByIndex method must be an integer.');
            }
            // TEST END =================================================
           
            if (this.eventList[eventName]) {
                if (index > -1 && index < this.eventList[eventName].length) {
                    Array.prototype.splice.call(this.eventList[eventName], index, 1);
                }
                if (this.eventList[eventName].length === 0) {
                    delete this.eventList[eventName];
                }
            }
            return this;  
      }
     
    , removeAllEventsByName: function (eventName) {
            // TEST BEGIN =================================================
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of removeAllEvents method must be a string.');
            }
            // TEST END =================================================
           
            if (this.eventList[eventName]) {
                delete this.eventList[eventName];
            }
            return this;
      }
   
    , emitEvent: function (eventName) {
            // TEST BEGIN =================================================
            var data
                , eventListFunctionsLength
                , i;
            if (arguments.length > 1) {
                  data = Array.prototype.slice.call(arguments, 1);
            } else if (arguments.length < 1) {
                  throw new Error('Error! You must pass arguments into observer\'s emitEvent method.');
            }
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of emitEvent method must be a string.');
            }
            // TEST END =================================================
           
            if (this.eventList[eventName]) {
                eventListFunctionsLength = this.eventList[eventName].length;
                for (i = 0; i < eventListFunctionsLength; i++) {
                    this.eventList[eventName][i].apply(this, data);
                }
            }
            return this;
      }
   
    , triggerEvent: function (eventName) {
            // TEST BEGIN =================================================
            var data
                , key
                , i;
            if (arguments.length > 1) {
                  data = Array.prototype.slice.call(arguments, 1);
            } else if (arguments.length < 1) {
                  throw new Error('Error! You must pass arguments into observer\'s triggerEvent method.');
            }
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of triggerEvent method must be a string.');
            }
            // TEST END =================================================
           
            for (key in this.eventList) {
                if (key === eventName) {
                    for (i = 0; i < this.eventList[key].length; i++) {
                        this.eventList[key][i].apply(this, data);
                    }
                }
            }
            return this;
      }
     
    , triggerEventByIndex: function (eventName, index) {
            // TEST BEGIN =================================================
            var data
                , key;
            if (arguments.length > 2) {
                  data = Array.prototype.slice.call(arguments, 2);
            } else if (arguments.length < 1 || arguments.length < 2) {
                  throw new Error('Error! You must pass event name and event index into observer\'s triggerEventByIndex method.');
            }
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of triggerEventByIndex method must be a string.');
            }
            if (Object.prototype.toString.call(index)  !== '[object Number]') {
                throw new Error('Error! Observer\'s first argument of triggerEventByIndex method must be a number.');
            }
            if (!this._isInt(index)) {
                throw new Error('Error! Observer\'s first argument of triggerEventByIndex method must be an integer.');
            }
            // TEST END =================================================
           
            for (key in this.eventList) {
                if (key === eventName) {
                    if (index > -1 && index < this.eventList[key].length) {
                        this.eventList[key][index].apply(this, data);
                    }                  
                }
            }
            return this;
      }
     
    , countAllEventsByName: function(eventName) {
            // TEST BEGIN =================================================
            if (Object.prototype.toString.call(eventName)  !== '[object String]') {
                throw new Error('Error! Observer\'s first argument of countAllEventsByName method must be a string.');
            }
            // TEST END =================================================
           
            if (this.eventList[eventName]) {
                return this.eventList[eventName].length;
            }
            return 0;
     }
   
    , countAllEventsByType: function() {
            var eventsLength = 0
                , key;
            for (key in this.eventList) {
                if (this.eventList.hasOwnProperty(key)) {
                    eventsLength++;
                }
            }
            return eventsLength;
     }
   
     // Shortcuts

   , on: function () {
            return this.addEvent.apply(this, arguments);
     }
   
   , once: function () {
            return this.addEventOnceThenRemoveItAfterEmission.apply(this, arguments);
     }
   
   , off: function () {
            return this.removeEventByIndex.apply(this, arguments);
     }
 
   , offAll: function () {
            return this.removeAllEventsByName.apply(this, arguments);
     }
   
   , emit: function () {
            return this.emitEvent.apply(this, arguments);
     }
   
   , trigger: function () {
            return this.triggerEvent.apply(this, arguments);
     }
   
   , triggerByIndex: function () {
            return this.triggerEventByIndex.apply(this, arguments);
     }
   
   , count: function () {
            return this.countAllEventsByName.apply(this, arguments);
     }
   
   , countAll: function () {
            return this.countAllEventsByType.apply(this, arguments);
     }
   
     // Other synonyms
   
     // Add event
   
   , subscribe: function () {
            return this.addEvent.apply(this, arguments);
     }

   , addEventListener: function () {
            return this.addEvent.apply(this, arguments);
     }

   , attach: function () {
            return this.addEvent.apply(this, arguments);
     }

   , one: function () {
            return this.addEventOnceThenRemoveItAfterEmission.apply(this, arguments);
     }
   
     // Remove event
   
   , unsubscribe: function () {
            return this.removeEventByIndex.apply(this, arguments);
     }

   , removeEventListener: function () {
            return this.removeEventByIndex.apply(this, arguments);
     }

   , detach: function () {
            return this.removeEventByIndex.apply(this, arguments);
     }
   
     // Emit event
   
   , publish: function () {
            return this.emitEvent.apply(this, arguments);
     }

   , broadcastEvent: function () {
            return this.emitEvent.apply(this, arguments);
     }

   , broadcast: function () {
            return this.emitEvent.apply(this, arguments);
     }

   , translateEvent: function () {
            return this.emitEvent.apply(this, arguments);
     }

   , notify: function () {
            return this.emitEvent.apply(this, arguments);
     }
   
     // Trigger event
   
   , fireEvent: function () {
            return this.triggerEvent.apply(this, arguments);
     }
   
   , fire: function () {
            return this.triggerEvent.apply(this, arguments);
     }
   
     // Count events
   
   , countEvents: function () {
            return this.countAllEventsByName.apply(this, arguments);
     }
 
   , countAllEvents: function () {
            return this.countAllEventsByType.apply(this, arguments);
     }
   
};

Observer.extend = function (obj) {
    var key;
    for (key in Observer) {
      obj[key] = Observer[key];
    }
};

// TESTS

var observer = new Observer();

observer.addEvent('one', function(){console.log('One event');}); console.log('1: Added One');
observer.addEvent('two', function(){console.log('Two event');}); console.log('2: Added Two');

console.log('3: countAllEventsByName: ' + observer.countAllEventsByName('one'));
console.log('4: countAllEventsByType: ' + observer.countAllEventsByType());

observer.removeEventByIndex('one', 0); console.log('5: Removed One');
   
console.log('6: countAllEventsByName: ' + observer.countAllEventsByName('one'));

observer.removeAllEventsByName('two'); console.log('7: Removed Two');

console.log('8: countAllEventsByName: ' + observer.countAllEventsByName('two'));

console.log('9: countAllEventsByType: ' + observer.countAllEventsByType());

observer.addEvent('three', function(data1){console.log('Three data1: ' + data1);}); console.log('10: Added Three');
observer.addEvent('four', function(data1, data2){console.log('Four data1: ' + data1 + ', data2: ' + data2);}); console.log('11: Added Four');

console.log('12: countAllEventsByName: ' + observer.countAllEventsByName('three'));
console.log('13: countAllEventsByType: ' + observer.countAllEventsByType());

console.log('14: Triggered Three');
observer.triggerEvent('three', 'a', 'b');

console.log('15: Triggered Four');
observer.triggerEvent('four', 'c', 'd');

observer.addEvent('three', function(data1){console.log('Second Three data1: ' + data1);}); console.log('16: Added Second Three');

console.log('17: Triggered Second Three');
observer.triggerEventByIndex('three', 1, 'g');

console.log('18: Emit event Three DOUBLE');

observer.emitEvent('three', 'f');

console.log('19: Synonyms');

console.log('20: countAllEventsByName: ' + observer.count('three'));  
console.log('21: countAllEventsByType: ' + observer.countAll());

observer.off('three', 1); console.log('22: Removed Second Three');
observer.offAll('three');  console.log('23: Removed Three');

console.log('24: countAllEventsByName: ' + observer.count('three'));  
console.log('25: countAllEventsByType: ' + observer.countAll());

console.log('26: Triggered Four');
observer.trigger('four', 'x', 'y');

console.log('27: Emit event Four');

observer.emit('four', 'h', 'k');

observer.on('five', function(){console.log('Five event');}); console.log('28: Added Five 1');
observer.emit('five');

console.log('29: Test addEventOnceThenRemoveItAfterEmission');

observer.addEventOnceThenRemoveItAfterEmission('six', function(data) {console.log('Six data: ' + data);});  console.log('30: Added Six Once');

console.log('31: Emit Six Once');
observer.emit('six', 'OK');

console.log('32: countAllEventsByName: ' + observer.count('six'));

console.log('33: Test Once');

observer.once('six', function(data) {console.log('Six data: ' + data);});  console.log('34: Added Six Once');

console.log('35: Emit Six Once');
observer.emit('six', 'GOOD');

console.log('36: countAllEventsByName: ' + observer.count('six'));

Event List Functions

function EventList () {
    this.eventList = [];
    return this;
}

EventList.prototype.addEvent = function(event){
    this.eventList.push(event);
    return this;
};

EventList.prototype.removeEventByIndex = function(index){
    if (index > -1 && index < this.eventList.length) {
        Array.prototype.splice.call(this.eventList, index, 1);
    } else {
        return false;
    }
};

EventList.prototype.removeAllEvents = function(){
    this.eventList = [];
    return this;
};

EventList.prototype.countEvents = function(){
    return this.eventList.length;
};

EventList.prototype.getEventByIndex = function(index) {
    if (index > -1 && index < this.eventList.length) {
        return this.eventList[index];
    } else {
        return false;
    }
};

EventList.prototype.insertEventByIndex = function(event, index){
    if (index > -1 && index < this.eventList.length) {
        if (index === 0) {
            this.eventList.unshift(event);
        } else if (index > 0 && index <= this.eventList.length - 1) {
            var tempArray = Array.prototype.slice.call(this.eventList, 0, index);
            tempArray.push(event);
            this.eventList = tempArray.concat(Array.prototype.slice.call(this.eventList, index, this.eventList.length));
        } else if (index === eventList.length) {
            this.eventList.push(event);
        }
    } else {
        return false;
    }
};

EventList.prototype.indexOfEvent = function (event) {
    var eventListLength = this.eventList.length;
    while (eventListLength--) {
        if (this.eventList[eventListLength] === event) {
            return eventListLength;
        }
    }
    return false;
};

// TESTS

var events = new EventList();

events.addEvent('one');
events.addEvent('two');
console.log('1: ' + events.countEvents());
events.insertEventByIndex('three', 1);
console.log('2: ' + events.countEvents());
console.log('3: ' + events.getEventByIndex(0));
console.log('4: ' + events.getEventByIndex(1));
console.log('5: ' + events.indexOfEvent('three'));
events.removeEventByIndex(0);
console.log('6: ' + events.countEvents());
console.log('7: ' + events.indexOfEvent('three'));
events.removeAllEvents();
console.log('8: ' + events.countEvents());

среда, 17 июля 2013 г.

Node.js Server code

Глобальные модули 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
    &nbsp;
- }

a(href="?page=" + (page + 1)) Next

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