Node.js 体验-存储服务和服务运行时

当我描述如何在Windows Azure承载Node.js应用程序时,将可能引发一个有关如何使用 Windows Azure 服务,如存储、 服务总线、 访问控制等的问题......在Node.js中与windows azure 服务交互是通过 Windows Azure Node.js SDK,这是一个在NPM中可用的模块。 在这篇文章我想描述了如何使用 Windows Azure 存储 (即WAS) 以及服务运行时。

使用Windows Azure 存储

让我们首先看看如何通过 Node.js使用 WAS。在前面的文章中我们知道可以在WindowsAzure 网站上承载 Node.js 应用(即 WAWS) 以及Windows Azure 云计算服务 (即WACS)。理论上,WAWS与一些更多的功能是构建在WACS工作者角色之上的。 因此在这篇文章中我将只演示承载WACS辅助角色。

Node.js 代码可以用于使用WAS当其承载在WAWS 上时。 但由于在 WAWS 中没有的角色,因此在下一节中提到的使用服务运行时代码不能用于 WAWS 节点应用程序。

我们可以使用我后面张贴的我创建的解决方案。或者在 Visual Studio 中创建一个新的工作者角色的 windows azure 项目,添加"node.exe"和"index.js"和安装"express"和"node-sqlserver"模块,设置所有的文件为"Copy always"。

要使用 windows azure 服务, 我们需要Windows Azure Node.js SDK,作为一个名为"azure"的模块可以通过NPM安装。一旦我们下载和安装,我们需要将它们包括在我们的工作者角色项目,设置它们为"Copy always"。

您可以使用我在我的最后一篇中提到的"Copyall always"工具来更新当前工作者角色项目文件。您还可以在这里找到此工具的源代码.

Node.js 的 Windows Azure SDK 的源代码可以在其GitHub 页中找到。 它包含两个部分。其中一个是 CLI 工具,提供了Mac 和Linux跨平台命令行包管理WAWS 和 Windows Azure 虚拟机 (即
WAVM) 进行管理。另一个是用于管理和使用不同 windows azure 服务的一个库,包括表、 blob、 队列、 服务总线和服务运行时。我将不涵盖所有的但只将演示如何在这篇文章中使用的表和服务运行时信息。您可以在这里找到此 SDK的完整文档.

回到 Visual Studio,并打开"index.js",让我们继续我们上一篇文章中的,针对 Windows Azure SQL 数据库 (即WASD) 的应用程序。这些代码应该看起来就像这样。

  1: var express = require("express");

  2: var sql = require("node-sqlserver");

  3:

  4: var connectionString = "Driver={SQL Server Native Client 10.0};Server=tcp:ac6271ya9e.database.windows.net,1433;Database=synctile;Uid=shaunxu@ac6271ya9e;Pwd={PASSWORD};Encrypt=yes;Connection
Timeout=30;";

  5: var port = 80;

  6:

  7: var app = express();

  8:

  9: app.configure(function () {

 10:    
app.use(express.bodyParser());

 11: });

 12:

 13: app.get("/", function (req, res) {

 14:    
sql.open(connectionString, function (err, conn) {

 15:         if (err) {

 16:            
console.log(err);

 17:            
res.send(500, "Cannot
open connection.");

 18:        
}

 19:         else {

 20:            
conn.queryRaw("SELECT
* FROM [Resource]", function (err,
results) {

 21:                
if (err) {

 22:                     console.log(err);

 23:                     res.send(500, "Cannot retrieve records.");

 24:                
}

 25:                
else {

 26:                     res.json(results);

 27:                
}

 28:            
});

 29:         }

 30:     });

 31: });

 32:

 33: app.get("/text/:key/:culture", function (req, res) {

 34:    
sql.open(connectionString, function (err, conn) {

 35:         if (err) {

 36:            
console.log(err);

 37:            
res.send(500, "Cannot
open connection.");

 38:        
}

 39:         else {

 40:            
var key = req.params.key;

 41:            
var culture = req.params.culture;

 42:            
var command = "SELECT
* FROM [Resource] WHERE [Key] = '" + key +
"' AND [Culture] = '" + culture + "'";

 43:            
conn.queryRaw(command, function (err, results) {

 44:                
if (err) {

 45:                     console.log(err);

 46:                     res.send(500, "Cannot retrieve records.");

 47:                
}

 48:                
else {

 49:                     res.json(results);

 50:                
}

 51:            
});

 52:        
}

 53:     });

 54: });

 55:

 56: app.get("/sproc/:key/:culture", function (req, res) {

 57:    
sql.open(connectionString, function (err, conn) {

 58:         if (err) {

 59:            
console.log(err);

 60:            
res.send(500, "Cannot
open connection.");

 61:         }

 62:         else {

 63:            
var key = req.params.key;

 64:            
var culture = req.params.culture;

 65:            
var command = "EXEC
GetItem '" + key + "', '" + culture + "'";

 66:            
conn.queryRaw(command, function (err, results) {

 67:                
if (err) {

 68:                     console.log(err);

 69:                     res.send(500, "Cannot retrieve records.");

 70:                
}

 71:                
else {

 72:                     res.json(results);

 73:                
}

 74:            
});

 75:         }

 76:     });

 77: });

 78:

 79: app.post("/new", function (req, res) {

 80:     var key =
req.body.key;

 81:     var culture =
req.body.culture;

 82:     var val =
req.body.val;

 83:

 84:    
sql.open(connectionString, function (err, conn) {

 85:         if (err) {

 86:            
console.log(err);

 87:            
res.send(500, "Cannot
open connection.");

 88:        
}

 89:         else {

 90:            
var command = "INSERT
INTO [Resource] VALUES ('" + key +
"', '" + culture + "', N'" + val +
"')";

 91:            
conn.queryRaw(command, function (err, results) {

 92:                
if (err) {

 93:                     console.log(err);

 94:                     res.send(500, "Cannot retrieve records.");

 95:                
}

 96:                
else {

 97:                     res.send(200, "Inserted Successful");

 98:                
}

 99:            
});

100:         }

101:     });

102: });

103:

104:
app.listen(port);

现在让我们创建一个新函数,将记录从 WASD 复制到表服务。

1.删除名为"resource"的表。

2.创建一个名为"resource"的新表。这 2 个步骤可确保我们有一个空表。

3.从 WASD 中的"resource "表中加载的所有记录。

4.为每个从 WASD加载的记录,将它们插入到一个表。

5.用户完成后进行提示。

要使用表服务,我们需要存储帐户和键,这些可以从开发人员门户网站中找到。选择存储帐户,然后单击管理密钥按钮。

 

然后在我们 Node.js 存储的帐户名称和关键的应用程序中创建两个本地variants。因为我们需要使用 WAS,我们需要导入的azure的模块。另外我还创建另一个变量存储表的名称。

为使用表服务,我需要创建客户存储服务。这是和.NET的Windows Azure SDK非常相似。我下面的代码创建新变量命名"client"和使用"createTableService",指定我的存储的帐户名称和密钥。

 

  1: var azure = require("azure");

  2: var storageAccountName = "synctile";

  3: var storageAccountKey = "/cOy9L7xysXOgPYU9FjDvjrRAhaMX/5tnOpcjqloPNDJYucbgTy7MOrAW7CbUg6PjaDdmyl+6pkwUnKETsPVNw==";

  4: var tableName = "resource";

  5: var client =
azure.createTableService(storageAccountName, storageAccountKey);

现在为 URL"/was /init"创建一个新函数,这样,我们可以通过浏览器触发它。然后在该函数中我们将首先从 WASD加载的所有记录。  

  1: app.get("/was/init", function (req, res) {

  2:     // load all records from windows azure sql
database

  3:    
sql.open(connectionString, function (err, conn) {

  4:         if (err) {

  5:            
console.log(err);

  6:            
res.send(500, "Cannot
open connection.");

  7:         }

  8:         else {

  9:            
conn.queryRaw("SELECT
* FROM [Resource]", function (err,
results) {

 10:                
if (err) {

 11:                     console.log(err);

 12:                     res.send(500, "Cannot retrieve records.");

 13:                
}

 14:                
else {

 15:                     if
(results.rows.length > 0) {

 16:                         // begin to transform the records into table
service

 17:                     }

 18:                
}

 19:            
});

 20:        
}

 21:     });

 22: });

当我们成功加载所有的记录,我们可以开始将它们转换为表服务。首先我需要重新创建的表服务中的表。这可以通过我以前刚刚建立的表的客户端删除和创建表。

  1: app.get("/was/init", function (req, res) {

  2:     // load all records from windows azure sql
database

  3:    
sql.open(connectionString, function (err, conn) {

  4:         if (err) {

  5:            
console.log(err);

  6:            
res.send(500, "Cannot
open connection.");

  7:         }

  8:         else {

  9:            
conn.queryRaw("SELECT
* FROM [Resource]", function (err,
results) {

 10:                
if (err) {

 11:                     console.log(err);

 12:                     res.send(500, "Cannot retrieve records.");

 13:                
}

 14:                
else {

 15:                     if
(results.rows.length > 0) {

 16:                         // begin to transform the records into table
service

 17:                         // recreate the table named 'resource'

 18:                        
client.deleteTable(tableName, function (error) {

 19:                            
client.createTableIfNotExists(tableName, function (error) {

 20:                                 if (error)
{

 21:                                     error["target"] = "createTableIfNotExists";

 22:                                    
res.send(500, error);

 23:                                 }

 24:                                 else {

 25:                                     // transform the records

 26:                                 }

 27:                             });

 28:                         });

 29:                     }

 30:         
       }

 31:            
});

 32:        
}

 33:     });

 34: });

正如您所看到的 azure SDK 提供其回调模式中的方法。事实上,在Node.js 中的几乎所有模块都使用回拨模式。

例如,删除表时我调用"deleteTable"方法,提供表的名称,表被删除或失败时将执行的回调函数。 相应的,azure模块将在POSIX 异步线程池中异步执行表删除操作。并且它完成后将执行的回调函数。这是我们需要在删除函数内部嵌套表创建代码的原因。如果我们执行表创建代码后删除代码然后他们将并行调用。

下一步,为每个记录的 WASD 我创建一个实体,然后插入表服务。最后我将发送到浏览器的响应。

您可以在下面的代码中查找错误吗?我将稍后在这篇文章描述它。

 

  1: app.get("/was/init", function (req, res) {

  2:     // load all records from windows azure sql
database

  3:    
sql.open(connectionString, function (err, conn) {

  4:         if (err) {

  5:            
console.log(err);

  6:            
res.send(500, "Cannot
open connection.");

  7:         }

  8:         else {

  9:            
conn.queryRaw("SELECT
* FROM [Resource]", function (err,
results) {

 10:                
if (err) {

 11:                     console.log(err);

 12:                     res.send(500, "Cannot retrieve records.");

 13:                
}

 14:                
else {

 15:                     if (results.rows.length
> 0) {

 16:                         // begin to transform the records into table
service

 17:                         // recreate the table named 'resource'

 18:                        
client.deleteTable(tableName, function (error) {

 19:                            
client.createTableIfNotExists(tableName, function (error) {

 20:                                 if (error)
{

 21:                                     error["target"] = "createTableIfNotExists";

 22:                                    
res.send(500, error);

 23:                                 }

 24:                                 else {

 25:                                     // transform the records

 26:                                     for (var i = 0;
i < results.rows.length; i++) {

 27:                                         var entity =
{

 28:                                             "PartitionKey": results.rows[i][1],

 29:                                             "RowKey": results.rows[i][0],

 30:                                             "Value": results.rows[i][2]

 31:                                         };

 32:                                        
client.insertEntity(tableName, entity, function (error) {

 33:                                             if (error) {

 34:                                                
error["target"] = "insertEntity";

 35:                                                
res.send(500, error);

 36:                                             }

 37:                                             else {

 38:                                                
console.log("entity inserted");

 39:                                             }

 40:            
                            });

 41:                                     }

 42:                                     // send the

 43:                                    
console.log("all done");

 44:                                    
res.send(200, "All
done!");

 45:                                 }

 46:                             });

 47:                         });

 48:                     }

 49:                
}

 50:            
});

 51:         }

 52:     });

 53: });

现在,我们可以将其发布到云计算试一试。但是,通常我们第一次最好在本地模拟器上测试它。Node.js SDK 中有为本地存储模拟器提供的帐户名称、 密钥和主机地址的三个生成的属性。我们可以使用它们来初始化表服务客户端。我们也需要改变SQL 连接字符串,让它使用我的本地数据库。该代码将更改为如下所示。

  1: // windows
azure sql database

  2: //var
connectionString = "Driver={SQL Server Native Client 10.0};Server=tcp:ac6271ya9e.database.windows.net,1433;Database=synctile;Uid=shaunxu@ac6271ya9e;Pwd=eszqu94XZY;Encrypt=yes;Connection
Timeout=30;";

  3: // sql
server

  4: var connectionString = "Driver={SQL Server Native Client
11.0};Server={.};Database={Caspar};Trusted_Connection={Yes};";

  5:

  6: var azure = require("azure");

  7: var storageAccountName = "synctile";

  8: var storageAccountKey = "/cOy9L7xysXOgPYU9FjDvjrRAhaMX/5tnOpcjqloPNDJYucbgTy7MOrAW7CbUg6PjaDdmyl+6pkwUnKETsPVNw==";

  9: var tableName = "resource";

 10: //
windows azure storage

 11: //var
client = azure.createTableService(storageAccountName, storageAccountKey);

 12: // local
storage emulator

 13: var client = azure.createTableService(azure.ServiceClient.DEVSTORE_STORAGE_ACCOUNT,
azure.ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY,
azure.ServiceClient.DEVSTORE_TABLE_HOST);

现在让我们来运行该应用程序并导航到"localhost:12345/was/init" ,我是在端口12345上承载的。我们可以从我的本地数据库数据找到它转换为本地表服务。

所有的一切看起来都很正常。但我的代码中有 bug。如果我们看看我们会发现它发送响应之前插入了所有记录的 Node.js 命令窗口上,这不是我所期望的。
 

原因是,正如我之前所说的,Node.js 在非阻塞模式中执行所有 IO 操作。我们在插入时,我们并行执行表服务记录插入方法,也并行执行发送响应操作,即使是写在我的逻辑末尾。

正确的逻辑应该是,当所有实体已被都复制到表服务,没有错误,然后我将发送到浏览器的响应,否则我应该发送到浏览器的错误消息。做所以我需要导入另一个名为"async",可帮助我们协调我们异步代码的模块。

安装模块并将其导入代码的开头。然后我们可以将其"forEach"方法用于异步代码的插入表的实体。"ForEach"的第一个参数是数组,它将执行。第二个参数是数组中的每个项的操作。并将调用第三个参数然后执行了所有项目或发生了任何错误。在这里我们可以发送到浏览器的回应。

  1: app.get("/was/init", function (req, res) {

  2:     // load all records from windows azure sql
database

  3:    
sql.open(connectionString, function (err, conn) {

  4:         if (err) {

  5:            
console.log(err);

  6:            
res.send(500, "Cannot
open connection.");

  7:         }

  8:         else {

  9:            
conn.queryRaw("SELECT
* FROM [Resource]", function (err,
results) {

 10:                
if (err) {

 11:                     console.log(err);

 12:                     res.send(500, "Cannot retrieve records.");

 13:                
}

 14:                
else {

 15:                     if
(results.rows.length > 0) {

 16:                         // begin to transform the records into table
service

 17:                         // recreate the table named 'resource'

 18:                        
client.deleteTable(tableName, function (error) {

 19:                            
client.createTableIfNotExists(tableName, function (error) {

 20:                                 if (error)
{

 21:                                     error["target"] = "createTableIfNotExists";

 22:                                    
res.send(500, error);

 23:                                 }

 24:                                 else {

 25:                                    
async.forEach(results.rows,

 26:                                         // transform the records

 27:                                         function (row,
callback) {

 28:                                             var entity
= {

 29:                                                
"PartitionKey": row[1],

 30:                                                
"RowKey": row[0],

 31:                                                
"Value": row[2]

 32:                                             };

 33:                                            
client.insertEntity(tableName, entity, function (error) {

 34:                                                
if (error) {

 35:                                                    
callback(error);

 36:                                                
}

 37:                                                
else {

 38:                                                    
console.log("entity
inserted.");

 39:                                                    
callback(null);

 40:                                                
}

 41:                                            
});

 42:                                         },

 43:                                         // send reponse

 44:                                         function (error)
{

 45:                                             if (error) {

 46:                                                
error["target"] = "insertEntity";

 47:                                                
res.send(500, error);

 48:                                             }

 49:                                             else {

 50:                                                
console.log("all done");

 51:                                                
res.send(200, "All
done!");

 52:                                             }

 53:                                         }

 54:                                     );

 55:                                 }

 56:                             });

 57:                         });

 58:                     }

 59:                
}

 60:            
});

 61:         }

 62:     });

 63: });

它在本地运行,现在我们可以找到的所有实体已都插入后已发送的响应。
 

针对表服务查询实体也是简单的。只需使用"queryEntity"方法中,从表服务客户端和提供的分区键和键的行。我们还可以提供一个复杂的查询条件,例如代码在这里.

在下面的代码中我查询实体由的分区键和键的行,并在响应中返回适当的本地化值。

  1: app.get("/was/:key/:culture", function (req, res) {

  2:     var key =
req.params.key;

  3:     var culture =
req.params.culture;

  4:    
client.queryEntity(tableName, culture, key, function (error,
entity) {

  5:         if (error) {

  6:            
res.send(500, error);

  7:         }

  8:         else {

  9:            
res.json(entity);

 10:        
}

 11:     });

 12: });

然后在本地模拟器上对其进行测试。
 

最后如果我们想要发布到云此应用程序我们应该改变数据库的连接字符串和存储帐户。

有关如何使用 blob 和队列服务以及服务总线的详细信息请参阅MSDN 页面.

使用服务运行时

如我上文所述,我们发表我们对云计算的应用程序之前,我们需要改变我们的代码中的连接字符串和帐户信息。但如果你使用了WACS,服务运行时提供了能够检索配置设置、 终结点和本地资源信息在运行时应该知道。这意味着我们可以有 CSCFG 和CSDEF 的文件中定义这些值,然后运行时应该能够检索适当的值。

例如我们可以添加一些角色设置,虽然属性窗口的作用,指定连接字符串和存储帐户云及本地。
 

还可使用在角色环境对我们的 Node.js 应用程序中定义的终结点。

Node.js SDK 中我们可以得到一个对象从"azure.RoleEnvironment",它提供的功能来检索配置设置和终结点,等等......下面的代码中,我定义的连接字符串变量,然后使用 SDK 来检索和初始化表客户端。

  1: var connectionString = "";

  2: var storageAccountName = "";

  3: var storageAccountKey = "";

  4: var tableName = "";

  5: var client;

  6:

  7: azure.RoleEnvironment.getConfigurationSettings(function (error,
settings) {

  8:     if (error)
{

  9:        
console.log("ERROR: getConfigurationSettings");

 10:        
console.log(JSON.stringify(error));

 11:     }

 12:     else {

 13:        
console.log(JSON.stringify(settings));

 14:        
connectionString = settings["SqlConnectionString"];

 15:        
storageAccountName = settings["StorageAccountName"];

 16:        
storageAccountKey = settings["StorageAccountKey"];

 17:        
tableName = settings["TableName"];

 18:

 19:        
console.log("connectionString =
%s", connectionString);

 20:        
console.log("storageAccountName =
%s", storageAccountName);

 21:        
console.log("storageAccountKey =
%s", storageAccountKey);

 22:        
console.log("tableName = %s", tableName);

 23:

 24:        
client = azure.createTableService(storageAccountName, storageAccountKey);

 25:     }

 26: });

以这种方式,我们不需要修改的地方之间配置代码和云环境因为服务运行时将维护它。

在代码的终点,我们会监听从 SDK 中检索到该端口的应用程序。

  1: azure.RoleEnvironment.getCurrentRoleInstance(function (error,
instance) {

  2:     if (error)
{

  3:        
console.log("ERROR: getCurrentRoleInstance");

  4:        
console.log(JSON.stringify(error));

  5:     }

  6:     else {

  7:        
console.log(JSON.stringify(instance));

  8:         if
(instance["endpoints"] && instance["endpoints"]["nodejs"]) {

  9:            
var endpoint = instance["endpoints"]["nodejs"];

 10:            
app.listen(endpoint["port"]);

 11:         }

 12:         else {

 13:            
app.listen(8080);

 14:        
}

 15:     }

 16: });

但是如果我们测试应用程序现在我们会发现它无法从服务运行时检索的任何值。这是因为默认情况下,向工作者角色类定义此角色的入口点。在 windows azure 环境服务运行时将打开命名的管道到入口点的实例,以便它可以连接到运行时和检索值。但在这种情况下,由于入口点是辅助作用,Node.js 打开该角色内,我们工作者作用类和服务运行,因此我们的 Node.js 应用程序不能使用它之间建立命名的管道。
 

要解决此问题,我们需要打开 azure 项目下的CSDEF 文件,添加一个名为运行时的新元素。然后,添加名为入口点的 Node.js 命令行指定的元素。使Node.js 应用程序会连接到服务运行时,它是能够读取配置。
 

开始在本地 Node.js 我们可以找到它的仿真程序中检索连接中,存储的本地用户帐户。
 

并且,如果我们发布我们对 到azure 的应用程序,然后用WASD 和存储服务通过云的配置工作。
 

总结

在这篇文章我演示了如何使用 Node.js 的Windows Azure SDK 来与存储服务进行交互,特别是表服务。我还演示了如何使用WACS服务运行时,如何检索配置设置和终结点信息。此外,并为向我的 Node.js 提供的服务运行时应用程序需要创建一个条目点 CSDEF 文件中的元素,并设置"node.exe"作为切入点。

我用五个员额来介绍和演示如何在 Windows 平台上,运行Node.js 的应用程序如何使用 Windows Azure Web 站点和Windows Azure 云计算服务工作者角色承载我们的 Node.js 应用程序。我还介绍了如何使用由Windows Azure 平台通过 Windows Azure SDK 的Node.js 提供的其他服务。

Node.js 是一个非常新的、 年轻的网络应用平台。但是,因为它是非常简单且易于学习和部署,以及它利用单个线程非阻塞 IO 模型,Node.js变得越来越受欢迎的 web 应用程序和 web 服务开发特别是对于那些 IO 敏感项目。Node.js是非常擅长扩展,它是在云计算平台上更有用。

在 Windows 平台上的使用Node.js 也是新的。为 SQL 数据库和 Windows Azure SDK 模块则仍在发展和加强。它不支持 SQL参数"节点命令"。它不支持使用存储连接字符串创建"azure"中的存储客户端。但微软正在努力使它们更易于使用,工作上的添加更多特性和功能。

PS,你可以在这里下载源代码。你可以在这里下载"Copyall always"工具的源代码.

希望这些能有帮助

Shaun

本文翻译自:https://geekswithblogs.net/shaunxu/archive/2012/09/24/node.js-adventure---storage-services-and-service-runtime.aspx