Shaun Xu

The Sheep-Pen of the Shaun


News

logo

Shaun, the author of this blog is a software architect with 9 years experience in the .NET world. He hopes to show that software development is more of an art rather than manufacturing. He is into Windows Azure, SQL Azure, WCF and ASP.NET MVC, with his favorite being C#.

Currently Shaun is working at IGT Technology Development (Beijing) Co., Ltd. as the architect of the sbX group responsible for cloud computing platform design and development.

MVP


It has been long time didn’t blog anything since last November. There are many exciting things happened during this period. I attend Microsoft TechED 2012 China and provided a session about Node.js and Windows Azure. I also provided a small but funny session at the Microsoft China Community New Year Party on this January. Then I enjoyed Chinese New Year at Lvshun, a quiet and beautiful city aside Bohai Bay with my wife, where her hometown is.

But I decided to write something when I read this. On February, Microsoft announced that the Windows Azure Store had just expanded availability to new 25 markets, includes Hong Kong SAR, which is the country/region for my Windows Azure account. This means I can try this fantastic feature with my subscription.

 

Windows Azure Store

Windows Azure Store is a brand new service provided by Windows Azure platform. It likes a service market, where you can buy services provided by many third-party publishers. With Windows Azure Store, as a developer we can:

1. Choose from popular app services including: AppDynamics (monitoring and scaling as a service), New Relic (performance management and monitoring), SendGrid (email delivery as a service) and more.

2. Find the data your app needs from data providers like: Dun & Bradstreet (business insights), Loqate (Worldwide address verification and cleansing service), StrikeIron (Phone number verification service; Sales and use tax rates lookup service), and more.

3. Connect and manage it all from the developer portal: The integrated store UI makes it easy to find, buy, manage the services and data you need directly from the portal.  Integrated billing means you store purposes are added directly to your Windows Azure account.  We also provide connection strings and sample code and help get you started fast, as well as usage data from directly within the Windows Azure management portal.

Furthermore, there are other benefit of using Windows Azure Store.

1. It utilizes similar UI as Windows Azure portal, which means it’s very easy to select, view and purchase.

2. All services are located in Windows Azure data centers. This means we can choose the best region to minimize our cost.

3. We can view the billing report in our Windows Azure subscription management page.

4. Almost all services in Windows Azure Store provides multiple subscription plan and most of them have free subscription.

 

Windows Azure Store and Windows Azure Marketplace

If you had playing with Windows Azure a while, you might heard that there’s another feature in Windows Azure called Windows Azure Marketplace, where you can buy data and applications published from other venders. It sounds very similar as Windows Azure Store. So what’s different between them? Will Windows Azure Marketplace be replaced by Windows Azure Store.

Here I would like to quote some clarification from Ryan McGee at Microsoft.

The Windows Azure Store and Windows Azure Marketplace will continue to co-exist; there is not a plan to converge them. The reason is, they serve different audiences and they contain different kinds of products.

The Windows Azure Store makes it easy to find and purchase add-ons, and use them to create your applications. While the Windows Azure Marketplace is an online market where ISVs and data publishers can publish and sell finished applications and premium datasets to business customers.

So my understanding is, if you are going to build your application on Windows Azure and want to leverage some services Windows Azure doesn’t provide, you’d better look for the Windows Azure Store. But if you are going to use some data, or you want to find some applications, you can seek in Windows Azure Marketplace.

 

Node.js + MongoDB by Using Windows Azure Store

Now let’s take a look on how to use Windows Azure Store. In this post I would like to demonstrate how to build a simple website by using Node.js and MongoDB.

 

As you know, Windows Azure doesn’t offer MongoDB. Before Windows Azure Store was announced, we have to run our own MongoDB service in a work role or in a virtual machine. This is OK but we have to take care for installation, configuration, scaling, etc.. Now we have Windows Azure Store so let’s check if there’s some MongoDB related services. Open the Windows Azure portal, from the navigate click ADD-ONS, and then click the PURCHASE ADD-ONS.

image

Now we can see the services in the Windows Azure Store. Scroll down the list and we will find a service named MongoLab, which is MongoDB-as-a-Service. Click next icon at the bottom right of the dialog.

image

Then we can choose the purchase plan. Currently there’s only one free plan for MongoLab which includes one 0.5G MongoDB. Select the valid Windows Azure subscription, specify our MongoDB name and choose the region. This is where our MongoDB will be provisioned.

image

Then in the last step, review our selection and clicked the PURCHASE icon.

If you found in the last step it said “We’re sorry, we cannot estimate the bill at this time.” this because you had registered Windows Azure Marketplace with different country/region. To solve this issue, you can log in the Windows Azure Marketplace and change the country/region to the one your Windows Azure subscription is.

After several seconds our MongoDB will be established in that region.

image

The connection information can be retrieved by clicking the CONNECTION INFO button at the bottom. We will use this information in our Node.js application later.

image

 

Next, we will create a new Windows Azure Website and write our Node.js code. Ref how to use Windows Azure Website and publish through local GIT, please refer my previous post. Just ensure that we specify the same region when creating website so that the network traffic will not be billed.

image

After cloned it to local disk we will add the .gitignore and package.json files. The content would be like this.

   1: node_modules
   1: {
   2:   "name": "nodemongotest",
   3:   "version": "1.0.0",
   4:   "dependencies": {
   5:     "mongoose": "~3.5.7",
   6:     "express": "~3.1.0"
   7:   }
   8: }

And then install “express” and “mongoose” modules through NPM command. “mongoose” is the module we are going to use to connect our MongoDB.

For more information about the “mongoose” module please refer its website.

Then, create our Node.js source code file named “server.js” and paste the code from below. Make sure to replace the MongoDB connection string by your MongoDB connection information in line 16.

   1: (function () {
   2:     "use strict";
   3:  
   4:     var app = require("express")();
   5:     var server = require("http").createServer(app);
   6:  
   7:     var mongoose = require("mongoose");
   8:     var Schema = mongoose.Schema;
   9:     var ObjectId = Schema.ObjectId;
  10:  
  11:     var guestSchema = new Schema({
  12:         client_ip: String,
  13:         request_date: String
  14:     });
  15:  
  16:     var conn = mongoose.createConnection("mongodb://TestMongoDB:wL5JJFkRPtYFS96_BbdustMrcgijw3C17B0rTdStqh8-@ds045107.mongolab.com:45107/TestMongoDB");
  17:     // var conn = mongoose.createConnection("mongodb://127.0.0.1/nodeandmongo-db");
  18:     var GuestItem = conn.model("GuestItem", guestSchema);
  19:  
  20:     var guestCount = function (req, callback) {
  21:         // retrieve the client request ip and date
  22:         var request_date = (new Date()).toString();
  23:         var request_ip = req.header('x-forwarded-for') || req.connection.remoteAddress;;
  24:         // try to insert into mongo
  25:         var guest = new GuestItem;
  26:         guest.client_ip = request_ip;
  27:         guest.request_date = request_date;
  28:         guest.save(function (error) {
  29:             if (error) {
  30:                 callback(error, null);
  31:             }
  32:             else {
  33:                 // retrieve all items in mongo
  34:                 GuestItem.find(function (error, items) {
  35:                     callback(error, items);
  36:                 });
  37:             }
  38:         });
  39:     }
  40:  
  41:     // routing
  42:     app.get("/", function (req, res) {
  43:         guestCount(req, function (error, guests) {
  44:             if (error) {
  45:                 res.send(200, error);
  46:             }
  47:             else {
  48:                 res.send(200, guests);
  49:             }
  50:         })
  51:     });
  52:  
  53:     server.listen(process.env.PORT || 12345);
  54: })();

This code will create a connection in our MongoDB named GuestItem. Then it will log the request client IP and request date once the website was viewed, and show the records from MongoDB.

Publish the code to Windows Azure Website and have a try, we can see that the records grown when we refresh the page.

image

 

Manage MongoDB

We are using MongoLab from Windows Azure Store in our application. But in Windows Azure portal there’s no management page for us to view the collections and documents. But we can view them in MongoLab’s website. Open the Windows Azure portal select the MongoLab in ADD-ONS section, and then click the MANAGE button.

image

Another web page will be popped up. It’s the management page provided by MongoLab. And from here we can see our collection and documents in our MongoDB.

image

And we can add, update and delete collections, documents in this page.

image

 

Summary

In this post I introduced Windows Azure Store, the different with the existing Windows Azure Marketplace. I Also demonstrate on how to use MongoLab from Windows Azure Store, working with Node.js.

There are many other services in Windows Azure Store currently. Below are some of them we thought might be useful.

ClearDB MySQL database-as-a-service, which is being used in Windows Azure Website feature.
MongoLab MongoDB database-as-a-service.
New Relic All-in-one web application performance tool that lets you see performance from the end user experience, through servers, and down to the line of app code.
Scheduler Time-based eventing made simple. Schedule CRON jobs with ease.
SendGrid Cloud-based email service that delivers email on behalf of companies to increase deliverability and improve customer communications.

And if you are interested in hosting and sale our great service in Windows Azure Store, you can contact wastorepartners@microsoft.com.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


In one of my previous post I described and demonstrated how to use NPM packages in Node.js and Windows Azure Web Site (WAWS). In that post I used NPM command to install packages, and then use Git for Windows to commit my changes and sync them to WAWS git repository. Then WAWS will trigger a new deployment to host my Node.js application.

Someone may notice that, a NPM package may contains many files and could be a little bit huge. For example, the “azure” package, which is the Windows Azure SDK for Node.js, is about 6MB. Another popular package “express”, which is a rich MVC framework for Node.js, is about 1MB. When I firstly push my codes to Windows Azure, all of them must be uploaded to the cloud.

Is that possible to let Windows Azure download and install these packages for us? In this post, I will introduce how to make WAWS install all required packages for us when deploying.

 

Let’s Start with Demo

Demo is most straightforward. Let’s create a new WAWS and clone it to my local disk. Drag the folder into Git for Windows so that it can help us commit and push.

Please refer to this post if you are not familiar with how to use Windows Azure Web Site, Git deployment, git clone and Git for Windows.

And then open a command windows and install a package in our code folder. Let’s say I want to install “express”.

image

And then created a new Node.js file named “server.js” and pasted the code as below.

   1: var express = require("express");
   2: var app = express();
   3:  
   4: app.get("/", function(req, res) {
   5:     res.send("Hello Node.js and Express.");
   6: });
   7:  
   8: console.log("Web application opened.");
   9: app.listen(process.env.PORT);

If we switch to Git for Windows right now we will find that it detected the changes we made, which includes the “server.js” and all files under “node_modules” folder. What we need to upload should only be our source code, but the huge package files also have to be uploaded as well. Now I will show you how to exclude them and let Windows Azure install the package on the cloud.

First we need to add a special file named “.gitignore”. It seems cannot be done directly from the file explorer since this file only contains extension name. So we need to do it from command line. Navigate to the local repository folder and execute the command below to create an empty file named “.gitignore”. If the command windows asked for input just press Enter.

   1: echo > .gitignore

image

Now open this file and copy the content below and save.

   1: node_modules

Now if we switch to Git for Windows we will found that the packages under the “node_modules” were not in the change list. So now if we commit and push, the “express” packages will not be uploaded to Windows Azure.

image

Second, let’s tell Windows Azure which packages it needs to install when deploying. Create another file named “package.json” and copy the content below into that file and save.

   1: {
   2:   "name": "npmdemo",
   3:   "version": "1.0.0",
   4:   "dependencies": {
   5:     "express": "*"
   6:   }
   7: }

Now back to Git for Windows, commit our changes and push it to WAWS.

image

Then let’s open the WAWS in developer portal, we will see that there’s a new deployment finished. Click the arrow right side of this deployment we can see how WAWS handle this deployment. Especially we can find WAWS executed NPM.

image

And if we opened the log we can review what command WAWS executed to install the packages and the installation output messages. As you can see WAWS installed “express” for me from the cloud side, so that I don’t need to upload the whole bunch of the package to Azure.

image

Open this website and we can see the result, which proved the “express” had been installed successfully.

image

 

What’s Happened Under the Hood

Now let’s explain a bit on what the “.gitignore” and “package.json” mean.

The “.gitignore” is an ignore configuration file for git repository. All files and folders listed in the “.gitignore” will be skipped from git push. In the example below I copied “node_modules” into this file in my local repository. This means,  do not track and upload all files under the “node_modules” folder. So by using “.gitignore” I skipped all packages from uploading to Windows Azure.

“.gitignore” can contain files, folders. It can also contain the files and folders that we do NOT want to ignore. In the next section we will see how to use the un-ignore syntax to make the SQL package included.

The “package.json” file is the package definition file for Node.js application. We can define the application name, version, description, author, etc. information in it in JSON format. And we can also put the dependent packages as well, to indicate which packages this Node.js application is needed.

In WAWS, name and version is necessary. And when a deployment happened, WAWS will look into this file, find the dependent packages, execute the NPM command to install them one by one. So in the demo above I copied “express” into this file so that WAWS will install it for me automatically.

I updated the dependencies section of the “package.json” file manually. But this can be done partially automatically. If we have a valid “package.json” in our local repository, then when we are going to install some packages we can specify “--save” parameter in “npm install” command, so that NPM will help us upgrade the dependencies part.

For example, when I wanted to install “azure” package I should execute the command as below. Note that I added “--save” with the command.

   1: npm install azure --save

Once it finished my “package.json” will be updated automatically.

image

Each dependent packages will be presented here. The JSON key is the package name while the value is the version range. Below is a brief list of the version range format. For more information about the “package.json” please refer here.

Format Description Example
version Must match the version exactly. "azure": "0.6.7"
>=version Must be equal or great than the version. "azure": ">0.6.0"
1.2.x The version number must start with the supplied digits, but any digit may be used in place of the x. "azure": "0.6.x"
~version The version must be at least as high as the range, and it must be less than the next major revision above the range. "azure": "~0.6.7"
* Matches any version. "azure": "*"

And WAWS will install the proper version of the packages based on what you defined here. The process of WAWS git deployment and NPM installation would be like this.

image

 

But Some Packages…

As we know, when we specified the dependencies in “package.json” WAWS will download and install them on the cloud. For most of packages it works very well. But there are some special packages may not work. This means, if the package installation needs some special environment restraints it might be failed.

For example, the SQL Server Driver for Node.js package needs “node-gyp”, Python and C++ 2010 installed on the target machine during the NPM installation. If we just put the “msnodesql” in “package.json” file and push it to WAWS, the deployment will be failed since there’s no “node-gyp”, Python and C++ 2010 in the WAWS virtual machine.

For example, the “server.js” file.

   1: var express = require("express");
   2: var app = express();
   3:  
   4: app.get("/", function(req, res) {
   5:     res.send("Hello Node.js and Express.");
   6: });
   7:  
   8: var sql = require("msnodesql");
   9: var connectionString = "Driver={SQL Server Native Client 10.0};Server=tcp:tqy4c0isfr.database.windows.net,1433;Database=msteched2012;Uid=shaunxu@tqy4c0isfr;Pwd=P@ssw0rd123;Encrypt=yes;Connection Timeout=30;";
  10: app.get("/sql", function (req, res) {
  11:     sql.open(connectionString, function (err, conn) {
  12:         if (err) {
  13:             console.log(err);
  14:             res.send(500, "Cannot open connection.");
  15:         }
  16:         else {
  17:             conn.queryRaw("SELECT * FROM [Resource]", function (err, results) {
  18:                 if (err) {
  19:                     console.log(err);
  20:                     res.send(500, "Cannot retrieve records.");
  21:                 }
  22:                 else {
  23:                     res.json(results);
  24:                 }
  25:             });
  26:         }
  27:     });
  28: });
  29:  
  30: console.log("Web application opened.");
  31: app.listen(process.env.PORT);

The “package.json” file.

   1: {
   2:   "name": "npmdemo",
   3:   "version": "1.0.0",
   4:   "dependencies": {
   5:     "express": "*",
   6:     "msnodesql": "*"
   7:   }
   8: }

And it failed to deploy to WAWS.

image

From the NPM log we can see it’s because “msnodesql” cannot be installed on WAWS.

image

The solution is, in “.gitignore” file we should ignore all packages except the “msnodesql”, and upload the package by ourselves. This can be done by use the content as below. We firstly un-ignored the “node_modules” folder. And then we ignored all sub folders but need git to check each sub folders. And then we un-ignore one of the sub folders named “msnodesql” which is the SQL Server Node.js Driver.

   1: !node_modules/
   2:  
   3: node_modules/*
   4: !node_modules/msnodesql

For more information about the syntax of “.gitignore” please refer to this thread.

Now if we go to Git for Windows we will find the “msnodesql” was included in the uncommitted set while “express” was not. I also need remove the dependency of “msnodesql” from “package.json”.

image

Commit and push to WAWS. Now we can see the deployment successfully done.

image

And then we can use the Windows Azure SQL Database from our Node.js application through the “msnodesql” package we uploaded.

image

 

Summary

In this post I demonstrated how to leverage the deployment process of Windows Azure Web Site to install NPM packages during the publish action. With the “.gitignore” and “package.json” file we can ignore the dependent packages from our Node.js and let Windows Azure Web Site download and install them while deployed.

For some special packages that cannot be installed by Windows Azure Web Site, such as “msnodesql”, we can put them into the publish payload as well.

With the combination of Windows Azure Web Site, Node.js and NPM it makes even more easy and quick for us to develop and deploy our Node.js application to the cloud.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


In the first post of this series I mentioned some popular modules in the community, such as underscore, async, etc.. I also listed a module named “Wind (zh-CN)”, which is created by one of my friend, Jeff Zhao (zh-CN). Now I would like to use a separated post to introduce this module since I feel it brings a new async programming style in not only Node.js but JavaScript world.

If you know or heard about the new feature in C# 5.0 called “async and await”, or you learnt F#, you will find the “Wind” brings the similar async programming experience in JavaScript. By using “Wind”, we can write async code that looks like the sync code. The callbacks, async stats and exceptions will be handled by “Wind” automatically and transparently.

 

What’s the Problem: Dense “Callback” Phobia

Let’s firstly back to my second post in this series. As I mentioned in that post, when we wanted to read some records from SQL Server we need to open the database connection, and then execute the query. In Node.js all IO operation are designed as async callback pattern which means when the operation was done, it will invoke a function which was taken from the last parameter.

For example the database connection opening code would be like this.

   1: sql.open(connectionString, function(error, conn) {
   2:     if(error) {
   3:         // some error handling code
   4:     }
   5:     else {
   6:         // connection opened successfully
   7:     }
   8: });

And then if we need to query the database the code would be like this. It nested in the previous function.

   1: sql.open(connectionString, function(error, conn) {
   2:     if(error) {
   3:         // some error handling code
   4:     }
   5:     else {
   6:         // connection opened successfully
   7:         conn.queryRaw(command, function(error, results) {
   8:             if(error) {
   9:                 // failed to execute this command
  10:             }
  11:             else {
  12:                 // records retrieved successfully
  13:             }
  14:         };
  15:     }
  16: });

Assuming if we need to copy some data from this database to another then we need to open another connection and execute the command within the function under the query function.

   1: sql.open(connectionString, function(error, conn) {
   2:     if(error) {
   3:         // some error handling code
   4:     }
   5:     else {
   6:         // connection opened successfully
   7:         conn.queryRaw(command, function(error, results) {
   8:             if(error) {
   9:                 // failed to execute this command
  10:             }
  11:             else {
  12:                 // records retrieved successfully
  13:                 target.open(targetConnectionString, function(error, t_conn) {
  14:                     if(error) {
  15:                         // connect failed
  16:                     }
  17:                     else {
  18:                         t_conn.queryRaw(copy_command, function(error, results) {
  19:                             if(error) {
  20:                                 // copy failed
  21:                             }
  22:                             else {
  23:                                 // and then, what do you want to do now...
  24:                             }
  25:                         };
  26:                     }
  27:                 };
  28:             }
  29:         };
  30:     }
  31: });

This is just an example. In the real project the logic would be more complicated. This means our application might be messed up and the business process will be fragged by many callback functions. I would like call this “Dense Callback Phobia”.

This might be a challenge how to make code straightforward and easy to read, something like below.

   1: try
   2: {
   3:     // open source connection
   4:     var s_conn = sqlConnect(s_connectionString);
   5:     // retrieve data
   6:     var results = sqlExecuteCommand(s_conn, s_command);
   7:     
   8:     // open target connection
   9:     var t_conn = sqlConnect(t_connectionString);
  10:     // prepare the copy command
  11:     var t_command = getCopyCommand(results);
  12:     // execute the copy command
  13:     sqlExecuteCommand(s_conn, t_command);
  14: }
  15: catch (ex)
  16: {
  17:     // error handling
  18: }

 

What’s the Problem: Sync-styled Async Programming

Similar as the previous problem, the callback-styled async programming model makes the upcoming operation as a part of the current operation, and mixed with the error handling code. So it’s very hard to understand what on earth this code will do.

And since Node.js utilizes non-blocking IO mode, we cannot invoke those operations one by one, as they will be executed concurrently.

For example, in this post when I tried to copy the records from Windows Azure SQL Database (a.k.a. WASD) to Windows Azure Table Storage, if I just insert the data into table storage one by one and then print the “Finished” message, I will see the message shown before the data had been copied. This is because all operations were executed at the same time.

In order to make the copy operation and print operation executed synchronously I introduced a module named “async” and the code was changed as below.

   1: async.forEach(results.rows,
   2:     function (row, callback) {
   3:         var resource = {
   4:             "PartitionKey": row[1],
   5:             "RowKey": row[0],
   6:             "Value": row[2]
   7:         };
   8:         client.insertEntity(tableName, resource, function (error) {
   9:             if (error) {
  10:                 callback(error);
  11:             }
  12:             else {
  13:                 console.log("entity inserted.");
  14:                 callback(null);
  15:             }
  16:         });
  17:     },
  18:     function (error) {
  19:         if (error) {
  20:             error["target"] = "insertEntity";
  21:             res.send(500, error);
  22:         }
  23:         else {
  24:             console.log("all done.");
  25:             res.send(200, "Done!");
  26:         }
  27:     });

It ensured that the “Finished” message will be printed when all table entities had been inserted. But it cannot promise that the records will be inserted in sequence. It might be another challenge to make the code looks like in sync-style?

   1: try
   2: {
   3:     forEach(row in rows) {
   4:         var entity = { /* ... */ };
   5:         tableClient.insert(tableName, entity);
   6:     }
   7:  
   8:     console.log("Finished");
   9: }
  10: catch (ex) {
  11:     console.log(ex);
  12: }

 

How “Wind” Helps

“Wind” is a JavaScript library which provides the control flow with plain JavaScript for asynchronous programming (and more) without additional pre-compiling steps. It’s available in NPM so that we can install it through “npm install wind”.

Now let’s create a very simple Node.js application as the example. This application will take some website URLs from the command arguments and tried to retrieve the body length and print them in console. Then at the end print “Finish”. I’m going to use “request” module to make the HTTP call simple so I also need to install by the command “npm install request”. The code would be like this.

   1: var request = require("request");
   2:  
   3: // get the urls from arguments, the first two arguments are `node.exe` and `fetch.js`
   4: var args = process.argv.splice(2);
   5:  
   6: // main function
   7: var main = function() {
   8:     for(var i = 0; i < args.length; i++) {
   9:         // get the url
  10:         var url = args[i];
  11:         // send the http request and try to get the response and body
  12:         request(url, function(error, response, body) {
  13:             if(!error && response.statusCode == 200) {
  14:                 // log the url and the body length
  15:                 console.log(
  16:                     "%s: %d.",
  17:                     response.request.uri.href,
  18:                     body.length);
  19:             }
  20:             else {
  21:                 // log error
  22:                 console.log(error);
  23:             }
  24:         });
  25:     }
  26:     
  27:     // finished
  28:     console.log("Finished");
  29: };
  30:  
  31: // execute the main function
  32: main();

Let’s execute this application. (I made them in multi-lines for better reading.)

   1: node fetch.js 
   2:   "http://www.igt.com/us-en.aspx" 
   3:   "http://www.igt.com/us-en/games.aspx" 
   4:   "http://www.igt.com/us-en/cabinets.aspx" 
   5:   "http://www.igt.com/us-en/systems.aspx"  
   6:   "http://www.igt.com/us-en/interactive.aspx" 
   7:   "http://www.igt.com/us-en/social-gaming.aspx" 
   8:   "http://www.igt.com/support.aspx"

Below is the output.

image

As you can see the finish message was printed at the beginning, and the pages’ length retrieved in a different order than we specified. This is because in this code the request command, console logging command are executed asynchronously and concurrently.

Now let’s introduce “Wind” to make them executed in order, which means it will request the websites one by one, and print the message at the end.

 

First of all we need to import the “Wind” package and make sure the there’s only one global variant named “Wind”, and ensure it’s “Wind” instead of “wind”.

   1: var Wind = require("wind");

 

Next, we need to tell “Wind” which code will be executed asynchronously so that “Wind” can control the execution process. In this case the “request” operation executed asynchronously so we will create a “Task” by using a build-in helps function in “Wind” named Wind.Async.Task.create.

   1: var requestBodyLengthAsync = function(url) {
   2:     return Wind.Async.Task.create(function(t) {
   3:         request(url, function(error, response, body) {
   4:             if(error || response.statusCode != 200) {
   5:                 t.complete("failure", error);
   6:             }
   7:             else {
   8:                 var data = 
   9:                 {
  10:                     uri: response.request.uri.href,
  11:                     length: body.length
  12:                 };
  13:                 t.complete("success", data);
  14:             }
  15:         });
  16:     });
  17: };

The code above created a “Task” from the original request calling code. In “Wind” a “Task” means an operation will be finished in some time in the future. A “Task” can be started by invoke its start() method, but no one knows when it actually will be finished.

The Wind.Async.Task.create helped us to create a task. The only parameter is a function where we can put the actual operation in, and then notify the task object it’s finished successfully or failed by using the complete() method.

In the code above I invoked the request method. If it retrieved the response successfully I set the status of this task as “success” with the URL and body length. If it failed I set this task as “failure” and pass the error out.

 

Next, we will change the main() function. In “Wind” if we want a function can be controlled by Wind we need to mark it as “async”. This should be done by using the code below.

   1: var main = eval(Wind.compile("async", function() {
   2: }));

When the application is running, Wind will detect “eval(Wind.compile(“async”, function” and generate an anonymous code from the body of this original function. Then the application will run the anonymous code instead of the original one.

In our example the main function will be like this.

   1: var main = eval(Wind.compile("async", function() {
   2:     for(var i = 0; i < args.length; i++) {
   3:         try
   4:         {
   5:             var result = $await(requestBodyLengthAsync(args[i]));
   6:             console.log(
   7:                 "%s: %d.",
   8:                 result.uri,
   9:                 result.length);
  10:         }
  11:         catch (ex) {
  12:             console.log(ex);
  13:         }
  14:     }
  15:     
  16:     console.log("Finished");
  17: }));

As you can see, when I tried to request the URL I use a new command named “$await”. It tells Wind, the operation next to $await will be executed asynchronously, and the main thread should be paused until it finished (or failed). So in this case, my application will be pause when the first response was received, and then print its body length, then try the next one. At the end, print the finish message.

 

Finally, execute the main function. The full code would be like this.

   1: var request = require("request");
   2: var Wind = require("wind");
   3:  
   4: var args = process.argv.splice(2);
   5:  
   6: var requestBodyLengthAsync = function(url) {
   7:     return Wind.Async.Task.create(function(t) {
   8:         request(url, function(error, response, body) {
   9:             if(error || response.statusCode != 200) {
  10:                 t.complete("failure", error);
  11:             }
  12:             else {
  13:                 var data = 
  14:                 {
  15:                     uri: response.request.uri.href,
  16:                     length: body.length
  17:                 };
  18:                 t.complete("success", data);
  19:             }
  20:         });
  21:     });
  22: };
  23:  
  24: var main = eval(Wind.compile("async", function() {
  25:     for(var i = 0; i < args.length; i++) {
  26:         try
  27:         {
  28:             var result = $await(requestBodyLengthAsync(args[i]));
  29:             console.log(
  30:                 "%s: %d.",
  31:                 result.uri,
  32:                 result.length);
  33:         }
  34:         catch (ex) {
  35:             console.log(ex);
  36:         }
  37:     }
  38:     
  39:     console.log("Finished");
  40: }));
  41:  
  42: main().start();

 

Run our new application. At the beginning we will see the compiled and generated code by Wind. Then we can see the pages were requested one by one, and at the end the finish message was printed.

image

Below is the code Wind generated for us. As you can see the original code, the output code were shown.

   1: // Original: 
   2: function () {
   3:     for(var i = 0; i < args.length; i++) {
   4:         try
   5:         {
   6:             var result = $await(requestBodyLengthAsync(args[i]));
   7:             console.log(
   8:                 "%s: %d.",
   9:                 result.uri,
  10:                 result.length);
  11:         }
  12:         catch (ex) {
  13:             console.log(ex);
  14:         }
  15:     }
  16:     
  17:     console.log("Finished");
  18: }
  19:  
  20: // Compiled: 
  21: /* async << function () { */                   (function () {
  22:                                                    var _builder_$0 = Wind.builders["async"];
  23:                                                    return _builder_$0.Start(this,
  24:                                                        _builder_$0.Combine(
  25:                                                            _builder_$0.Delay(function () {
  26: /*     var i = 0; */                                           var i = 0;
  27: /*     for ( */                                                return _builder_$0.For(function () {
  28: /*     ; i < args.length */                                        return i < args.length;
  29:                                                                }, function () {
  30: /*     ; i ++) { */                                                i ++;
  31:                                                                },
  32: /*         try { */                                                _builder_$0.Try(
  33:                                                                        _builder_$0.Delay(function () {
  34: /*             var result = $await(requestBodyLengthAsync(args[i])); */    return _builder_$0.Bind(requestBodyLengthAsync(args[i]), function (result) {
  35: /*             console.log("%s: %d.", result.uri, result.length); */           console.log("%s: %d.", result.uri, result.length);
  36:                                                                                return _builder_$0.Normal();
  37:                                                                            });
  38:                                                                        }),
  39: /*         } catch (ex) { */                                           function (ex) {
  40: /*             console.log(ex); */                                         console.log(ex);
  41:                                                                            return _builder_$0.Normal();
  42: /*         } */                                                        },
  43:                                                                        null
  44:                                                                    )
  45: /*     } */                                                    );
  46:                                                            }),
  47:                                                            _builder_$0.Delay(function () {
  48: /*     console.log("Finished"); */                             console.log("Finished");
  49:                                                                return _builder_$0.Normal();
  50:                                                            })
  51:                                                        )
  52:                                                    );
  53: /* } */                                        })

 

How Wind Works

Someone may raise a big concern when you find I utilized “eval” in my code. Someone may assume that Wind utilizes “eval” to execute some code dynamically while “eval” is very low performance. But I would say, Wind does NOT use “eval” to run the code. It only use “eval” as a flag to know which code should be compiled at runtime.

When the code was firstly been executed, Wind will check and find “eval(Wind.compile(“async”, function”. So that it knows this function should be compiled. Then it utilized parse-js to analyze the inner JavaScript and generated the anonymous code in memory. Then it rewrite the original code so that when the application was running it will use the anonymous one instead of the original one.

image

Since the code generation was done at the beginning of the application was started, in the future no matter how long our application runs and how many times the async function was invoked, it will use the generated code, no need to generate again. So there’s no significant performance hurt when using Wind.

image

 

Wind in My Previous Demo

Let’s adopt Wind into one of my previous demonstration and to see how it helps us to make our code simple, straightforward and easy to read and understand.

In this post when I implemented the functionality that copied the records from my WASD to table storage, the logic would be like this.

1, Open database connection.

2, Execute a query to select all records from the table.

3, Recreate the table in Windows Azure table storage.

4, Create entities from each of the records retrieved previously, and then insert them into table storage.

5, Finally, show message as the HTTP response.

But as the image below, since there are so many callbacks and async operations, it’s very hard to understand my logic from the code.

image

Now let’s use Wind to rewrite our code. First of all, of course, we need the Wind package.

image

Then we need to include the package files into project and mark them as “Copy always”.

image

Add the Wind package into the source code. Pay attention to the variant name, you must use “Wind” instead of “wind”.

   1: var express = require("express");
   2: var async = require("async");
   3: var sql = require("node-sqlserver");
   4: var azure = require("azure");
   5: var Wind = require("wind");

Now we need to create some async functions by using Wind. All async functions should be wrapped so that it can be controlled by Wind which are open database, retrieve records, recreate table (delete and create) and insert entity in table.

Below are these new functions. All of them are created by using Wind.Async.Task.create.

   1: sql.openAsync = function (connectionString) {
   2:     return Wind.Async.Task.create(function (t) {
   3:         sql.open(connectionString, function (error, conn) {
   4:             if (error) {
   5:                 t.complete("failure", error);
   6:             }
   7:             else {
   8:                 t.complete("success", conn);
   9:             }
  10:         });
  11:     });
  12: };
  13:  
  14: sql.queryAsync = function (conn, query) {
  15:     return Wind.Async.Task.create(function (t) {
  16:         conn.queryRaw(query, function (error, results) {
  17:             if (error) {
  18:                 t.complete("failure", error);
  19:             }
  20:             else {
  21:                 t.complete("success", results);
  22:             }
  23:         });
  24:     });
  25: };
  26:  
  27: azure.recreateTableAsync = function (tableName) {
  28:     return Wind.Async.Task.create(function (t) {
  29:         client.deleteTable(tableName, function (error, successful, response) {
  30:             console.log("delete table finished");
  31:             client.createTableIfNotExists(tableName, function (error, successful, response) {
  32:                 console.log("create table finished");
  33:                 if (error) {
  34:                     t.complete("failure", error);
  35:                 }
  36:                 else {
  37:                     t.complete("success", null);
  38:                 }
  39:             });
  40:         });
  41:     });
  42: };
  43:  
  44: azure.insertEntityAsync = function (tableName, entity) {
  45:     return Wind.Async.Task.create(function (t) {
  46:         client.insertEntity(tableName, entity, function (error, entity, response) {
  47:             if (error) {
  48:                 t.complete("failure", error);
  49:             }
  50:             else {
  51:                 t.complete("success", null);
  52:             }
  53:         });
  54:     });
  55: };

Then in order to use these functions we will create a new function which contains all steps for data copying.

   1: var copyRecords = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:     }
   4:     catch (ex) {
   5:         console.log(ex);
   6:         res.send(500, "Internal error.");
   7:     }
   8: }));

Let’s execute steps one by one with the “$await” keyword introduced by Wind so that it will be invoked in sequence. First is to open the database connection.

   1: var copyRecords = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:         // connect to the windows azure sql database
   4:         var conn = $await(sql.openAsync(connectionString));
   5:         console.log("connection opened");
   6:     }
   7:     catch (ex) {
   8:         console.log(ex);
   9:         res.send(500, "Internal error.");
  10:     }
  11: }));

Then retrieve all records from the database connection.

   1: var copyRecords = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:         // connect to the windows azure sql database
   4:         var conn = $await(sql.openAsync(connectionString));
   5:         console.log("connection opened");
   6:         // retrieve all records from database
   7:         var results = $await(sql.queryAsync(conn, "SELECT * FROM [Resource]"));
   8:         console.log("records selected. count = %d", results.rows.length);
   9:     }
  10:     catch (ex) {
  11:         console.log(ex);
  12:         res.send(500, "Internal error.");
  13:     }
  14: }));

After recreated the table, we need to create the entities and insert them into table storage.

   1: var copyRecords = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:         // connect to the windows azure sql database
   4:         var conn = $await(sql.openAsync(connectionString));
   5:         console.log("connection opened");
   6:         // retrieve all records from database
   7:         var results = $await(sql.queryAsync(conn, "SELECT * FROM [Resource]"));
   8:         console.log("records selected. count = %d", results.rows.length);
   9:         if (results.rows.length > 0) {
  10:             // recreate the table
  11:             $await(azure.recreateTableAsync(tableName));
  12:             console.log("table created");
  13:             // insert records in table storage one by one
  14:             for (var i = 0; i < results.rows.length; i++) {
  15:                 var entity = {
  16:                     "PartitionKey": results.rows[i][1],
  17:                     "RowKey": results.rows[i][0],
  18:                     "Value": results.rows[i][2]
  19:                 };
  20:                 $await(azure.insertEntityAsync(tableName, entity));
  21:                 console.log("entity inserted");
  22:             }
  23:         }
  24:     }
  25:     catch (ex) {
  26:         console.log(ex);
  27:         res.send(500, "Internal error.");
  28:     }
  29: }));

Finally, send response back to the browser.

   1: var copyRecords = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:         // connect to the windows azure sql database
   4:         var conn = $await(sql.openAsync(connectionString));
   5:         console.log("connection opened");
   6:         // retrieve all records from database
   7:         var results = $await(sql.queryAsync(conn, "SELECT * FROM [Resource]"));
   8:         console.log("records selected. count = %d", results.rows.length);
   9:         if (results.rows.length > 0) {
  10:             // recreate the table
  11:             $await(azure.recreateTableAsync(tableName));
  12:             console.log("table created");
  13:             // insert records in table storage one by one
  14:             for (var i = 0; i < results.rows.length; i++) {
  15:                 var entity = {
  16:                     "PartitionKey": results.rows[i][1],
  17:                     "RowKey": results.rows[i][0],
  18:                     "Value": results.rows[i][2]
  19:                 };
  20:                 $await(azure.insertEntityAsync(tableName, entity));
  21:                 console.log("entity inserted");
  22:             }
  23:             // send response
  24:             console.log("all done");
  25:             res.send(200, "All done!");
  26:         }
  27:     }
  28:     catch (ex) {
  29:         console.log(ex);
  30:         res.send(500, "Internal error.");
  31:     }
  32: }));

If we compared with the previous code we will find now it became more readable and much easy to understand. It’s very easy to know what this function does even though without any comments.

image

When user go to URL “/was/copyRecords” we will execute the function above. The code would be like this.

   1: app.get("/was/copyRecords", function (req, res) {
   2:     copyRecords(req, res).start();
   3: });

And below is the logs printed in local compute emulator console. As we can see the functions executed one by one and then finally the response back to me browser.

image

 

Scaffold Functions in Wind

Wind provides not only the async flow control and compile functions, but many scaffold methods as well. We can build our async code more easily by using them. I’m going to introduce some basic scaffold functions here.

In the code above I created some functions which wrapped from the original async function such as open database, create table, etc.. All of them are very similar, created a task by using Wind.Async.Task.create, return error or result object through Task.complete function. In fact, Wind provides some functions for us to create task object from the original async functions.

If the original async function only has a callback parameter, we can use Wind.Async.Binding.fromCallback method to get the task object directly. For example the code below returned the task object which wrapped the file exist check function.

   1: var Wind = require("wind");
   2: var fs = require("fs");
   3:  
   4: fs.existsAsync = Wind.Async.Binding.fromCallback(fs.exists);

In Node.js a very popular async function pattern is that, the first parameter in the callback function represent the error object, and the other parameters is the return values. In this case we can use another build-in function in Wind named Wind.Async.Binding.fromStandard. For example, the open database function can be created from the code below.

   1: sql.openAsync = Wind.Async.Binding.fromStandard(sql.open);
   2:  
   3: /*
   4: sql.openAsync = function (connectionString) {
   5:     return Wind.Async.Task.create(function (t) {
   6:         sql.open(connectionString, function (error, conn) {
   7:             if (error) {
   8:                 t.complete("failure", error);
   9:             }
  10:             else {
  11:                 t.complete("success", conn);
  12:             }
  13:         });
  14:     });
  15: };
  16: */

When I was testing the scaffold functions under Wind.Async.Binding I found for some functions, such as the Azure SDK insert entity function, cannot be processed correctly. So I personally suggest writing the wrapped method manually.

 

Another scaffold method in Wind is the parallel tasks coordination. In this example, the steps of open database, retrieve records and recreated table should be invoked one by one, but it can be executed in parallel when copying data from database to table storage. In Wind there’s a scaffold function named Task.whenAll which can be used here.

Task.whenAll accepts a list of tasks and creates a new task. It will be returned only when all tasks had been completed, or any errors occurred. For example in the code below I used the Task.whenAll to make all copy operation executed at the same time.

   1: var copyRecordsInParallel = eval(Wind.compile("async", function (req, res) {
   2:     try {
   3:         // connect to the windows azure sql database
   4:         var conn = $await(sql.openAsync(connectionString));
   5:         console.log("connection opened");
   6:         // retrieve all records from database
   7:         var results = $await(sql.queryAsync(conn, "SELECT * FROM [Resource]"));
   8:         console.log("records selected. count = %d", results.rows.length);
   9:         if (results.rows.length > 0) {
  10:             // recreate the table
  11:             $await(azure.recreateTableAsync(tableName));
  12:             console.log("table created");
  13:             // insert records in table storage in parallal
  14:             var tasks = new Array(results.rows.length);
  15:             for (var i = 0; i < results.rows.length; i++) {
  16:                 var entity = {
  17:                     "PartitionKey": results.rows[i][1],
  18:                     "RowKey": results.rows[i][0],
  19:                     "Value": results.rows[i][2]
  20:                 };
  21:                 tasks[i] = azure.insertEntityAsync(tableName, entity);
  22:             }
  23:             $await(Wind.Async.Task.whenAll(tasks));
  24:             // send response
  25:             console.log("all done");
  26:             res.send(200, "All done!");
  27:         }
  28:     }
  29:     catch (ex) {
  30:         console.log(ex);
  31:         res.send(500, "Internal error.");
  32:     }
  33: }));
  34:  
  35: app.get("/was/copyRecordsInParallel", function (req, res) {
  36:     copyRecordsInParallel(req, res).start();
  37: });

 

Besides the task creation and coordination, Wind supports the cancellation solution so that we can send the cancellation signal to the tasks. It also includes exception solution which means any exceptions will be reported to the caller function.

 

Summary

In this post I introduced a Node.js module named Wind, which created by my friend Jeff Zhao. As you can see, different from other async library and framework, adopted the idea from F# and C#, Wind utilizes runtime code generation technology to make it more easily to write async, callback-based functions in a sync-style way. By using Wind there will be almost no callback, and the code will be very easy to understand.

Currently Wind is still under developed and improved. There might be some problems but the author, Jeff, should be very happy and enthusiastic to learn your problems, feedback, suggestion and comments. You can contact Jeff by

- Email: windjs@googlegroups.com

- Group: https://groups.google.com/d/forum/windjs

- GitHub: https://github.com/JeffreyZhao/wind/issues

 

Source code can be download here.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


When I described on how to host a Node.js application on Windows Azure, one of questions might be raised about how to consume the vary Windows Azure services, such as the storage, service bus, access control, etc.. Interact with windows azure services is available in Node.js through the Windows Azure Node.js SDK, which is a module available in NPM. In this post I would like to describe on how to use Windows Azure Storage (a.k.a. WAS) as well as the service runtime.

 

Consume Windows Azure Storage

Let’s firstly have a look on how to consume WAS through Node.js. As we know in the previous post we can host Node.js application on Windows Azure Web Site (a.k.a. WAWS) as well as Windows Azure Cloud Service (a.k.a. WACS). In theory, WAWS is also built on top of WACS worker roles with some more features. Hence in this post I will only demonstrate for hosting in WACS worker role.

The Node.js code can be used when consuming WAS when hosted on WAWS. But since there’s no roles in WAWS, the code for consuming service runtime mentioned in the next section cannot be used for WAWS node application.

We can use the solution that I created in my last post. Alternatively we can create a new windows azure project in Visual Studio with a worker role, add the “node.exe” and “index.js” and install “express” and “node-sqlserver” modules, make all files as “Copy always”.

In order to use windows azure services we need to have Windows Azure Node.js SDK, as knows as a module named “azure” which can be installed through NPM. Once we downloaded and installed, we need to include them in our worker role project and make them as “Copy always”.

You can use my “Copy all always” tool mentioned in my last post to update the currently worker role project file. You can also find the source code of this tool here.

image

The source code of Windows Azure SDK for Node.js can be found in its GitHub page. It contains two parts. One is a CLI tool which provides a cross platform command line package for Mac and Linux to manage WAWS and Windows Azure Virtual Machines (a.k.a. WAVM). The other is a library for managing and consuming vary windows azure services includes tables, blobs, queues, service bus and the service runtime. I will not cover all of them but will only demonstrate on how to use tables and service runtime information in this post. You can find the full document of this SDK here.

Back to Visual Studio and open the “index.js”, let’s continue our application from the last post, which was working against Windows Azure SQL Database (a.k.a. WASD). The code should looks like this.

   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);

Now let’s create a new function, copy the records from WASD to table service.

1. Delete the table named “resource”.

2. Create a new table named “resource”. These 2 steps ensures that we have an empty table.

3. Load all records from the “resource” table in WASD.

4. For each records loaded from WASD, insert them into the table one by one.

5. Prompt to user when finished.

In order to use table service we need the storage account and key, which can be found from the developer portal. Just select the storage account and click the Manage Keys button.

image

Then create two local variants in our Node.js application for the storage account name and key. Since we need to use WAS we need to import the azure module. Also I created another variant stored the table name.

In order to work with table service I need to create the storage client for table service. This is very similar as the Windows Azure SDK for .NET. As the code below I created a new variant named “client” and use “createTableService”, specified my storage account name and key.

   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);

Now create a new function for URL “/was/init” so that we can trigger it through browser. Then in this function we will firstly load all records from 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: });

When we succeed loaded all records we can start to transform them into table service. First I need to recreate the table in table service. This can be done by deleting and creating the table through table client I had just created previously.

   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: });

As you can see, the azure SDK provide its methods in callback pattern. In fact, almost all modules in Node.js use the callback pattern.

For example, when I deleted a table I invoked “deleteTable” method, provided the name of the table and a callback function which will be performed when the table had been deleted or failed. Underlying, the azure module will perform the table deletion operation in POSIX async threads pool asynchronously. And once it’s done the callback function will be performed. This is the reason we need to nest the table creation code inside the deletion function. If we perform the table creation code after the deletion code then they will be invoked in parallel.

Next, for each records in WASD I created an entity and then insert into the table service. Finally I send the response to the browser.

Can you find a bug in the code below? I will describe it later in this post.

   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: });

Now we can publish it to the cloud and have a try. But normally we’d better test it at the local emulator first. In Node.js SDK there are three build-in properties which provides the account name, key and host address for local storage emulator. We can use them to initialize our table service client. We also need to change the SQL connection string to let it use my local database. The code will be changed as below.

   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);

Now let’s run the application and navigate to “localhost:12345/was/init” as I hosted it on port 12345. We can find it transformed the data from my local database to local table service.

image

Everything looks fine. But there is a bug in my code. If we have a look on the Node.js command window we will find that it sent response before all records had been inserted, which is not what I expected.

image

The reason is that, as I mentioned before, Node.js perform all IO operations in non-blocking model. When we inserted the records we executed the table service insert method in parallel, and the operation of sending response was also executed in parallel, even though I wrote it at the end of my logic.

image

The correct logic should be, when all entities had been copied to table service with no error, then I will send response to the browser, otherwise I should send error message to the browser. To do so I need to import another module named “async”, which helps us to coordinate our asynchronous code.

Install the module and import it at the beginning of the code. Then we can use its “forEach” method for the asynchronous code of inserting table entities. The first argument of “forEach” is the array that will be performed. The second argument is the operation for each items in the array. And the third argument will be invoked then all items had been performed or any errors occurred. Here we can send our response to browser.

   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: });

Run it locally and now we can find the response was sent after all entities had been inserted.

image

Query entities against table service is simple as well. Just use the “queryEntity” method from the table service client and providing the partition key and row key. We can also provide a complex query criteria as well, for example the code here.

In the code below I queried an entity by the partition key and row key, and return the proper localization value in response.

   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: });

And then tested it on local emulator.

image

Finally if we want to publish this application to the cloud we should change the database connection string and storage account.

For more information about how to consume blob and queue service, as well as the service bus please refer to the MSDN page.

 

Consume Service Runtime

As I mentioned above, before we published our application to the cloud we need to change the connection string and account information in our code. But if you had played with WACS you should have known that the service runtime provides the ability to retrieve configuration settings, endpoints and local resource information at runtime. Which means we can have these values defined in CSCFG and CSDEF files and then the runtime should be able to retrieve the proper values.

For example we can add some role settings though the property window of the role, specify the connection string and storage account for cloud and local.

image

And the can also use the endpoint which defined in role environment to our Node.js application.

image

In Node.js SDK we can get an object from “azure.RoleEnvironment”, which provides the functionalities to retrieve the configuration settings and endpoints, etc.. In the code below I defined the connection string variants and then use the SDK to retrieve and initialize the table client.

   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: });

In this way we don’t need to amend the code for the configurations between local and cloud environment since the service runtime will take care of it.

At the end of the code we will listen the application on the port retrieved from SDK as well.

   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: });

But if we tested the application right now we will find that it cannot retrieve any values from service runtime. This is because by default, the entry point of this role was defined to the worker role class. In windows azure environment the service runtime will open a named pipeline to the entry point instance, so that it can connect to the runtime and retrieve values. But in this case, since the entry point was worker role and the Node.js was opened inside the role, the named pipeline was established between our worker role class and service runtime, so our Node.js application cannot use it.

image

To fix this problem we need to open the CSDEF file under the azure project, add a new element named Runtime. Then add an element named EntryPoint which specify the Node.js command line. So that the Node.js application will have the connection to service runtime, then it’s able to read the configurations.

image

Start the Node.js at local emulator we can find it retrieved the connections, storage account for local.

image

And if we publish our application to azure then it works with WASD and storage service through the configurations for cloud.

image

 

Summary

In this post I demonstrated how to use Windows Azure SDK for Node.js to interact with storage service, especially the table service. I also demonstrated on how to use WACS service runtime, how to retrieve the configuration settings and the endpoint information. And in order to make the service runtime available to my Node.js application I need to create an entry point element in CSDEF file and set “node.exe” as the entry point.

I used five posts to introduce and demonstrate on how to run a Node.js application on Windows platform, how to use Windows Azure Web Site and Windows Azure Cloud Service worker role to host our Node.js application. I also described how to work with other services provided by Windows Azure platform through Windows Azure SDK for Node.js.

Node.js is a very new and young network application platform. But since it’s very simple and easy to learn and deploy, as well as, it utilizes single thread non-blocking IO model, Node.js became more and more popular on web application and web service development especially for those IO sensitive projects. And as Node.js is very good at scaling-out, it’s more useful on cloud computing platform.

Use Node.js on Windows platform is new, too. The modules for SQL database and Windows Azure SDK are still under development and enhancement. It doesn’t support SQL parameter in “node-sqlserver”. It does support using storage connection string to create the storage client in “azure”. But Microsoft is working on make them easier to use, working on add more features and functionalities.

 

PS, you can download the source code here. You can download the source code of my “Copy all always” tool here.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


Microsoft had just announced an update for Windows Azure Web Site (a.k.a. WAWS). There are four major features added in WAWS which are free scaling mode, GitHub integration, custom domain and multi branches.

Since I ‘m working in Node.js and I would like to have my code in GitHub and deployed automatically to my Windows Azure Web Site once I sync my code, this feature is a big good news to me.

 

It’s very simple to establish the GitHub integration in WAWS. First we need a clean WAWS. In its dashboard page click “Set up Git publishing”.

image

Currently WAWS doesn’t support to change the publish setting. So if you have an existing WAWS which published by TFS or local Git then you have to create a new WAWS and set the Git publishing.

Then in the deployment page we can see now WAWS supports three Git publishing modes:

- Push my local files to Windows Azure: In this mode we will create a new Git repository on local machine and commit, publish our code to Windows Azure through Git command or some GUI.

- Deploy from my GitHub project: In this mode we will have a Git repository created on GitHub. Once we publish our code to GitHub Windows Azure will download the code and trigger a new deployment.

- Deploy from my CodePlex project: Similar as the previous one but our code would be in CodePlex repository.

 

Now let’s back to GitHub and create a new publish repository.

Currently WAWS GitHub integration only support for public repositories. The private repositories support will be available in several weeks.

We can manage our repositories in GitHub website. But as a windows geek I prefer the GUI tool. So I opened the GitHub for Windows, login with my GitHub account and select the “github” category, click the “add” button to create a new repository on GitHub.

image

You can download the GitHub for Windows here.

I specified the repository name, description, local repository, do not check the “Keep this code private”. After few seconds it will create a new repository on GitHub and associate it to my local machine in that folder.

image

We can find this new repository in GitHub website. And in GitHub for Windows we can also find the local repository by selecting the “local” category.

image

 

Next, we need to associate this repository with our WAWS. Back to windows developer portal, open the “Deploy from my GitHub project” in the deployment page and click the “Authorize Windows Azure” link. It will bring up a new windows on GitHub which let me allow the Windows Azure application can access your repositories.

image

After we clicked “Allow”, windows azure will retrieve all my GitHub public repositories and let me select which one I want to integrate to this WAWS. I selected the one I had just created in GitHub for Windows.

image

So that’s all. We had completed the GitHub integration configuration. Now let’s have a try. In GitHub for Windows, right click on this local repository and click “open in explorer”. Then I added a simple HTML file.

   1: <html>
   2:     <head>
   3:     </head>
   4:     <body>
   5:         <h1>
   6:             I came from GitHub, WOW!
   7:         </h1>
   8:     </body>
   9: </html>

Save it and back to GitHub for Windows, commit this change and publish. This will upload our changes to GitHub, and Windows Azure will detect this update and trigger a new deployment.

If we went back to azure developer portal we can find the new deployment. And our commit message will be shown as the deployment description as well.

image

And here is the page deployed to WAWS.

image

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


In my previous post I demonstrated about how to develop and deploy a Node.js application on Windows Azure Web Site (a.k.a. WAWS). WAWS is a new feature in Windows Azure platform. Since it’s low-cost, and it provides IIS and IISNode components so that we can host our Node.js application though Git, FTP and WebMatrix without any configuration and component installation.

But sometimes we need to use the Windows Azure Cloud Service (a.k.a. WACS) and host our Node.js on worker role. Below are some benefits of using worker role.

- WAWS leverages IIS and IISNode to host Node.js application, which runs in x86 WOW mode. It reduces the performance comparing with x64 in some cases.

- WACS worker role does not need IIS, hence there’s no restriction of IIS, such as 8000 concurrent requests limitation.

- WACS provides more flexibility and controls to the developers. For example, we can RDP to the virtual machines of our worker role instances.

- WACS provides the service configuration features which can be changed when the role is running.

- WACS provides more scaling capability than WAWS. In WAWS we can have at most 3 reserved instances per web site while in WACS we can have up to 20 instances in a subscription.

- Since when using WACS worker role we starts the node by ourselves in a process, we can control the input, output and error stream. We can also control the version of Node.js.

 

Run Node.js in Worker Role

Node.js can be started by just having its execution file. This means in Windows Azure, we can have a worker role with the “node.exe” and the Node.js source files, then start it in Run method of the worker role entry class.

Let’s create a new windows azure project in Visual Studio and add a new worker role. Since we need our worker role execute the “node.exe” with our application code we need to add the “node.exe” into our project. Right click on the worker role project and add an existing item. By default the Node.js will be installed in the “Program Files\nodejs” folder so we can navigate there and add the “node.exe”.

Then we need to create the entry code of Node.js. In WAWS the entry file must be named “server.js”, which is because it’s hosted by IIS and IISNode and IISNode only accept “server.js”. But here as we control everything we can choose any files as the entry code. For example, I created a new JavaScript file named “index.js” in project root.

Since we created a C# Windows Azure project we cannot create a JavaScript file from the context menu “Add new item”. We have to create a text file, and then rename it to JavaScript extension.

After we added these two files we should set their “Copy to Output Directory” property to “Copy Always”, or “Copy if Newer”. Otherwise they will not be involved in the package when deployed.

image

Let’s paste a very simple Node.js code in the “index.js” as below. As you can see I created a web server listening at port 12345.

   1: var http = require("http");
   2: var port = 12345;
   3:  
   4: http.createServer(function (req, res) {
   5:     res.writeHead(200, { "Content-Type": "text/plain" });
   6:     res.end("Hello World\n");
   7: }).listen(port);
   8:  
   9: console.log("Server running at port %d", port);

Then we need to start “node.exe” with this file when our worker role was started. This can be done in its Run method. I found the Node.js and entry JavaScript file name, and then create a new process to run it. Our worker role will wait for the process to be exited. If everything is OK once our web server was opened the process will be there listening for incoming requests, and should not be terminated. The code in worker role would be like this.

   1: public override void Run()
   2: {
   3:     // This is a sample worker implementation. Replace with your logic.
   4:     Trace.WriteLine("NodejsHost entry point called", "Information");
   5:  
   6:     // retrieve the node.exe and entry node.js source code file name.
   7:     var node = Environment.ExpandEnvironmentVariables(@"%RoleRoot%\approot\node.exe");
   8:     var js = "index.js";
   9:  
  10:     // prepare the process starting of node.exe
  11:     var info = new ProcessStartInfo(node, js)
  12:     {
  13:         CreateNoWindow = false,
  14:         ErrorDialog = true,
  15:         WindowStyle = ProcessWindowStyle.Normal,
  16:         UseShellExecute = false,
  17:         WorkingDirectory = Environment.ExpandEnvironmentVariables(@"%RoleRoot%\approot")
  18:     };
  19:     Trace.WriteLine(string.Format("{0} {1}", node, js), "Information");
  20:  
  21:     // start the node.exe with entry code and wait for exit
  22:     var process = Process.Start(info);
  23:     process.WaitForExit();
  24: }

Then we can run it locally. In the computer emulator UI the worker role started and it executed the Node.js, then Node.js windows appeared.

image

Open the browser to verify the website hosted by our worker role.

image

Next let’s deploy it to azure. But we need some additional steps. First, we need to create an input endpoint. By default there’s no endpoint defined in a worker role. So we will open the role property window in Visual Studio, create a new input TCP endpoint to the port we want our website to use. In this case I will use 80.

Even though we created a web server we should add a TCP endpoint of the worker role, since Node.js always listen on TCP instead of HTTP.

image

And then changed the “index.js”, let our web server listen on 80.

   1: var http = require("http");
   2: var port = 80;
   3:  
   4: http.createServer(function (req, res) {
   5:     res.writeHead(200, { "Content-Type": "text/plain" });
   6:     res.end("Hello World\n");
   7: }).listen(port);
   8:  
   9: console.log("Server running at port %d", port);

Then publish it to Windows Azure.

image

And then in browser we can see our Node.js website was running on WACS worker role.

image

We may encounter an error if we tried to run our Node.js website on 80 port at local emulator. This is because the compute emulator registered 80 and map the 80 endpoint to 81. But our Node.js cannot detect this operation. So when it tried to listen on 80 it will failed since 80 have been used.

 

Use NPM Modules

When we are using WAWS to host Node.js, we can simply install modules we need, and then just publish or upload all files to WAWS. But if we are using WACS worker role, we have to do some extra steps to make the modules work.

Assuming that we plan to use “express” in our application. Firstly of all we should download and install this module through NPM command. But after the install finished, they are just in the disk but not included in the worker role project. If we deploy the worker role right now the module will not be packaged and uploaded to azure. Hence we need to add them to the project. On solution explorer window click the “Show all files” button, select the “node_modules” folder and in the context menu select “Include In Project”.

image

But that not enough. We also need to make all files in this module to “Copy always” or “Copy if newer”, so that they can be uploaded to azure with the “node.exe” and “index.js”. This is painful step since there might be many files in a module. So I created a small tool which can update a C# project file, make its all items as “Copy always”. The code is very simple.

   1: static void Main(string[] args)
   2: {
   3:     if (args.Length < 1)
   4:     {
   5:         Console.WriteLine("Usage: copyallalways [project file]");
   6:         return;
   7:     }
   8:  
   9:     var proj = args[0];
  10:     File.Copy(proj, string.Format("{0}.bak", proj));
  11:  
  12:     var xml = new XmlDocument();
  13:     xml.Load(proj);
  14:     var nsManager = new XmlNamespaceManager(xml.NameTable);
  15:     nsManager.AddNamespace("pf", "http://schemas.microsoft.com/developer/msbuild/2003");
  16:  
  17:     // add the output setting to copy always
  18:     var contentNodes = xml.SelectNodes("//pf:Project/pf:ItemGroup/pf:Content", nsManager);
  19:     UpdateNodes(contentNodes, xml, nsManager);
  20:     var noneNodes = xml.SelectNodes("//pf:Project/pf:ItemGroup/pf:None", nsManager);
  21:     UpdateNodes(noneNodes, xml, nsManager);
  22:     xml.Save(proj);
  23:  
  24:     // remove the namespace attributes
  25:     var content = xml.InnerXml.Replace("<CopyToOutputDirectory xmlns=\"\">", "<CopyToOutputDirectory>");
  26:     xml.LoadXml(content);
  27:     xml.Save(proj);
  28: }
  29:  
  30: static void UpdateNodes(XmlNodeList nodes, XmlDocument xml, XmlNamespaceManager nsManager)
  31: {
  32:     foreach (XmlNode node in nodes)
  33:     {
  34:         var copyToOutputDirectoryNode = node.SelectSingleNode("pf:CopyToOutputDirectory", nsManager);
  35:         if (copyToOutputDirectoryNode == null)
  36:         {
  37:             var n = xml.CreateNode(XmlNodeType.Element, "CopyToOutputDirectory", null);
  38:             n.InnerText = "Always";
  39:             node.AppendChild(n);
  40:         }
  41:         else
  42:         {
  43:             if (string.Compare(copyToOutputDirectoryNode.InnerText, "Always", true) != 0)
  44:             {
  45:                 copyToOutputDirectoryNode.InnerText = "Always";
  46:             }
  47:         }
  48:     }
  49: }

Please be careful when use this tool. I created only for demo so do not use it directly in a production environment.

Unload the worker role project, execute this tool with the worker role project file name as the command line argument, it will set all items as “Copy always”. Then reload this worker role project.

Now let’s change the “index.js” to use express.

   1: var express = require("express");
   2: var app = express();
   3:  
   4: var port = 80;
   5:  
   6: app.configure(function () {
   7: });
   8:  
   9: app.get("/", function (req, res) {
  10:     res.send("Hello Node.js!");
  11: });
  12:  
  13: app.get("/User/:id", function (req, res) {
  14:     var id = req.params.id;
  15:     res.json({
  16:         "id": id,
  17:         "name": "user " + id,
  18:         "company": "IGT"
  19:     });
  20: });
  21:  
  22: app.listen(port);

Finally let’s publish it and have a look in browser.

image

 

Use Windows Azure SQL Database

We can use Windows Azure SQL Database (a.k.a. WACD) from Node.js as well on worker role hosting. Since we can control the version of Node.js, here we can use x64 version of “node-sqlserver” now. This is better than if we host Node.js on WAWS since it only support x86.

Just install the “node-sqlserver” module from NPM, copy the “sqlserver.node” from “Build\Release” folder to “Lib” folder. Include them in worker role project and run my tool to make them to “Copy always”. Finally update the “index.js” to use 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:{SERVER NAME}.database.windows.net,1433;Database={DATABASE NAME};Uid={LOGIN}@{SERVER NAME};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);

Publish to azure and now we can see our Node.js is working with WASD through x64 version “node-sqlserver”.

image

 

Summary

In this post I demonstrated how to host our Node.js in Windows Azure Cloud Service worker role. By using worker role we can control the version of Node.js, as well as the entry code. And it’s possible to do some pre jobs before the Node.js application started. It also removed the IIS and IISNode limitation. I personally recommended to use worker role as our Node.js hosting.

But there are some problem if you use the approach I mentioned here. The first one is, we need to set all JavaScript files and module files as “Copy always” or “Copy if newer” manually. The second one is, in this way we cannot retrieve the cloud service configuration information. For example, we defined the endpoint in worker role property but we also specified the listening port in Node.js hardcoded. It should be changed that our Node.js can retrieve the endpoint. But I can tell you it won’t be working here.

In the next post I will describe another way to execute the “node.exe” and Node.js application, so that we can get the cloud service configuration in Node.js. I will also demonstrate how to use Windows Azure Storage from Node.js by using the Windows Azure Node.js SDK.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


In the first post of this series I introduced on how to run Node.js application on Windows. In the second one I demonstrated about how to use SQL Server in Node.js. That was the steps when I was learning Node.js, firstly made it work on Windows, then try to use SQL Server. But returned back to my original goal, I need to implement a synchronization service for Wang Tao’s worktile, and planed to have a prototype hosted on Windows Azure. So the previous two posts are preparation. In this post I am going to describe how to host a Node.js website on Windows Azure Web Site.

 

Node.js on Windows Azure Web Site via Git Deployment

Windows Azure Web Site (a.k.a. WAWS) is a new service which introduced in Microsoft Meet Windows Azure event on June. It provides a low-cost, build-in templates and simple way to host websites on the cloud. For more information about WAWS, please refer to my previous posts here, here and here.

WAWS is the simplest way for us to deploy our Node.js website I think.

-  IIS and IISNode are installed and configured in WAWS environment, so that we don’t need to install and anything when deployment.

- WAWS supports multiple deployment approaches such as TFS, FTP and Git. For Node.js, FTP and Git deployment are very easy and quick.

- WAWS provides three types of scale mode, which are free, shared and reserved. The cost is lower than Windows Azure Cloud Service (a.k.a. WACS) if we only need a website with a database.

- WAWS supports Windows Azure SQL Database (a.k.a. WASD) as well as MySQL.

 

Let’s have a look on how simple and easy to have a website on WAWS in Node.js. First of all, we need to create a new WAWS. Since I will demonstrate how to use WASD from Node.js later this post we will create a WASD here as well. Go to windows azure developer portal, from the NEW button select COMPUTER, WEB SITE, CREATE WITH DATABASE.

image

Once the website was created we go to its dashboard, click the Set up Git publishing link on the right side.

image

After few seconds Windows Azure will finish the Git deployment configuration. If this is the first time you configured Git or FTP deployment on Windows Azure, you need to click the Reset deployment credentials link on the dashboard to provide the deployment user name and password.

image

Next, let’s clone the Git repository from Windows Azure to our local disk. If you don’t have Git installed on your machine you can download it here. After installed we will open the Git Bash from the start menu Git folder. In the command windows navigate to the folder we want to clone. For example in my case I ‘m going to use the folder “D:\Research\nodejs\apps\nodejssample” so I need to go to ““D:\Research\nodejs\apps” and make sure the folder “nodejssample” does NOT exists, since Git will create this folder when it clones.

Back to the developer portal deployment page, copy the Git URL and then execute the Git clone command as shown below. When it needs password, specify the password we set in the previous step.

image

Now the repository had been cloned from WAWS to our local disk. Then we will use another GUI tool to commit and push our changes named GitHub for Windows.

GitHub for Windows is a GUI tool running on Windows for easily control the repositories on GitHub. We can use this tool to control our repository cloned from WAWS as well. You can download it here.

Open the GitHub for Windows, open the folder where the repository we had just cloned in file explorer, and then drag this folder into the GitHub for Windows.

image

When we clicked this repository on the GitHub windows we need to input the credentials, which is what we specified in developer portal. Currently there no changes in local folder.

Since the WAWS hosts Node.js application through IIS and IISNode, it will start the JavaScript file named “server.js”. So we must create a source file named “server.js” and this is the entry of our website. Create a web server and let it listen on port which come from “process.env.PORT”.

“process.env.PORT” means it will retrieve the port number from the environment variant named “PORT”. Since the WAWS hosts all website behind the windows azure router and firewall, this environment variant represents the correct internal port our WAWS is listening on.

   1: var http = require("http");
   2:  
   3: http.createServer(function (req, res) {
   4:     res.writeHead(200, {"Content-Type": "text/plain"});
   5:     res.end("Hello Node.js and Windows Azure Website!\n");
   6: }).listen(process.env.port);
   7:  
   8: console.log("Server started.");

Once we saved the file and back to GitHub window we would found it detected this change. Then we can commit it.

image

Then click the “publish” button on top of the window. It will publish our changes to the WAWS remote repository, and then WAWS will begin to deploy.

image

Now back to windows azure developer portal, there will be a new deployment in the deployment page under this website and have a try.

image

NPM Modules in Windows Azure Web Site

Use NPM modules and deploy modules alone with our Node.js website is pretty simple, too. All modules we added are located at the “node_modules” subfolder so we do not need any extra effort.

For example, I installed the “express” module in my website through the NPM command. It downloaded all necessary files to “node_modules” subfolder. And then I will change my code to use “express”.

   1: var express = require("express");
   2: var app = express();
   3:  
   4: app.get("/", function(req, res) {
   5:     res.send("Hello Node.js, Express and Windows Azure Web Site.");
   6: });
   7:  
   8: app.get("/Echo/:value", function(req, res) {
   9:     var value = req.params.value;
  10:     res.json({
  11:         "Value" : value,
  12:         "Time" : new Date()
  13:     });
  14: });
  15:  
  16: console.log("Web application opened.");
  17: app.listen(process.env.PORT);

Then go to GitHub, commit and sync to remote repository.

image

Then in windows azure we will found the new deployment.

image

And if we refresh the website home page we will find the new content. Also we can test the new function we added in this changes.

image

 

Work with Windows Azure SQL Database

Let’s continue use the SQL schema and data I mentioned in my previous post, execute the script below under the SQL Database I created at the beginning.

   1: /****** Object:  Table [dbo].[Resource]    Script Date: 9/4/2012 3:47:14 PM ******/
   2: SET ANSI_NULLS ON
   3: GO
   4: SET QUOTED_IDENTIFIER ON
   5: GO
   6: CREATE TABLE [dbo].[Resource](
   7:     [Key] [varchar](256) NOT NULL,
   8:     [Culture] [varchar](8) NOT NULL,
   9:     [Value] [nvarchar](4000) NOT NULL,
  10:  CONSTRAINT [PK_Resource] PRIMARY KEY CLUSTERED 
  11: (
  12:     [Key] ASC,
  13:     [Culture] ASC
  14: )WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF)
  15: )
  16:  
  17: GO
  18: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeAbout_Message', N'en-US', N'Your app description page.')
  19: GO
  20: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeAbout_Message', N'zh-CN', N'???????')
  21: GO
  22: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeContact_Message', N'en-US', N'Your contact page.')
  23: GO
  24: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeContact_Message', N'zh-CN', N'?????????')
  25: GO
  26: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeIndex_Message', N'en-US', N'Modify this template to jump-start your ASP.NET MVC application.')
  27: GO
  28: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Controller_HomeIndex_Message', N'zh-CN', N'??????,??????ASP.NET MVC?????')
  29: GO
  30: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_Password_Display', N'en-US', N'Password')
  31: GO
  32: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_Password_Display', N'zh-CN', N'??')
  33: GO
  34: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_Password_Required', N'en-US', N'Please input {0}.')
  35: GO
  36: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_Password_Required', N'zh-CN', N'???{0}?')
  37: GO
  38: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_RememberMe_Display', N'en-US', N'Remember me?')
  39: GO
  40: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_RememberMe_Display', N'zh-CN', N'???????')
  41: GO
  42: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_UserName_Display', N'en-US', N'User Name')
  43: GO
  44: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_UserName_Display', N'zh-CN', N'???')
  45: GO
  46: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_UserName_Required', N'en-US', N'Please input the {0}.')
  47: GO
  48: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_LoginModel_UserName_Required', N'zh-CN', N'???{0}?')
  49: GO
  50: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_ConfirmPassword_Compare', N'en-US', N'The password and confirmation password do not match.')
  51: GO
  52: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_ConfirmPassword_Compare', N'zh-CN', N'???????????')
  53: GO
  54: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_ConfirmPassword_Display', N'en-US', N'Confirm password')
  55: GO
  56: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_ConfirmPassword_Display', N'zh-CN', N'??????')
  57: GO
  58: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_Password_StringLength', N'en-US', N'The {0} must be at least {2} characters long.')
  59: GO
  60: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'Model_AccountModels_RegisterModel_Password_StringLength', N'zh-CN', N'{0}?????')
  61: GO
  62: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_ExtenalAccount', N'en-US', N'Use another service to log in.')
  63: GO
  64: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_ExtenalAccount', N'zh-CN', N'?????????')
  65: GO
  66: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_LocalAccount', N'en-US', N'Use a local account to log in.')
  67: GO
  68: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_LocalAccount', N'zh-CN', N'?????????')
  69: GO
  70: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_RegisterIfNoAccount', N'en-US', N'{0} if you don''t have an account.')
  71: GO
  72: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountLogin_RegisterIfNoAccount', N'zh-CN', N'??????,?{0}?')
  73: GO
  74: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountRegister_Message', N'en-US', N'Create a new account.')
  75: GO
  76: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_AccountRegister_Message', N'zh-CN', N'????????')
  77: GO
  78: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_ExternalLoginsListPartial_MessageInfo', N'en-US', N'There are no external authentication services configured. See <a href="http://go.microsoft.com/fwlink/?LinkId=252166">this article</a> for details on setting up this ASP.NET application to support logging in via external services.')
  79: GO
  80: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_ExternalLoginsListPartial_MessageInfo', N'zh-CN', N'???????????????????ASP.NET?????????????????,????<a href="http://go.microsoft.com/fwlink/?LinkId=252166">??</a>?')
  81: GO
  82: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_ExternalLoginsListPartial_SocialLoginList', N'en-US', N'Log in using another service')
  83: GO
  84: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_ExternalLoginsListPartial_SocialLoginList', N'zh-CN', N'?????????')
  85: GO
  86: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeAbout_Title', N'en-US', N'About')
  87: GO
  88: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeAbout_Title', N'zh-CN', N'??')
  89: GO
  90: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeContact_Title', N'en-US', N'Contact')
  91: GO
  92: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeContact_Title', N'zh-CN', N'????')
  93: GO
  94: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeIndex_Title', N'en-US', N'Home Page')
  95: GO
  96: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomeIndex_Title', N'zh-CN', N'??')
  97: GO
  98: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Featured', N'en-US', N'To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>. The page features <mark>videos, tutorials, and samples</mark> to help you get the most from ASP.NET MVC. If you have any questions about ASP.NET MVC visit <a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Forum">our forums</a>.')
  99: GO
 100: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Featured', N'zh-CN', N'???????ASP.NET MVC??????<a href="http://asp.net/mvc" title="ASP.NET MVC??">http://asp.net/mvc</a>??????<mark>??,?????</mark>,??????????ASP.NET MVC???????????ASP.NET MVC???,??????<a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC??">??</a>?')
 101: GO
 102: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest', N'en-US', N'We suggest the following:')
 103: GO
 104: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest', N'zh-CN', N'????:')
 105: GO
 106: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_1_Title', N'en-US', N'Getting Started')
 107: GO
 108: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_1_Title', N'zh-CN', N'??')
 109: GO
 110: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_1_Val', N'en-US', N'ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development. ASP.NET MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards. <a href="http://go.microsoft.com/fwlink/?LinkId=245151">Learn more...</a>')
 111: GO
 112: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_1_Val', N'zh-CN', N'ASP.NET MVC?????????????????????????,???????????,????,?????????????? ASP.NET MVC???????,????????????,?????Web??,TDD??????<a href="http://go.microsoft.com/fwlink/?LinkId=245151">????…</a>')
 113: GO
 114: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_2_Title', N'en-US', N'Add NuGet packages and jump-start your coding')
 115: GO
 116: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_2_Title', N'zh-CN', N'??NuGet???,??????')
 117: GO
 118: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_2_Val', N'en-US', N'NuGet makes it easy to install and update free libraries and tools. <a href="http://go.microsoft.com/fwlink/?LinkId=245153">Learn more...</a>')
 119: GO
 120: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_2_Val', N'zh-CN', N'NuGet??????????????????????<a href="http://go.microsoft.com/fwlink/?LinkId=245153">????…</a>')
 121: GO
 122: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_3_Title', N'en-US', N'Find Web Hosting')
 123: GO
 124: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_3_Title', N'zh-CN', N'??????')
 125: GO
 126: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_3_Val', N'en-US', N'You can easily find a web hosting company that offers the right mix of features and price for your applications. <a href="http://go.microsoft.com/fwlink/?LinkId=245157">Learn more...</a>')
 127: GO
 128: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_HomIndex_Suggest_3_Val', N'zh-CN', N'???????????Web????,?????????????????????<a href="http://go.microsoft.com/fwlink/?LinkId=245157">????…</a>')
 129: GO
 130: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_Layout_LogoHere', N'en-US', N'your logo here')
 131: GO
 132: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_Layout_LogoHere', N'zh-CN', N'???????')
 133: GO
 134: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_Layout_Title', N'en-US', N'My ASP.NET MVC Application')
 135: GO
 136: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_Layout_Title', N'zh-CN', N'??ASP.NET MVC????')
 137: GO
 138: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Login', N'en-US', N'Log in')
 139: GO
 140: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Login', N'zh-CN', N'??')
 141: GO
 142: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Logoff', N'en-US', N'Log off')
 143: GO
 144: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Logoff', N'zh-CN', N'??')
 145: GO
 146: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Register', N'en-US', N'Register')
 147: GO
 148: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_LoginPartial_Register', N'zh-CN', N'??')
 149: GO
 150: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_About', N'en-US', N'About')
 151: GO
 152: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_About', N'zh-CN', N'??')
 153: GO
 154: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_Contact', N'en-US', N'Contact')
 155: GO
 156: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_Contact', N'zh-CN', N'????')
 157: GO
 158: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_Home', N'en-US', N'Home')
 159: GO
 160: INSERT [dbo].[Resource] ([Key], [Culture], [Value]) VALUES (N'View_PageName_Home', N'zh-CN', N'??')
 161: GO

Then we need to add the node-sqlserver module in our local repository. As I mentioned in the previous post the node-sqlserver works with SQL Server and WASD. But there’s a problem if we are using NPM to install it. If you remembered, I specified that when NPM installed node-sqlserver it will build some C++ code by Python. The output binary depends on the local machine and Python whether it’s x86 or x64. If our local machine was x86 then this is no problem. But if our local machine is x64, the node-sqlserver module cannot be used on WAWS, since in WAWS all website are hosted in IIS in x86 WOW mode.

One solution is to change our development machine to x86, which may not be acceptable since x64 is preferred for other Windows Azure services development such as WASD and WACS.

Or maybe we can find a x86 machine to download and compile a x86 version of node-sqlserver for us, then copy it to our working PC.

Or we can download the x86 version node-sqlserver from Microsoft, which named “Microsoft Driver for Node.JS for SQL Server Preview” found here. Then follow its instruction we could install and copy the “node-sqlserver” folder under the “node_modules” folder in our repository.

Now we can use the similar source code in previous post to work with WASD, just need to change the connection string. The code would be like this.

   1: var express = require("express");
   2: var sql = require("node-sqlserver");
   3:  
   4: var connectionString = "Driver={SQL Server Native Client 10.0};Server=tcp:{YOUR SERVER NAME}.database.windows.net,1433;Database=nodejssample;Uid={YOUR LOGIN}@{YOUR SERVER NAME};Pwd={YOUR PASSWORD};Encrypt=yes;Connection Timeout=30;";
   5: var port = process.env.PORT
   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);

The connection string can be found in our WASD page in developer portal. In Node.js we need to use the ODBC connection string and make sure change the password before commit and sync.

Save the file and in GitHub window commit and sync it. Our website will be deployed automatically.

image

If we changed a little bit of our C# console application then we can test the post function in our Node.js application hosted on WAWS. The updated C# console code would like this, just changed the remote URL.

   1: static void Main(string[] args)
   2: {
   3:     var key = args[0];
   4:     var culture = args[1];
   5:     var val = args[2];
   6:  
   7:     var req = HttpWebRequest.Create("http://nodejssample.azurewebsites.net/new");
   8:     req.ContentType = "application/x-www-form-urlencoded";
   9:     req.Method = WebRequestMethods.Http.Post;
  10:  
  11:     var param = string.Format("key={0}&culture={1}&val={2}", key, culture, val);
  12:     var bytes = System.Text.Encoding.UTF8.GetBytes(param);
  13:     req.ContentLength = bytes.Length;
  14:     using(var stream = req.GetRequestStream())
  15:     {
  16:         stream.Write(bytes, 0, bytes.Length);
  17:     }
  18:  
  19:     var res = req.GetResponse();
  20:     using (var sr = new StreamReader(res.GetResponseStream()))
  21:     {
  22:         Console.WriteLine(sr.ReadToEnd());
  23:     }
  24:     Console.ReadKey();
  25: }

Then run this application to add some records in WASD.

image

And then back to the browser and find the items we had just inserted.

image

 

Summary

In this post I demonstrated on how to host Node.js website on Windows Azure Web Site. It’s very simple and easy to deploy by using the Git command client and the new GitHub GUI.

I also described how to use Windows Azure SQL Database from Node.js on Windows Azure Web Site. Make sure you imported the right version of sqlserver.node. For better development and cloud environment we can have two node-sqlserver modules, x86 version in folder “node-sqlserver-x86” while x64 in “node-sqlserver-x64”. Then we can import the x64 on local development and changed it to “node-sqlserver-x86” before commit and sync to azure.

In Windows Azure there are some other services besides SQL Database such as Storage, Service Bus, etc.. Almost all Windows Azure services can be used by Node.js through another module named “azure”, which is Windows Azure Node.js SDK. In the next post I will demonstrate on how to host our Node.js application on Windows Azure Cloud Service Worker Role, how to consume the Storage Service and how to retrieve the Cloud Service configuration.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.


In my previous post I demonstrated how to install, configure and run Node.js application on Windows. In this post I will cover a specific and short topic, which is how to use SQL Server in Node.js.

 

Install SQL Service Module

In NPM there are many SQL Server modules. From the command windows we can use “npm find” to find the modules for specified terms. For example, below is the result when I found “sqlserver”.

image

In this post I will describe one of them, the “node-sqlserver” module. This module was built by Microsoft. It utilizes a library built in C++ as the bridge between the JavaScript API and the backend SQL Server Native Client. When we download and install this module, it will retrieve the source code from NPM, and it will compile the C++ code by a script written in Python. Hence there are some requirement components.

- Node.js: version 0.6.10 or higher. But per MS said it’s not fully tested under the 0.8.x Node.js.

- Python 2.7.x: It’s said that Python 3.x is not acceptable.

- Visual C++ 2010: Installed alone with Visual Studio Express 2010 or higher.

- SQL Server Native Client 11.0: Can be found at SQL Server 2012 Feature Pack.

If we have those installed then we can use NPM to download and install this module.

image

After it installed there is still one step need to be done manually. Seems like a bug in the module installation package, by default the binary of the SQL client (which compiled from the C++ source code) was not in the module’s library folder. So we need to do it by ourselves.

For example, assuming we installed node-sqlserver under the folder “D:\Research\nodejs\apps”, then we need to go to “D:\Research\nodejs\apps\node_modules\node-sqlserver\build\Release”, find a file named “sqlserver.node”, copy it to “D:\Research\nodejs\apps\node_modules\node-sqlserver\lib”.

image

The sqlserver.node will be compiled based on whether we installed x86 or x64 of Python. Basically if we are using x64 Node.js then we should use x64 Python to get x64 sqlserver.node. Otherwise the Node.js will raise an exception said it cannot find “sqlserver.node”.

 

Querying in Node.js

Here I would like to use the database I introduced in my Caspar project. It only contains one table with 3 columns which is very simple. The full schema and data creation script is listed below.

   1: /****** Object:  Table [dbo].[Resource]    Script Date: 9/4/2012 3:47:14 PM ******/
   2: SET ANSI_NULLS ON
   3: GO
   4: SET QUOTED_IDENTIFIER ON
   5: GO