======================================================
QUICK NOTE: By readers request - browse towards the end to find file upload example using Node and formidable.
Seriously. Look at the below code snippet.
var http = require('http')
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type":"text/plain"})
response.write("Hello World")
response.end()
}).listen(8888)
And you wrote a HTTP server. Its as easy. This is not the end but just the start. This is written in NodeJS.
You can find the Node installation guide here.
To run this issue this in the terminal node server.js - assuming you wrote the above code in a file called server.js. Then fire a browser and access localhost:8888 - and we will see "Hello world" printed on your browser.
Another thing to note in the above code snippet - we passed an anonymous function to anther function - which is an important characteristic of a functional language. Why should I consider NodeJS in the first place? There are so many to choose from. Alright, not all technologies are fit for everything. Node is not fit for everything too. It has its own forte. Java and likes are good in their area Node and likes are good in their territory. Node JS suits well at the frontend server layer - that supplies webpages, handles user interactions, constructs dynamic pages, talks to other system asynchronously. But why we should use Node in these areas? The major essence of the answer lies in the code snippet - Node is event driven. Does it make it fast? No - in raw speed it is not fast, but its being event-driven and asynchronous it provides better responsiveness to the users - does not block calls. This feature in Node is called evented IO - we will examine that later what it really is - and how it works.
IMPATIENTS CAN SKIP THIS SECTION
Now let me take a step back and tell you where I am going and why. The context is fairly complex web application. In web applications we need to consider user interaction, page rendering, occasionally internationalization/localization, form handling, file upload/download, interacting with other systems, information processing, algorithmic processing, etc. Not a single technology is good for all these tasks. In this post we are going to see a good technology stack that works - achieves better results.
IMPATIENTS CAN RESUME HERE
Enough talks. Lets create a RESTful web-service. We will just refactor the the above code.
server.js
var http = require('http')
var url = require('url')
function start() {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname
console.log("Request for pathname " + pathname + " received")
response.writeHead(200, {"Content-Type": "text/plain"})
response.write("Hello world from refactored server")
response.end()
}
http.createServer(onRequest).listen(8888)
}
start()
To understand in little deeper Node nitigrities I recommend to start with awesome book The Node Beginner Book by Manuel Kiessling. The examples here are heavily influenced by this book. I refactored codes there in order to make it sometimes efficient and sometimes simple.
Lets write one more program called router.js - a special program to handle the incoming requests. We will figure out how to use it in context.
function route(pathname) {
console.log("request for path - " + pathname + " received through function route")
}
exports.route = route
exports.route makes route (any member - here is a function) available to other programs - remember it.
A little refactoring of server.js.
var http = require('http')
var url = require('url')
function start(route) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname
route(pathname)
response.writeHead(200, {"Content-Type": "text/plain"})
response.write("Hello world from refactored server")
response.end()
}
http.createServer(onRequest).listen(8888)
}
exports.start = start
Now see we exported the method start. That means we want to call it from another program and not executing within server.js.
The difference here is in line 8. We are delegating the processing of pathname to another function route in another program - router.js.
You can just run the server by adding 2 more lines - one in the beginning - var router = require('/router.js') and one in the end - start(router.route). Now run server.js. - node server.js. Point the browser to http://localhost:8888/anything and observe the console.
If you want to make the server.js working without the additional two lines - which is the the right way - add another program - lets name is index.js. As simple as it could be -
var server = require ('./server.js')
var router = require('./router.js')
server.start(router.route)
Run node index.js instead of server.js, hit the browser - observer the console.
Cool, router.js so far is in very primitive stage now - lets enhance it. Lets introduce one more program called handler.js. That maps the incoming requests to appropriate functions.
var handle = {}
handle['/'] = start
handle['/start'] = start
handle['/upload'] = upload
function start() {
console.log("method start")
}
function upload() {
console.log("method upload")
}
exports.handle = handle
In order to use this we need to refactor router.js, server.js remains unchanged.
The refactored router.js
var handler = require('./handler.js')
var handle = handler.handle
function route(pathname) {
console.log("request for path - " + pathname + " received through function route")
if(typeof handle[pathname] === 'function') {
handle[pathname]()
}
else {
console.log("No request handle found for pathname " + pathname)
}
}
exports.route = route
Now you can just run node index.js. Access http://localhost:8888/start, try http://localhost:8888/upload and also try something other than satrt and upload, and observer the console.
We have pretty decent HTTP server that has RESTful service capability. Being a heavy Spring 3.1/Roo user - I still can appreciate the rapid development capability using Node - more than rapid development capability - it has some more features that makes it appealing/useful. That is more directness - no big un-understandable convention over configuration theory - it has rather a very astute functional trait - simple and direct programming model. Productivity and great programming model alone can not be the criteria to choose a programming language - we must see what else it brings to us. Non-blocking evented IO is the coolest thing I have seen in a while, which makes user interaction more responsive.
Node is not the best in many things - and we must not use it in those areas. Node is not very good in heavy information processing, algorithmic tasks etc. We can of course utilize Node's capability to delegate task in non-blocking way to other programs written in capable languages (may be to Scala or Java for the grandpas - I am uncle at this moment:)) for heavy processing and algorithmic tasks. And Node can be used to handle user interactions, page rendering and and a as top-middleware integration layers - that does the delegation without blocking. BTW - Node has very close connection with C++, and we may want to write some node stuffs in C++ - I have not done this yet myself, if anybody did it, please post your experience here.
If we talk in official node dialect - Node is an one big, efficient event loop. To read more about it Mixu's tech blog - Understanding the node.js event loop
FILE UPLOAD EXAMPLE - Using Node JS and Formidable
Lets refactor the code to have a file upload application - I am going to post the the complete code and only code here. It is improved version of what is on the Manuel Kiessling's book.
index.js
var server = require ('./server.js')
var router = require('./router.js')
server.start(router.route)
server.js
var http = require('http')
var url = require('url')
function start(route) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname
route(pathname, request, response)
}
http.createServer(onRequest).listen(8888)
}
exports.start = start
router.js
var handler = require('./handler.js')
var handle = handler.handle
function route(pathname, request, response) {
console.log("request for path - " + pathname + " received through function route")
if(typeof handle[pathname] === 'function') {
handle[pathname](request, response)
}
else {
console.log("No request handle found for pathname " + pathname)
}
}
exports.route = route
handler.js
var handle = {}
var querystring = require('querystring'),
fs = require('fs'),
formidable = require('formidable')
handle['/'] = start
handle['/start'] = start
handle['/upload'] = upload
handle['/show'] = show
function start(request, response) {
console.log("method start")
response.writeHead(200, {"Content-Type": "text/html"})
response.write(require('./pages.js').body)
response.end()
}
function upload(request, response) {
console.log("method upload has been called")
response.writeHead(200, {"Content-Type": "text/html"})
var form = formidable.IncomingForm()
var util = require('util'),
fields = [],
files = [],
i = 0
form
.on('error', function(err) {
console.log("error handled?")
response.end("=>Error occured. Did you try to access a post only link without post data?")
})
.on('field', function(field, value) {
console.log(field, value)
fields.push([field, value])
})
.on('file', function(field, file) {
console.log(field, file)
files.push([field, file])
i += 1
fs.rename(file.path, "/tmp/tttest"+i+".jpg", function(err) {
console.log("renamed")
if(err) {
console.log("error renaming")
fs.unlink("/tmp/test.jpg")
fs.rename(file.path, "/tmp/tttest"+i+".jpg")
}
})
response.write("eeee
")
})
.on('end', function() {
console.log(util.inspect(files))
response.end("something got uploaded")
})
form.parse(request)
}
function show(request, response) {
console.log("method show")
fs.readFile("/tmp/tttest1.jpg", "binary", function(error, file) {
if(error) {
response.writeHead(500, {"Content-Type": "text/plain"})
response.write("error" + "\n")
response.end()
} else {
response.writeHead(200, {"Content-Type": "image/jpeg"})
response.write(file, "binary")
response.end()
}
})
}
exports.handle = handle
pages.js
var body = ''+
''+
''+''+''+
''+
''+
'';
exports.body = body
Just make sure this pages.js is well formatted. All other files should be good but since this one has HTML code into it - I made some deliberate spelling mistakes in tag names - make sure that are right and well formatted - and it will work.
====== Enjoy the Node.JS - rest of the article will be available soon ======
Lets bring the Rabbit here - Welcome RabbitMQ. We just need it to delegate tasks from node to Java
======= Adding ======
As the first step to integrating NodeJS and Java using AMQP (RabbitMQ), lets first see how to connect NodeJS and RabbitMQ - both publishing messages and consuming messages. A little bit of change in server.js, have a look.
server.js
var http = require('http')
var url = require('url')
var amqp = require('amqp')
function start(route) {
var connection = amqp.createConnection({ host: 'localhost' })
connection.setMaxListeners(200)
connection.on('ready', function() {
var exchange = connection.exchange('amq.fanout', {'type': 'fanout', durable: true}, function() {
var queue = connection.queue('queueA', {durable: false, exclusive: true}, function() {
queue.subscribe(function(msg) {
console.log("Message received: " + msg.data.toString())
})
queue.bind('amq.fanout', 'key.b.a')
queue.on('queueBindOk', function() {
http.createServer(onRequest).listen(8888)
})
})
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname
route(pathname, request, response, exchange)
}
})
})
}
exports.start = start
There are some changes in this code snippet above, lets analyze those chnages minutely before we go to the codes. Line number 11 - thats says connection.setMaxListeners(200) This one I had to use to get rid of one problem I was getting on the node console (node) warning: possible EventEmitter memory leak detected. 21 listeners added. Use emitter.setMaxListeners() to increase limit
Exchange and Queue, at line number 13-15 - this code will wait for messages in the defined queue, and if there is any message in the defined queue it will print it on the console. Line number 17 is just the queue binding - remember this is JavaScript and asynchronous so this line may get executed before others in this function. Line number 19-21 I am creating the HTTP server upon successful binding of the queue. Everything else remains same except one more line, line number 25, I added one more parameter to the function route(pathname, request, response, exchange), that is exchange, since this is required to publish a message to the designated queue, we are passing it to the router and then handler so that respective methods can use it in order to publish messages. As simple as that.
Lets look at router.js as the next piece of code, change is very small, only at one place - I am going to talk about that after the code snippet.
router.js
var handler = require('./handler.js')
var handle = handler.handle
function route(pathname, request, response, exchange) {
console.log("request for path - " + pathname + " received through function route")
if(typeof handle[pathname] === 'function') {
handle[pathname](request, response, exchange)
}
else {
console.log("No request handle found for pathname " + pathname)
}
}
exports.route = route
Change is only at line number 5 and line number 9; the new parameter exchange, if you remember I passed it from server.js. This will be used from a method in handler.js to publish messages to the defined queue. Lets see handler.js now.
Notice that I reduced handler.js code for brevity by deleting all file upload related part that was there before. Nevertheless, this code is fully functional, if you want to try this out.
handler.js
var handle = {}
var querystring = require('querystring'),
fs = require('fs'),
formidable = require('formidable')
handle['/publish'] = publish
handle['/messageform'] = messageform
function messageform(request, response, exchange) {
console.log("method messageform")
response.writeHead(200, {"Content-Type": "text/html"})
response.write(require('./messageform.js').body)
response.end()
}
function publish(request, response, exchange) {
var form = formidable.IncomingForm()
form
.on('error', function(err) {
console.log("error handled?")
response.end("=>Error occured. Did you try to access a post only link without post data?")
})
.on('field', function(field, value) {
console.log(field, value)
exchange.publish("key.a.b", value)
})
.on('end', function() {
response.end("something good happened")
})
form.parse(request)
console.log("method publish")
response.writeHead(200, {"Content-Type": "text/html"})
response.write(require('./messageform.js').body)
response.end()
}
exports.handle = handle
We are almost done - if you run this project with node index.js and point your browser at http://localhost:8888/messageform, the line number 16 of the above code would be invoked. Now wait a minute you need one more file, thats the final one to demonstrate AMQP communication - that is messageform.js. Look at line number 19 in the above code and you will understand why that is required. Its essentially a HTML snippet. Its very difficult to list HTML file on typepad, let me see how much I can. You will get a clear idea in either way.
messageform.js
var body = ''+
''+
' '+' '+''+
''+
' '+
' ';
exports.body = body
Now once you run the project by using node index.js and point your browser at http://localhost:8888/messageform, you will see the magic. Do not forget to run RabbitMQ on your localhost too.
Node and Java communication - Now we understand how to publish and subscribe messages from NideJS using AMQP server; its just easy to connect Java with NodeJS if we can write Java AMQP publish/subscribe code. Its just easy - spring-amqp also has a nice project to do that. I will write a simple Java program sometimes later if required. Let me know if that is something you guys want.
=================
NodeJS with MongoDB is coming soon - stay tuned. Want it fast - then get ready to pay for it :)