Статьи

Node.js и Windows Azure: история любви

Node.js и Windows Azure отлично работают вместе. Windows Azure является идеальным хостом для запуска веб-сайтов Node.js, веб-сервисов и других приложений. В моем AzureConf и Visual Studio Live! В этом году я рассказывал историю этой интеграции и подумал, что было бы неплохо задокументировать некоторые демонстрации, которые я использую.

Бесстыдный плагин: я буду выступать с докладом Node on Azure снова в Visual Studio Live! Редмонд в августе. Еще есть время зарегистрироваться с ранней скидкой и дополнительной скидкой докладчику, если вы воспользуетесь следующей ссылкой: http://bit.ly/RDSPK12Reg

Прежде всего: вы можете найти весь код на GitHub и выполнить его. Вам придется использовать собственную подписку Windows Azure, для которой потребуются учетные данные для хранения таблиц, базы данных SQL и других ресурсов, но в противном случае вы сможете использовать код как есть.

Первая демонстрация, которую я показываю, — это приложение Node.js, которое использует express и socket.io для реализации простой доски объявлений — вы вводите имя пользователя и сообщение, и это сообщение сохраняется в базе данных и мгновенно отправляется всем подключенным клиентам. Express предоставляет scaffolding, а socket.io имеет очень простой API для отправки обновлений всем подключенным клиентам через WebSockets, если они поддерживаются, и через другие резервные версии, если нет.

В первой демонстрации базовая база данных — это просто модуль nstore . Он оборачивает файл JSON на диск и обеспечивает несколько простых операций CRUD поверх него. Вот операции GET и POST для доски объявлений, соответственно:

var messages = nstore.new('messages.db', function () {
    console.log('*** Messages database has been initialized');
});

app.get('/', function (req, res) {
    messages.all(function (err, results) {
        if (err) {
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        res.render('messages.jade', {
            pagetitle:'Messages', messages: results
        });
    });
});

app.post('/newmessage', function (req, res) {
    var newmessage = {
        id: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    };
    messages.save(newmessage.id, newmessage, function (err, key) {
        res.redirect('/');
        io.sockets.emit('newmessage', newmessage);
    });
});

В предыдущем коде messages.all извлекает все объекты в локальном файле JSON. Результаты передаются в средство визуализации messages.jade, которое отображает список сообщений. Чтобы сохранить сообщение, messages.save берет идентификатор (который можно использовать для запросов) и объект JSON и сохраняет его в файле. Как только операция сохранения завершается, код отправляет сообщение socket.io всем подключенным клиентам с содержимым сообщения.

Во второй демонстрации я немного запутался, потребовав использовать более готовый к работе механизм хранения. Одним из простых вариантов является Azure Table Storage — он невероятно дешев, а API не намного сложнее, чем у nstore. Вот что требуется для выполнения операций GET и POST с таблицей сообщений в хранилище таблиц Azure:

app.get('/', function (req, res) {
    var query = azure.TableQuery.select().from('messages');
    tableService.queryEntities(query, function (err, results) {
        if (err) {
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        for (var i = 0; i < results.length; ++i)
            results[i].id = results[i].RowKey;
        res.render('messages.jade', {
            pagetitle:'Messages', messages: results
        });
    });
});

app.post('/newmessage', function (req, res) {
    var newmessage = {
        PartitionKey: 'partition',
        RowKey: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    };
    tableService.insertEntity('messages', newmessage, function (err) {
        io.sockets.emit('newmessage', newmessage);
        res.redirect('/');
    });
});

Все в значительной степени одинаково, за исключением того, что теперь нам нужно подумать о явных понятиях ключа раздела и ключа строки. Для отличного разговора, объясняющего, как спроектировать модель базы данных Azure Table Storage, настройтесь на видео dotnetconf от Caitie McCaffrey .

Кстати, нет необходимости размещать предыдущий код в Windows Azure. Вы можете работать с хранилищем таблиц Azure из локального приложения Node.js и переместить само приложение в Windows Azure, только если это имеет смысл в вашем сценарии.

В третьей демонстрации я заменяю базовое хранилище базой данных SQL Server. Хотя я мог бы использовать локальную установку SQL Server, более полезно использовать базу данных, размещенную в Windows Azure. Чтобы получить доступ к базе данных из приложения Node, нам нужен модуль msnodesql , который на момент написания статьи работает только в Windows, поскольку он опирается на собственный клиент SQL Server. (Это единственная точка в презентации, где мне нужно переключиться на виртуальную машину Windows, чтобы продемонстрировать эту точку интеграции.)

app.get('/', function (req, res) {
    sql.query(conn_str, 'SELECT [User] as [user], [Text] as [text] FROM bbs.Message', function (err, results) {
        if (err) {
            console.log(JSON.stringify(err));
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        res.render('messages.jade', {
            pagetitle:'Messages', messages: results
        });
    });
});

app.post('/newmessage', function (req, res) {
    //WARNING: This is subject to horrible SQL injection
    var newmessage = {
        user: req.body.user,
        text: req.body.text
    };
    var values = "'" + newmessage.user + "', '" + newmessage.text + "'";
    sql.queryRaw(
        conn_str, 'INSERT INTO bbs.Message ([User], [Text]) VALUES (' + values + ')', function (err) {
        if (err) {
            console.log('Error: ' + err);
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        io.sockets.emit('newmessage', newmessage);
        res.redirect('/');
    });
});

Если вы попытаетесь запустить этот код локально и нацелиться на базу данных SQL в Azure, вы обнаружите, что брандмауэр не пропускает вас. Действительно, база данных SQL не разрешает удаленные подключения по умолчанию — и вам нужно настроить брандмауэр, чтобы пропускать ваш конкретный диапазон IP-адресов.

образ

Теперь, когда у нас есть интеграция с базой данных SQL Server, мы также можем подключить мобильную службу, которая обращается к той же базе данных, и добиться некоторой дополнительной интеграции между мобильными службами Windows Azure и веб-приложением Node, работающим в Windows Azure. Внедрение мобильной службы выходит за рамки этого поста, но я рекомендую вам ознакомиться с учебником из четырех частей, который я опубликовал пару месяцев назад и который охватывает обширное мультиплатформенное демонстрационное приложение, использующее мобильные службы Windows Azure.

Наконец, последняя демонстрация заменяет хранилище резервных копий SQL Server на MongoDB, размещенное на виртуальной машине в Windows Azure. Это святой Грааль интеграции и открытости Microsoft: вы можете запустить приложение Node на веб-сайте Windows Azure и получить доступ к базе данных MongoDB, которая работает на виртуальной машине Ubuntu (Linux) .

Затем код приложения использует mongoose (Node ODM для MongoDB) для доступа к базе данных следующим образом:

app.get('/', function (req, res) {
    Message.find(function (err, results) {
        if (err) {
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        res.render('messages.jade', {
            pagetitle:'Messages', messages: results
        });
    });
});

app.post('/newmessage', function (req, res) {
    var newmessage = new Message({
        id: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    });
    newmessage.save(function (err) {
        if (err) {
            res.render('error.jade', {pagetitle: 'Error', error: err});
            return;
        }
        io.sockets.emit('newmessage', newmessage);
        res.redirect('/');
    });
});

Объект Message инициализируется с помощью mongoose следующим образом, чтобы отразить схему документа базы данных для сообщений:

var messageSchema = new mongoose.Schema({
        id: String,
        user: String,
        text: String
    });
Message = db.model('Message', messageSchema);

Подводя итог: вы можете найти полный код на GitHub и изучить эти демонстрации. Node и Windows Azure прекрасно подходят, и хотя вы можете запускать приложения Node локально и пользоваться всеми точками интеграции, приведенными в этом посте, вы также можете рассмотреть возможность размещения своего приложения Node — бесплатно — на веб-сайте Windows Azure и ознакомления с Преимущества аутсорсинга — еще один источник беспокойства в области ИТ. И в заключение сделаем «подрывную» заметку: Node является кроссплатформенным, поэтому большинство из вышеперечисленного применимо ко всем облачным провайдерам и операционным системам.