Awesome Chat
Express
Welcome to Express
We will see more functionality and syntax in Jade as we work on our chat application in the next chapter. In the HTML code generated, we can see that /stylesheets/style.css is being referred to; this file is served by the static file server we configured in the app. We can find this and the other files in the public folder. To run this application, we will use npm. Run the following command on the console: npm start Then go to http://localhost:3000/. Summary In this chapter we were introduced to the Node.js and Express web frameworks. As mentioned earlier, this is in no way a complete introduction to Node.js or Express. To learn more, please refer to the vast documentation available online or any of the books written on Node.js web development. Let's Chat Beginning with Yahoo! Chat in the early 2000 and up to today's popular Google Talk or Facebook Chat, chatting has been the most popular form of real-time communication on the Internet. In this chapter, we will build a chat room using node and express, which we learned in the previous chapter, and the socket.io library that we will learn in this chapter. Creating the application Similar to the way we created our application in the previous chapter, we will create a new awesome-chat application by executing the following commands in the command line: $ express awesome-chat $ cd awesome-chat $ npm install This will create our application and install the express application dependencies. Open the package.json file and change the name to awesome-chat, as shown in the following code snippet: { "name": "awesome-chat", "version": "0.0.1", "private": true, "scripts": { "start": "node app" }, "dependencies": { "express": "3.0.0rc2express": "3.x", "jade": "*" } } Let's Chat [ 44 ] Designing the chat room Let's modify the view to make it look like a chat room. We will need an area to display the messages, a text input for the user to enter the message, and a button to send the message. We will add some aesthetic elements, such as a header, banner, and footer. When we are done, our chat room user interface should look like the one shown in the following screenshot: Awesome chat UI Let's start editing layout.jade by adding a header and footer to it: doctype 5 html block head title= title link(rel='stylesheet', href='/stylesheets/style.css') body header#banner h1 Awesome Chat block content footer Hope you enjoy your stay here The first change we make is to add the block keyword before head. This makes head a block, to which we can append content from the extending pages. Chapter 3 [ 45 ] The other change is the addition of a new header and footer. Note that we are using the header and footer tags from HTML5. This code also introduces us to a new jade syntax. When we write header#banner, it will generate headers with banner as the id value. The generated HTML code will be as follows:Awesome Chat
' + data.message + '
'); }); $(function(){ $('#send').click(function(){ var data = { message: $('#message').val(), type:'userMessage' }; socket.send(JSON.stringify(data)); $('#message').val(''); }); }); The first step in starting the chat is to connect to the server: var socket = io.connect('/'); This will send a connection request to the server from which the page was loaded. This will also negotiate the actual transport protocol and will finally result in the connection event being triggered on the server app. The following code snippet connects the event handler for the message event: socket.on('message', function (data) { data = JSON.parse(data); $('#messages').append('' + data.message + '
'); }); Let's Chat [ 54 ] All we have to do with the incoming message is to append it to the messages area. We are adding one additional detail here by setting the class property for the newly appended div tag to be of the same type as that of the message. We can later use this to give a different look to the different types of messages. The last thing to do on the client side is to send the messages from the user. This will be done when the user writes his/her message in the message box and clicks the Send button. So, let's add an event handler to the Send button. The important thing about UI elements' event handlers is that they should be attached once the element is added to the document, that is, after it is created and ready. jQuery provides a convenient method to detect when the document is ready and adds a handler function to execute. There are two ways to do this; one is the following: $(document).ready(function(){ //Execute once the document is ready }); The shortcut for the same is as follows: $(function(){ //Execute once the document is ready }); Once the document is ready, we attach the event handler to the click event of the Send button: $(function(){ $('#send').click(function(){ var data = { message: $('#message').val(), type:'userMessage' }; socket.send(JSON.stringify(data)); $('#message').val(''); }); }); On clicking the Send button, we create our data object, setting the content of the message box as message, and type as userMessage. We can then use the socket. send method to send this data to the server. As you can see from the preceding code snippet, the syntax for sending messages from the client is the same as that of the server, and here too the message will be sent as a sting, which we create using JSON.stringify(data). Chapter 3 [ 55 ] Like the connection event and other predefined events on the server, we have some predefined events on the client too. These are as follows: • socket.on('connect', function () {}): The connect event is emitted when the socket is connected successfully. • socket.on('connecting', function () {}):The connecting event is emitted when the socket is attempting to connect with the server. • socket.on('disconnect', function () {}): The disconnect event is emitted when the socket is disconnected. • socket.on('connect_failed', function () {}): The connect_failed event is emitted when socket.io fails to establish a connection to the server and has no more transports to fall back to. • socket.on('error', function () {}): The error event is emitted when an error occurs and it cannot be handled by the other event types. • socket.on('message', function (message, callback) {}): The message event is emitted when a message sent by using socket.send is received. The message parameter is the sent message, and callback is an optional acknowledgment function. • socket.on('anything', function(data, callback) {}): The anything event can be any event except the reserved events. The data parameter represents the data, and callback can be used to send a reply. • socket.on('reconnect_failed', function () {}): The reconnect_ failed event is emitted when socket.io fails to reestablish a working connection after the connection was dropped. • socket.on('reconnect', function () {}): The reconnect event is emitted when socket.io is successfully reconnected to the server. • socket.on('reconnecting', function () {}): The reconnecting event is emitted when the socket is attempting to reconnect with the server. The last task to be done on the client side is to add socket.io and the chat scripts to our chat room page. Since these will not be used on every page, instead of adding them to layout.jade, we will add these to index.jade. Remember the change we had made to layout.jade, changing the code from head to block head? It will allow us to append the content from index.jade to the head tag: block append head script(type='text/javascript', src='/socket.io/socket.io.js') script(type='text/javascript', src='/javascripts/chat.js') Let's Chat [ 56 ] In the following line of code, we are using Jade's functionality to append content to a block in an inherited template from the child element. This is done using the append keyword. The syntax is as follows: block append ' + 'Hello '+data.name+' div>'); $('#send').click(function(){ var data = { message: $('#message').val(), type:'userMessage' }; socket.send(JSON.stringify(data)); $('#message').val(''); }); Making It More Fun! [ 62 ] socket.on('message', function (data) { data = JSON.parse(data); if(data.username){ $('#messages').append('
' + data.username + ": " + data.message + '
'); }else{ $('#messages').append('' + data.message + '
'); } }); }); $(function(){ $('#setname').click(function(){ socket.emit("set_name", {name: $('#nickname').val()}); }); }); In the previous code snippet, we add two new lines of code to hide the overlay and to append the greeting to the messages area. Apart from this, we have also moved the code to handle the sending and receiving of messages to this handler, so that it is set up only after the user has set the name and avoids people from just hiding the overlay using Firebug or other similar tools. There is one last change in the message received handler; we need to check for the presence of a username in the incoming data and prefix it to the displayed message if it is. Chapter 4 [ 63 ] To see the code in action, let's restart our node server and refresh the browser. Once you enter the name, it will bring up the chat room and show the greeting with the name you just entered along with the welcome message: Greeting with name Open our chat room in another browser window and sign in as Friend this time. Enter a message in the new message box and click Send. The message appears in the message area in both the browsers. Try it from the first chat room you have opened: Chat with names Making It More Fun! [ 64 ] More on events In the previous section, we saw how we can use custom events over a socket. The interesting thing is that just like your messages, events can also be broadcast. Let us see how we can use an event broadcast to announce the entry of a participant in our chat room. For this, the first thing we will do is start emitting a new user_entered event from the server, with the name of the user in the data once the user has joined the chat. Let us change our routes/socket.js file to do this. We will add our code to broadcast the user_entered event once the username is set. socket.on("set_name", function(data){ socket.set('nickname', data.name, function(){ socket.emit('name_set', data); socket.send(JSON.stringify({type:'serverMessage', message: 'Welcome to the most interesting" + "chat room on earth!'})); socket.broadcast.emit('user_entered', data); }); }); To send a broadcast to all the clients connected on this socket, we use the emit method, but on socket.broadcast rather than on socket itself. The signature of the method is the same. Now, the user_entered event will be sent to all the connected clients, so we will need to add an event handler in the client chat.js file. socket.on('name_set', function(data){ // EXISTING CODE socket.on("user_entered", function(user){ $('#messages').append('' + user.name + ' has joined the room.' + ' div>'); }); }); Chapter 4 [ 65 ] Here, we are adding an event handler for the user_entered event and then displaying the message to the user. Let us start our server once again and log in to our chat room: The first user's chat room Now open another browser window and log in with a different name: The second user's chat room Making It More Fun! [ 66 ] As you will notice, in the first user's window, we will see the entrance message for both Friend001 and Friend002, and for Friend002 in the second user's (Friend001) window. Working with namespaces In this section, we won't be adding any new functionality to our chat room, but instead we will just use a feature of socket.io to make our application design better and our code easier to maintain. We are sending different messages between the client and the server and differentiating them by type. Wouldn't it be better that we send different messages on different messaging channels? Our current approach also doesn't play well and may cause conflicts when our application or module is part of a larger system. But then there are questions, what will be the cost of opening multiple connections? What will be the effect on performance? This is where namespaces come to the rescue. A namespace provides a way to multiply a socket.io connection, so that we get different channels for different types of messages without adding a big overhead to the system and its performance. Let us see how we can use namespaces in our chat system. In our chat application, we have two different types of messages or events being sent. These are infrastructural, such as setting the name and welcome messages, and communication between the users. So let us go ahead and create two namespaces, namely chat_com and chat_infra. We will send the communication messages (user messages) on chat_com and the infrastructural messages (welcome, user entry, and so on) on chat_infra. For this, let us first edit the socket.js file, which is on the server: var io = require('socket.io'); exports.initialize = function (server) { io = io.listen(server); var chatInfra = io.of("/chat_infra") .on("connection", function(socket){ socket.on("set_name", function (data) { socket.set('nickname', data.name, function () { socket.emit('name_set', data); socket.send(JSON.stringify({type:'serverMessage', message:'Welcome to the most interesting ' + 'chat room on earth!'})); socket.broadcast.emit('user_entered', data); Chapter 4 [ 67 ] }); }); }); var chatCom = io.of("/chat_com") .on("connection", function (socket) { socket.on('message', function (message) { message = JSON.parse(message); if (message.type == "userMessage") { socket.get('nickname', function (err, nickname) { message.username = nickname; socket.broadcast.send(JSON.stringify(message)); message.type = "myMessage"; socket.send(JSON.stringify(message)); }); } }); }); } As we can see from the preceding code, most of the code remains the same, apart from the highlighted snippets and some code reorganization. What we are doing here is separating the messages and events into two code blocks corresponding to their namespaces. We use the io.of method to create a new namespace. Upon creation of the namespace, it can be used as any socket's object. In our case, we are creating two namespaces and adding a connection event handler to both of them. One for chat_infra, as shown in the following code snippet: var chatInfra = io.of("/chat_infra") .on("connection", function(socket){ And another for chat_com: var chatCom = io.of("/chat_com") .on("connection", function (socket) { Once the connection is established, we will get a socket object in the connection event handler, which we will use just as we did earlier. In case of chat_infra, we add all the messaging and events that are not part of the user-to-user communication: socket.on("set_name", function (data) { socket.set('nickname', data.name, function () { socket.emit('name_set', data); socket.send(JSON.stringify({type:'serverMessage', message:'Welcome to the most interesting ' + 'chat room on earth!'})); Making It More Fun! [ 68 ] socket.broadcast.emit('user_entered', data); }); }); So, we are moving the set_name handler, the event emitter for name_set, messaging for serverMessage, and the event broadcaster for user_entered to the chat_infra namespace. socket.on('message', function (message) { message = JSON.parse(message); if (message.type == "userMessage") { socket.get('nickname', function (err, nickname) { message.username = nickname; socket.broadcast.send(JSON.stringify(message)); message.type = "myMessage"; socket.send(JSON.stringify(message)); }); } }); This leaves only the standard User messaging components on the chat_com namespace. Let us now see how this affects our client code: var chatInfra = io.connect('/chat_infra'), chatCom = io.connect('/chat_com'); chatInfra.on('name_set', function (data) { chatInfra.on("user_entered", function (user) { $('#messages').append(') Here, server is the instance of the node HTTP server. Socket.IO Quick Reference [ 110 ] Listening to events The event handlers are attached to socket using the on method. The on method takes the event name and the callback/handler function as parameters: sio.on(, function(eventData){ //DO SOMETHING }); Here, event is the name of the event and eventData represents the event-specific data passed to the handler when it is invoked. Emitting an event We use the emit method to trigger an event. This event will be handled on the client: socket.emit(, , ack_callback); Here, event is the name of the event to trigger, event_data is the event data as a JSON object, and ack_callback is the optional callback function that is invoked on the successful receipt of the event on the client. Sending a message The send method is used to send a message to the client: socket.send(, ack_callback); Where message is the message that will be sent to the client and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the client. Sending a JSON message A JSON message can be sent by using the json flag before the send method: socket.json.send(, ack_callback); Here, message is the message that will be sent to the client and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the client. Appendix A [ 111 ] Broadcasting a message/event A message or an event can be broadcasted to all the connected sockets using the broadcast flag: socket.broadcast.emit(, ); Here, event is the name of event to emit and event_data is the JSON data that will be sent with the event. The following line of code shows how to broadcast a message: socket.broadcast.send(); Here, message is the message that will be sent to the client and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the client. Sending a volatile message Sometimes the message being sent is not important and can be ignored if not delivered. So these methods need not be queued or attempted to be redelivered. This is done with the volatile flag: socket.volatile.send(); Here, message is the message that will be sent to the client and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the client. Storing socket data We can call the set method on the socket to store some data on the socket. This is an asynchronous method call and takes a key, value, and a callback function: socket.set(, , function(){ //DO SOMETHING }); Here, key is the key name for this data and value is the value to store. Socket.IO Quick Reference [ 112 ] Getting the socket data We use the get method to fetch the value stored on a socket. This is an asynchronous method and takes a key and a callback function, which will get the value: socket.get(, function(value){ //DO SOMETHING }); Here, key is the key of the data to fetch and value is the value if stored with the socket. This will be null if the value is not stored. Restricting to a namespace We can multiplex the socket and restrict messages/events to a namespace by using the of method. This method returns a socket, which can be used as any other socket, but the messages will be restricted to only the clients connected to this namespace: var namespace_socket = socket.of(); Here, namespace is the namespace we want to restrict the socket to. Joining a room We use the join method of socket to join a room. It will create a new room if one doesn't already exist: socket.join(); Here, room is the name of the room to join. Broadcasting messages/events in a room We can send messages to all the connected clients in the room by using the in flag with broadcast: socket.broadcast.in().send(); socket.broadcast.in().emit(, ); Here, room is the room to send the message in, message is the message to send, event is the event to emit, and event_data is the data to be sent with the event. Appendix A [ 113 ] Leaving a room The leave method is used to leave a room. We don't need to do this explicitly if the socket is exiting. Also, an empty room will automatically be destroyed: socket.leave(); Here, room is the room to exit from. Changing the configuration Socket.io is configured using the set method in the configure method's callback handler: io.configure('environment', function () { io.set(, ); }); Here, environment is the optional environment in which this configuration will be used, property is the property to be set, and value is the value for the property. Server events We will discuss some server-related events in this section. connection This event is fired when an initial connection with a client is established: io.sockets.on('connection', function(socket) {}) Here, socket is used in further communication with the client. message The message event is emitted when a message sent with socket.send is received: socket.on('message', function(, ) {}) Here, message is the message sent and ack_callback is an optional acknowledgment function. Socket.IO Quick Reference [ 114 ] disconnect This event is fired when the socket disconnects: socket.on('disconnect', function() {}) The client In this section we will get to know the client APIs. Connecting to a socket We connect to a socket using the connect method on the io object in the client: var socket = io.connect(); Here, uri is the server URI to connect to. It can be absolute or relative. If it is other than /, or an absolute equivalent of that, it will connect to the namespace. Listening to events We can attach event handlers to a socket using the on method: socket.on(, function(event_data, ack_callback){}); Here, event is the event to listen for, event_data is the data for the event, and ack_callback is the optional callback method to call to acknowledge the receipt of the event. Emitting an event We use the emit method to trigger an event. This event will be handled on the server: socket.on(, , ack_callback); Here, event is the name of the event to trigger, event_data is the event data as a JSON object, and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the server. Appendix A [ 115 ] Sending a message The send method is used to send a message to the server: socket.send(, ack_callback); Here, message is the message that will be sent to the server and ack_callback is the optional callback function that is invoked on the successful receipt of the message on the server. Client events In this section we will get to know some client-side events. connect The connect event is emitted when the socket is connected successfully: socket.on('connect', function () {}) connecting The connecting event is emitted when the socket is attempting to connect with the server: socket.on('connecting', function () {}) disconnect The disconnect event is emitted when the socket is disconnected: socket.on('disconnect', function () {}) connect_failed The connect_failed event is emitted when socket.io fails to establish a connection to the server for reasons such as when none of the transports work or authorization failed: socket.on('connect_failed', function () {}) Socket.IO Quick Reference [ 116 ] error The error event is emitted when an error occurs and it cannot be handled by the other event types: socket.on('error', function () {}) message The message event is emitted when a message sent with socket.send is received: socket.on('message', function (, ) {}) Here, message is the sent message and ack_callback is an optional acknowledgment function. reconnect The reconnect event is emitted when socket.io successfully reconnects to the server: socket.on('reconnect', function () {}) reconnecting The reconnecting event is emitted when the socket is attempting to reconnect with the server: socket.on('reconnecting', function () {}) reconnect_failed The reconnect_failed event is emitted when socket.io fails to reestablish a working connection after the connection was dropped: socket.on('reconnect_failed', function () {}) Socket.IO Backends Socket.io started in Node.js and the primary backend continues to be Node.js. This book focuses on building a chat system with socket.io, Node.js, and Express.js. But what if your primary platform of choice is not Node.js or you are working on a project where you want the same capabilities as provided by socket.io but cannot as you have an existing standardized platform and cannot bring a new system in the equation. Many before you have faced the same dilemma and in the spirit of open source, socket.io servers exist for various platforms. In this appendix let's take a look at the various implementations available for socket.io backends. Every platform will require you to apply the learning and logic from this book to rewrite the server-side code targeting that platform. The client code can continue to be the same. The following is an alphabetic list of the implementations by their languages/platforms: Erlang There are two different backends for socket.io on erlang, Yurii Rashkovskii's socket. io-erlang (https://github.com/yrashk/socket.io-erlang) and Yongboy's erlang-socketio(https://code.google.com/p/erlang-scoketio/). Yurii seems to have a disagreement with the path taken by socket.io's post-0.6.x releases, and so the library supports only up to Version 0.6 of the spec. Naturally, most of the examples in this book and many other examples on the Internet, won't work over it. Yongboy's erlang-socketio seems to be keeping itself up to date with the latest happenings in socket.io and is compatible with the latest spec for socket.io-1.0 at the time of writing. Thus we will focus the rest of this section on this library. Socket.IO Backends [ 118 ] This library is available for Cowboy and Mochiweb, two popular server-side frameworks in erlang. Both these versions support socket.io spec 1.0. The Cowboy version supports all the transports, while the Mochiweb version is limited to xhr-polling, htmlfile, and json-polling. Google Go Go is a language in its early years, but is gaining popularity, mainly due to the corporate backing from Google and being one of the three languages supported on the Google App Engine, beside Python and Java. The go-socket.io implementation provides socket.io support on Go. This project supports almost all the transports and also supports socket.io on Google's App Engine. The original codebase for this project is at https://github.com/madari/ go-socket.io, but the development there has stagnated for a while; but others seem to have taken up the torch. The socket.io wiki points to this fork: https://github.com/davies/go-socket.io. One thing to notice here is that this codebase still doesn't support versions higher than 0.6.x. Check out the forks created in github and you will find interesting developments being done to the code. Like this fork, which was updated much more recently: https://github.com/justinfx/go-socket.io. If you want to use a newer version of socket.io, the fork at https://github.com/ murz/go-socket.io should support versions up to 0.8.x (this was at the time of writing). Java There are multiple implementations available for socket.io on a Java server. Let's take a look at them. The first is Socket.IO-Java, maintained most actively at https://github.com/Ovea/ Socket.IO-Java. It has been forked and modified to work with various servers and platforms. Appendix B [ 119 ] Then there is Atmosphere. Atmosphere began as a project to bring server push to glassfish servers, but was spun off as a project of its own and works with almost any Java server. Atmosphere server comes with atmosphere.js, which is its own JS client, but any Atmosphere application will work with a socket.io client out of the box, without any modification; use https://github.com/Atmosphere/atmosphere/ wiki/Getting-Started-with-Socket.IO to get started with Atmosphere,. If you are starting a new java project or are introducing push in your existing java one, don't make a decision until you have taken a look at Atmosphere. Netty brings an asynchronous server to Java; and very important to mention is Yongboy's socketio-netty (http://code.google.com/p/socketio-netty/). It is highly recommended due to the async nature of netty, which is more suited for these applications. Gisio (https://bitbucket.org/c58/gnisio/wiki/Home) brings socket.io to the GWT framework, the Google's write-in-Java-and-compile-to-JS library. So if your application is built in GWT and you want to introduce server-push in your application, you can use this library. And for the new and upcoming completely asynchronous server Vert.x, there is mod-socket-io (https://github.com/keesun/mod-socket-io) Again, if you are looking at an application of a highly asynchronous nature, I would suggest you to take a look at this server and this module. Perl Perl may be a very old language, but is still used in many places, and it has an actively maintained socket.io server module called pocketio (https://github.com/ vti/pocketio). Python Python is another language that is gaining wide acceptance and popularity. And there are multiple socket.io server implementations for Python. The first we look at is gevent-socket.io (https://github.com/abourget/gevent- socketio), which works with any WSGI-based web frameworks. So if you are using any framework such as Pyramid, Pylons, Flask, and Django, this will work for you. The only dependencies are gevent and gevent-websocket. Socket.IO Backends [ 120 ] If Tornado is your framework of choice, take a look at Tornadio 2 (https://github.com/MrJoes/tornadio2), which provides support for socket.io Versions 0.7 and higher. Again, Tornado is an asynchronous framework and good for such applications. And dedicated to bringing socket.io to Django is django-socketio (https://github. com/stephenmcd/django-socketio). Summary In this chapter we saw the socket.io backend implementations for some popular platforms. If you are using some other platform, just search for a socket.io server implementation on the Internet and I am sure you will find one. It may not be the best or in an ideal state, but you definitely will find a solution to get started. Index A AJAX 10 AMID 21 application running 98 running, ways 99, 100 scaling up 105, 106 applications, real-time web business applications 14 gaming 13 social stream updates 13 web-based monitors 14 Asynchronous JavaScript and XML. See AJAX Asynchronous Module Definition. See AMID Atmosphere 119 authorization method 84 B BPM 8 business applications 14 Business Process Management. See BPM C chat 43 chat application creating 43 chat room designing 44-47 click event 54 client about 53-56 event, emitting 114 events, listening to 114 message, sending 115 socket, connecting to 114 client events connect 115 connect_failed 115 connecting 115 disconnect 115 error 116 message 116 reconnect 116 reconnect_failed 116 reconnecting 116 close method 89 Comet 11 connect event 115 connect_failed event 115 connecting event 115 connection event 55 connect method 114 Cowboy 118 createServer method 21 D Daemontools 101 disconnect event 115 django-socketio 120 E emit method 114 erlang-socketio 117 error event 116 events handling 49 user_entered event 64 [ 122 ] exit event 104 Express JS 34-42 F files serving 32-34 Forever 100 forever stop command 100 G gaming 13 get method 112 gevent 119 gevent-socket.io 119 gevent-websocket 119 Gisio 119 Go 118 H Haml 40 handshake property 85 HAProxy defining 101 working 101, 102 Hello Web 20-22 Hello World with Node.js 20 HTTP methods 27, 28 I initialize method 53 io.of method 67 io.sockets.on event 52 J Jade 40 JavaScript 9 join method 112 join_room event 73 jQuery adding 48 K ket.io-Java 118 L listen method 51 M message event 116 Mochiweb 118 mod-socket-io 119 modules creating 29-32 Monit defining 99 using 100 N name_set event 85 namespaces working with 66-70 Netty 119 node executing, tips 107 node cluster 103, 104 Node.js about 15 features 16-18 obtaining 19 origin 16 Node.js, features corporate backing 18 event-driven design 17, 18 JavaScript 16, 17 Node.js package manager (npm) 19 P Perl 119 Persevere 16 pocketio 119 production environment 97, 98 Python 119 R real-time web about 7, 8 AJAX Request 10 [ 123 ] applications 13 defining 8 history 8-13 HTTP browser-server interaction 9 reconnect event 116 reconnect_failed event 116 reconnecting event 116 Redis Session Store 105 request routing 23-26 RingoJS 16 rooms about 70, 72 connecting to 76, 77 listing 77-81 route.on method 27 S scaling 101 send method 115 server about 50, 51 configuration, leaving 113 emitting 110 event, broadcasting 111 event, broadcasting in room 112 events 51-53 instantiating 109 join method, using 112 JSON message, sending 110 leave method, using 113 listening to 110 message, broadcasting 111 message, broadcasting in room 112 message, sending 110 namespace, restricting to 112 socket data, getting 112 socket data, storing 111 socket.io server, starting 109 volatile message, sending 111 server events connection 113 disconnect 114 message 113 server.forRoute method 32 Server-Sent Events. See SSE serveStatic method 33 set method 111 set_name event 59 social stream updates 13 Socket.IO about 48, 87, 109 client 114 client events 115 server 109 server events 113 Socket.IO API 89, 90 Socket.IO backend about 117 erlang 117 Socket.IO connection 8080 92 about 91 http 91 myhost.com 91 responding ways 92 socket.io-erlang 117 Socket.io messages about 93 ACK 96 Connect 94 Disconnect 93 Error 96 Event 95 Heartbeat 94 JSON message 95 Message 94 NOOP 96 socketio-netty 119 socket.io socket 90, 91 socket.send method 52 SSE 13 start method 31 Supervisord 101 system messages 49 T third party modules 34-42 Tornadio 2 120 [ 124 ] W web-based monitors 14 WebSocket API 88, 89 WebSocket protocol limitations 87, 88 write method 22 Y yum command 99 U Upstart 101 user_entered event 64 user messages 49 user name sharing 81-85 users in chat room 63 name, assigning 57-63 V V8 16 Vert.x 119 Thank you for buying Socket.IO Real-time Web Application Development About Packt Publishing Packt, pronounced 'packed', published its first book "Mastering phpMyAdmin for Effective MySQL Management" in April 2004 and subsequently continued to specialize in publishing highly focused books on specific technologies and solutions. Our books and publications share the experiences of your fellow IT professionals in adapting and customizing today's systems, applications, and frameworks. Our solution based books give you the knowledge and power to customize the software and technologies you're using to get the job done. Packt books are more specific and less general than the IT books you have seen in the past. Our unique business model allows us to bring you more focused information, giving you more of what you need to know, and less of what you don't. Packt is a modern, yet unique publishing company, which focuses on producing quality, cutting-edge books for communities of developers, administrators, and newbies alike. For more information, please visit our website: www.packtpub.com. About Packt Open Source In 2010, Packt launched two new brands, Packt Open Source and Packt Enterprise, in order to continue its focus on specialization. This book is part of the Packt Open Source brand, home to books published on software built around Open Source licenses, and offering information to anybody from advanced developers to budding web designers. The Open Source brand also runs Packt's Open Source Royalty Scheme, by which Packt gives a royalty to each Open Source project about whose software a book is sold. Writing for Packt We welcome all inquiries from people who are interested in authoring. Book proposals should be sent to author@packtpub.com. If your book idea is still at an early stage and you would like to discuss it first before writing a formal book proposal, contact us; one of our commissioning editors will get in touch with you. We're not just looking for published authors; if you have strong technical skills but no writing experience, our experienced editors can help you develop a writing career, or simply get some additional reward for your expertise. Node Web Development ISBN: 978-1-849515-14-6 Paperback: 172 pages A practical introduction to Node, the exciting new server-side JavaScript web development stack 1. Go from nothing to a database-backed web application in no time at all 2. Get started quickly with Node and discover that JavaScript is not just for browsers anymore 3. An introduction to server-side JavaScript with Node, the Connect and Express frameworks, and using SQL or MongoDB database back-end Node Cookbook ISBN: 978-1-849517-18-8 Paperback: 342 pages Over 50 recipes to master the art of asynchronous server-side JavaScript using Node 1. Packed with practical recipes taking you from the basics to extending Node with your own modules 2. Create your own web server to see Node’s features in action 3. Work with JSON, XML, web sockets, and make the most of asynchronous programming Please check www.PacktPub.com for information on our titles Getting Started with Meteor.js JavaScript Framework ISBN: 978-1-782160-82-3 Paperback: 130 pages Develop modern web applications in Meteor, one of the hottest new JavaScript platforms 1. Create dynamic, multi-user web applications completely in JavaScript 2. Use best practice design patterns including MVC, templates, and data synchronization 3. Create simple, effective user authentication including Facebook and Twitter integration 4. Learn the time-saving techniques of Meteor to code powerful, lightning-fast web apps in minutes Learning Modernizr ISBN: 978-1-782160-22-9 Paperback: 118 pages Create forward-compatible websites using feature detection features of Modernizr 1. Build a progressive experience using a vast array of detected CSS3 features 2. Replace images with CSS based counterparts 3. Learn the benefits of detecting features instead of checking the name and version of the browser and serving accordingly Please check www.PacktPub.com for information on our titles
' + user.name + ' has joined the room.' + '
'); }); chatInfra.on('message', function (message) { var message = JSON.parse(message); $('#messages').append(' '); }); chatCom.on('message', function (message) { var message = JSON.parse(message); $('#messages').append(' '); }); $('#nameform').hide(); $('#messages').append('Hello ' + Chapter 4 [ 69 ] data.name + '
'); $('#send').click(function () { var data = { message:$('#message').val(), type:'userMessage' }; chatCom.send(JSON.stringify(data)); $('#message').val(''); }); }); $(function () { $('#setname').click(function () { chatInfra.emit("set_name", {name:$('#nickname').val()}); }); }); The first and the most important thing we see in the previous code is that we are connecting two sockets: var chatInfra = io.connect('/chat_infra'), chatCom = io.connect('/chat_com'); In fact, socket.io will establish a single socket connection and multiplex the two namespaces over it. But establishing these two connections will give us the ability to handle the chat_infra and chat_com namespaces' messages or events separately. In the following code snippet, we are adding the handlers that correspond to the emitters for chat_infra that we added on the server. The name_set handler will be on the chat_infra namespace: chatInfra.on('name_set', function (data) { We will also do the same for the user_entered handler: chatInfra.on("user_entered", function (user) { $('#messages').append('' + user.name + ' has joined the room.' + '
'); }); Next, we add the on handler to listen for the messages on chat_infra; this will receive all the server messages: chatInfra.on('message', function (message) { var message = JSON.parse(message); $('#messages').append(' '); }); Making It More Fun! [ 70 ] We also emit the set_name event on chat_infra: chatInfra.emit("set_name", {name:$('#nickname').val()}); On the chat_com namespace, we send the user message, as shown in the following code: $('#send').click(function () { var data = { message:$('#message').val(), type:'userMessage' }; chatCom.send(JSON.stringify(data)); Also, we will attach the handler to receive the user messages relayed from the server by using the following code snippet: chatCom.on('message', function (message) { var message = JSON.parse(message); $('#messages').append(' '); }); Now that we understand namespaces and have made use of them to clean up our design and code, let us go ahead and add some new features. Rooms In this section we will use another multiplexing feature of socket.io, called rooms. And we will use it to do just what the name says, create rooms. A chat room will be very noisy and confusing if everyone in the network is chatting in the same room. So as the first step, let's move our chat away from the landing page of our website to /chatroom. For this, we should move our code from index.jade to chatroom.jade and put the following code in index.jade: extends layout block content section#welcome div Welcome a#startchat(type="button", class="btn", href="/chatroom") Start now Chapter 4 [ 71 ] Basically, we will create a landing page with a welcome message and a link to go to the chat room. Let's also add the following styles for the landing page in style.css: #welcome div{ font-family: fantasy; font-size: 100px; margin-left: 20px; margin-top: 100px; } .btn { background-color: #5BB75B; background-image: linear-gradient(to bottom, #62C462, #51A351); background-repeat: repeat-x; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); color: #FFFFFF; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); border-image: none; border-radius: 4px 4px 4px 4px; border-style: solid; border-width: 1px; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; display: inline-block; font-size: 14px; line-height: 20px; margin-bottom: 0; padding: 4px 12px; text-align: center; vertical-align: middle; position: absolute; right: 40px; bottom: 80px; text-decoration: none; } Making It More Fun! [ 72 ] Now our landing page will look like this: The landing page The Start now link will send you to the chat room, but there is nothing there yet. So let us modify our routes/index.js file to serve chatroom. Add the following snippet to the end of the file: exports.chatroom = function(req, res){ res.render('chatroom', { title: 'Express Chat' }); } We will also have to add the mapping to app.js: app.get('/chatroom', routes.chatroom); Now that we have a landing page, we are ready to add multiple rooms. We will now add support for the chat room page so that it can accept a room parameter and will connect to that room when requested. So the call to connect to enter the chat room will look like this: http://localhost:3000/chatroom?room=jsconf For this we need to edit our chat.js client script file: var chatInfra = io.connect('/chat_infra'), chatCom = io.connect('/chat_com'); var roomName = decodeURI( (RegExp("room" + '=' + '(.+?)(&|$)').exec(location.search) Chapter 4 [ 73 ] || [, null])[1]); if (roomName) { chatInfra.on('name_set', function (data) { chatInfra.emit('join_room', {'name':roomName}); //EXISTING CODE }); } $(function () { $('#setname').click(function () { chatInfra.emit("set_name", {name:$('#nickname').val()}); }); }); The first thing is to parse the URL query to get the room name, here is how this is done: var roomName = decodeURI( (RegExp("room" + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]); Here, in the preceding code, we are creating a regex to parse out the value between room= and & or to the end of the content. In the next line, we check if a room name was provided and once the user has entered the name, we will join the room. To join the room, we emit the join_room event with roomName as a parameter. This event will be handled on the server: if (roomName) { chatInfra.on('name_set', function (data) { chatInfra.emit('join_room', {'name':roomName}); Since we will use the room only to restrict the broadcast messages (others are anyhow sent only to the recipient's socket), this is all we need to do on the client. Now we will edit the sockets.js file on our server to handle the join_room event on chat_infra and to change the broadcasts to send messages in the room they are meant for. Let us take a look at the changes in sockets.js: var io = require('socket.io'); exports.initialize = function (server) { io = io.listen(server); var self = this; Making It More Fun! [ 74 ] this.chatInfra = io.of("/chat_infra"); this.chatInfra.on("connection", function (socket) { //EXISTING CODE }); socket.on("join_room", function (room) { socket.get('nickname', function (err, nickname) { socket.join(room.name); var comSocket = self.chatCom.sockets[socket.id]; comSocket.join(room.name); comSocket.room = room.name; socket.in(room.name).broadcast .emit('user_entered', {'name':nickname}); }); }); }); this.chatCom = io.of("/chat_com"); this.chatCom.on("connection", function (socket) { socket.on('message', function (message) { message = JSON.parse(message); if (message.type == "userMessage") { socket.get('nickname', function (err, nickname) { message.username = nickname; socket.in(socket.room).broadcast.send(JSON. stringify(message)); message.type = "myMessage"; socket.send(JSON.stringify(message)); }); } }); }); } So this brings in some minor structural changes. Since we will need to refer chatCom in chatInfra, we add them both to the current object, which is also stored as itself, so that they are accessible in the closures. In the chat_infra connection handler, we register a new event handler for join_room: socket.on("join_room", function (room) { socket.get('nickname', function (err, nickname) { socket.join(room.name); var comSocket = self.chatCom.sockets[socket.id]; comSocket.join(room.name); comSocket.room = room.name; socket.in(room.name).broadcast .emit('user_entered', {'name':nickname}); }); }); Chapter 4 [ 75 ] In the handler, we are receiving the room object, which will in turn have the name of the room to join. Next we connect the chat_infra socket to the room. This is done using the join method of the socket object: socket.join(room.name); The join method takes a name string for the room. The room will be created if not present, else the socket will be connected to an existing room. Now, once our client joins the room, it will get all the messages intended for the specific room in the chat_infra namespace. But, this will not be useful until we also join the room in the chat_com namespace. For this, we will need to obtain the socket object, corresponding to the current socket object in the chat_com namespace and then call the same join method on it: var comSocket = self.chatCom.sockets[socket.id]; comSocket.join(room.name); To get the corresponding socket object on chat_com, we fetch it using the current socket object's ID (as it will be similar) from the sockets array in the chatCom namespace object. The next line simply calls the join method on it. Now both have joined the room in both the namespaces. But when we receive the messages in the chat_com namespace, we will need the name of the room this socket is connected to. For this, we will set the room property on the comSocket object to the room it is connected to: comSocket.room = room.name; Now that all is set up, we will announce in the room that the user has joined: socket.in(room.name).broadcast .emit('user_entered', {'name':nickname}); }); As we did earlier, we still use broadcast.emit, but instead of calling it on the socket, we restrict it to be sent only in the room, using in(room.name). Another change we make will be that of broadcasting the user messages again by restricting them to the room: socket.in(socket.room).broadcast.send(JSON.stringify(message)); Now you can open the chat room by going to the following URL: http://localhost:3000/chatroom?room=test001 Open this in two browser windows and log in with different names. Open another chat room in another browser window using the following URL: http://localhost:3000/chatroom?room=test002 Making It More Fun! [ 76 ] The messages and alerts sent only in the room test001 will be visible in the first two browsers, while the one connected to test002 will not be able to see them: User one connected to the room test001 Here is the second user connected to the room test001: User two connected to the room test001 Chapter 4 [ 77 ] Here, in the following screenshot, the third user is shown connected to the room test002: User three connected to the room test002 Listing the rooms Now that we have support for creating multiple rooms, let us go ahead and add a page to list, join, and create new rooms. We will begin by adding a Jade view named rooms.jade with the following code: extends layout block append head script(type='text/javascript', src='/socket.io/socket.io.js') script(type='text/javascript', src='/javascripts/rooms.js') block content section#chatrooms div#new_room span Start a new Room input#new_room_name(type="text") input#new_room_btn(type="button", value="Start") div#allrooms div#header Or join a room div#rooms_list Making It More Fun! [ 78 ] This view has an input box to accept a new room name and a div tag to add the list of existing rooms. We are also adding scripts for socket.io.js and a new script file for our client-side code for listing rooms, namely rooms.js. Next, create the rooms. js script file with the following code: var chatInfra = io.connect('/chat_infra'); chatInfra.on("connect", function(){ chatInfra.emit("get_rooms", {}); chatInfra.on("rooms_list", function(rooms){ for(var room in rooms){ var roomDiv = ''; $('#rooms_list').append(roomDiv); } }); }); $(function(){ $('#new_room_btn').click(function(){ window.location = '/chatroom?room=' + $('#new_room_name').val(); }); }); In the preceding code, we are connecting based on the chat_infra namespace, requesting the chat rooms on it, and rendering them in the view. Let us take a quick look at an important step happening here: chatInfra.emit("get_rooms", {}); As shown in the preceding code, the first thing we do after connecting is emit an event to get_rooms. This will request the list of rooms from the server. Next, we set a listener to receive the list of rooms and render them: chatInfra.on("rooms_list", function(rooms){ In the handler, as shown in the following code block, we are looping over the map of rooms and number of users in them and appending it to the room list: for(var room in rooms){ var roomDiv = ''; $('#rooms_list').append(roomDiv); } Finally, we have the code to create a new room. To create a new room, all we need to do is redirect to the chat room, with the name for the new room in the URL parameters: $('#new_room_btn').click(function(){ window.location = '/chatroom?room=' + $('#new_room_name').val(); }); Next, we need to add a get_rooms handler on the server to return the list of the rooms. For this, we will add the handler on the chat_infra namespace in sockets.js: this.chatInfra.on("connection", function (socket) { //EXISTING CODE socket.on("get_rooms", function(){ var rooms = {}; for(var room in io.sockets.manager.rooms){ if(room.indexOf("/chat_infra/") == 0){ var roomName = room.replace("/chat_infra/", ""); rooms[roomName] = io.sockets.manager rooms[room].length; } } socket.emit("rooms_list", rooms); }); }); We can get the list of all the rooms using io.sockets.manager and then we can build the map expected by our client by looping over the list. In our case, we filter to get rooms only from chat_infra as they will also be created in chat_com, and we don't want duplicates. Once we have the map, we will emit it as rooms_list. Following this we will need to add the entry to our routes/index.js file, as shown here: exports.rooms = function(req, res){ res.render('rooms', { title: 'Express Chat' }); } Making It More Fun! [ 80 ] We also need to add the mapping in app.js to server rooms at /rooms: app.get('/rooms', routes.rooms); Finally, let us add some CSS styling for our new room's page in style.css: #chatrooms{ margin: 20px; } #new_room { font-size: 17px; } #new_room span{ padding-right: 15px; } #allrooms #header{ border-bottom: 1px solid; border-top: 1px solid; font-size: 17px; margin-bottom: 10px; margin-top: 16px; padding: 5px 0; } .room_div { border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; padding-top: 12px; } .room_name { display: inline-block; font-weight: bold; width: 165px; } .room_div a{ position: absolute; right: 40px; } Chapter 4 [ 81 ] Go to /rooms and create a few rooms, and then when you open the room's page in a new browser, you'll see something similar to this: List of rooms on our chat server Sharing the session Now we have support for multiple rooms, but it is very clumsy to enter a nickname every time we enter a room. Let us modify our system to accept the nickname once when entering the system and use it in all the rooms. For this, let us start by modifying the landing page to add an input box to accept a nickname and add a JavaScript file to add the logic: extends layout block append head script(type='text/javascript', src='/javascripts/landing.js') block content section#welcome div Welcome span input#nickname(type="text", placeholder="Enter a nickname") a#startchat(class="btn") Login Making It More Fun! [ 82 ] Here, in the preceding code, we are adding a script entry to add landing.js and replacing the Start now button with the field to enter a name and a Login button. Next, let us take a look at landing.js: $(function(){ $('#startchat').click(function(){ document.cookie = "nickname=" + $('#nickname').val() + ";; path=/"; window.location = "/rooms"; }); }); In the previous code, we are attaching a click handler to the startchat button. In the handler, we are adding the nickname entered by the user to the cookie and redirecting the user to /rooms. We will be reading this cookie information while connecting the socket and then setting it on the socket. Before this cookie information can be accessed in the socket connection, we need to lay down some ground work to enable cookies in the Express.js application. For this, edit the app.js code by referring to the following code block: var express = require('express') , routes = require('./routes') , http = require('http') , path = require('path') , connect = require('connect'); var app = express(); var sessionStore = new connect.session.MemoryStore(); app.configure(function(){ //EXISTING CODE app.use(express.bodyParser()); app.use(express.cookieParser('somesuperspecialsecrethere')); app.use(express.session({ key: 'express.sid', store: sessionStore})); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public'))); }); //EXISTING CODE Chapter 4 [ 83 ] The first step is to add connect as a dependency in the package.json file and the require keyword in app.js. The connect keyword is used to create a session store; in this case, an in-memory session store: var sessionStore = new connect.session.MemoryStore(); We also enable the cookieParser middleware and the session module in the express application. Express' cookieParser middleware will take a secret parameter, which will be used to encrypt the cookies. The express' session module is initialized along with passing it the key (express.sid is the key for a session) and a store where the session should be maintained. In the following code, we are passing it an in-memory store, which we created in the previous step: app.use(express.bodyParser()); app.use(express.cookieParser('somesuperspecialsecrethere')); app.use(express.session({ key: 'express.sid', store: sessionStore})); app.use(express.methodOverride()); app.use(app.router); One important point to note about the previous code is the order of adding these two middleware components. These should be added after adding the bodyParser middleware and before adding the router middleware. If you open the browser and browse to the landing page now, you can see the cookie with the express.sid key in the browser's debugging tools under the Cookies tab. If you enter a name and click the Enter button, you will again see a new cookie, named after your nickname, being set: var io = require('socket.io'); exports.initialize = function (server) { io = io.listen(server); io.set('authorization', function (data, accept) { if (data.headers.cookie) { data.cookie = require('cookie').parse(data.headers.cookie); data.sessionID = data.cookie['express.sid'].split('.')[0]; data.nickname = data.cookie['nickname']; } else { return accept('No cookie transmitted.', false); } accept(null, true); }); var self = this; Making It More Fun! [ 84 ] this.chatInfra = io.of("/chat_infra"); this.chatInfra.on("connection", function (socket) { socket.on("join_room", function (room) { var nickname = socket.handshake.nickname; socket.set('nickname', nickname, function () { socket.emit('name_set', {'name': socket.handshake.nickname}); socket.send(JSON.stringify({type:'serverMessage', message:'Welcome to the most interesting ' + 'chat room on earth!'})); socket.join(room.name); var comSocket = self.chatCom.sockets[socket.id]; comSocket.join(room.name); comSocket.room = room.name; socket.in(room.name).broadcast.emit('user_entered', {'name':nickname}); }); }); //EXISTING CODE } The first change in the preceding code block introduces us to a new feature in socket. io; this change is shown in the following highlighted code block: io.set('authorization', function (data, accept) { if (data.headers.cookie) { data.cookie = require('cookie').parse(data.headers.cookie); data.sessionID = data.cookie['express.sid'].split('.')[0]; data.nickname = data.cookie['nickname']; } else { return accept('No cookie transmitted.', false); } accept(null, true); }); In this code snippet, we are setting an authorization method for the socket. This method will get two parameters, the data that contains all the HTTP request information and the accept method callback. The authorization method is called when a socket.io connection is requested but before it is established. We can use this method for actually performing an authorization, but in our case we will just use it to get the nickname from the cookies, as this is the only socket.io method that will have the HTTP data available with it. Chapter 4 [ 85 ] We are reading the cookie headers from the HTTP data and are parsing it using the cookie module's parse method. From the cookie, we are extracting the sessionID value and the nickname and setting it to the data object. This object is available on the socket as the handshake property. Finally, we will call the accept callback, which accepts two parameters, first a message and another a Boolean variable, indicating whether the authorization was successful or not. We will remove the set_name handler, as this handler need not be called because we already have the name with us. We will move the logic from the set_name handler to the join_room handler: socket.on("join_room", function (room) { var nickname = socket.handshake.nickname; socket.set('nickname', nickname, function () { socket.emit('name_set', {'name': nickname}); socket.send(JSON.stringify({type:'serverMessage', message:'Welcome to the most interesting ' + 'chat room on earth!'})); socket.join(room.name); var comSocket = self.chatCom.sockets[socket.id]; comSocket.join(room.name); comSocket.room = room.name; socket.in(room.name).broadcast.emit('user_entered', {'name':nickname}); }); }); In the join_room handler, we will fetch the nickname from the socket.handshake map and set it as a property on the socket. On setting the nickname property, we will still trigger the name_set event so as to keep the changes on the client to a minimum: var chatInfra = io.connect('/chat_infra'), chatCom = io.connect('/chat_com'); var roomName = decodeURI((RegExp("room" + '=' + '(.+?)(&|$)'). exec(location.search) || [, null])[1]); if (roomName) { chatInfra.emit('join_room', {'name':roomName}); chatInfra.on('name_set', function (data) { //EXISTING CODE }); } Making It More Fun! [ 86 ] As the join_room handler is the initializer for the room on the server, we will take it out of the name_set handler and directly call it during the page load. The rest of the code remains as is. To try this code, you will have to open two different browsers or browsers in different incognito sessions as the cookies/sessions will be shared for the same browser. Summary In this chapter, we saw how to set data for a session, how to work with namespaces and rooms, and how to integrate with the express sessions. Here we have completed a good and working chat system. It will be a nice exercise for you to build more functionality in this, based on the concepts we learned here. Some interesting features to build can be user exist alert, user list for the rooms, private chats, and so on. In the next chapter, we will take a look at the socket.io protocol and understand its workings. The Socket.IO Protocol Socket.io provides a very simple API that is easy to use but exposes a lot of functionality. Moreover, this functionality works uniformly across browsers and the various transport mechanisms provided by socket.io. To achieve this, a socket.io client and server do a lot of work in the background. In this chapter, we will examine and try to understand the communication in socket.io as well as some socket.io internals. Why do we need another protocol? The first question to people familiar with WebSocket is, why do we need another protocol when we already have WebSocket? The answer is twofold; socket.io works in a uniform manner across browsers (dating back to Internet Explorer 6), and socket.io provides a much richer API. The WebSocket specification is still under development and is not supported on many of the browsers that are in use. In fact, any version of Internet Explorer prior to IE10 doesn't have support for WebSocket. There are still many people out there using old browsers that don't support WebSocket. Another problem for WebSocket is firewalls and proxies. Most of the firewalls block any communication (apart from standard HTTP 1.0/1.1), and may not allow a WebSocket connection to be established. The same applies to most proxy servers. So, if we decide to use just the WebSocket protocol, we have to understand that there will be many people who may not be able to use our application. The Socket.IO Protocol [ 88 ] Contrary to this, when we build our application using socket.io, the people who can use WebSocket will continue using it, but those who can't will fall back on the next best available transport mechanism and then the next and so on, until they find one that works in the browser, even through the firewalls and proxies, all the way down to iframes (which is rarely used). The default order is as follows: • WebSocket • FlashSocket • XHR long polling • XHR multipart streaming • XHR polling • JSONP polling • iframe It's also worth noting that using JSONP polling, socket.io provides support for cross- domain communication without the need for any special configuration on the server or any special code on the client. Now, let us take a look at the differences in the API. For this, we will see only the JavaScript client-side API, as any server will have its own implementation and API depending on the programming language used. The WebSocket API Let us begin by taking a quick look at a code snippet showing the skeleton of a WebSocket client: Chapter 5 [ 89 ] The first step, as can be seen in the previous code snippet, is to create a new instance of WebSocket; in this, we have to pass the URI for the WebSocket server. This URI, like any other, has a part that specifies the protocol, which in this case can be either ws (unsecured) or wss (secured); the server address (the server's IP address or valid domain name); and finally, the port. Ideally, we also need to check if WebSocket is supported on the browser that the user has, but I have skipped that part to focus on the API. Following the creation of the WebSocket object, we can attach event handlers to it. There are three events exposed by WebSocket, with their corresponding event handlers: • open: The onopen event handler • message: The onmessage event handler • close: The onclose event handler As is evident by their names, these handlers will be called on the opening of a socket connection, when there is a new message on the socket, and on closing the socket connection, respectively. For every event, the client receives the event data. In case the event is a message, it contains that message along with other data. The WebSocket client doesn't try to interpret the message or its type, that is to say, it treats all messages as plain text and it is left to the application to interpret and understand it. Also, there is no mention of the namespacing of messages or the multiplexing of socket connections. If you see the onopen handler, you will notice the send method, which is used by the client to send messages. Again, it can send only plain text, so you have to take care of serialization and deserialization. Finally, we have the close method, which, as the name suggests, can be used to close the socket connection from the client. The Socket.IO API Let us see the same code using socket.io: The above code snippet looks similar to the one with WebSockets and, not surprisingly, does the same work as the previous code. However, there are some minor changes: instead of using onopen, onmessage, and onclose, we use socket. io's on method to attach the handlers. The advantage is that when we use socket.io's custom events functionality, the API to handle the event remains the same. As we have already seen, you can emit a new event using the following line of code: socket.emit("myevent", {"eventData": "..."}); And then receive it using the following: socket.on("myevent", function(event){...}); As you can see, in this case, we are passing a JSON object for the data; socket.io will take care of serializing and deserializing it for us. Moreover, socket.io provides support for namespacing of messages, multiplexing of connections, disconnection detection, reconnection, and an API to broadcast messages to all clients. Considering everything covered in this section, it is not difficult to conclude that socket.io will need its own protocol and mechanism to work. The Socket.IO socket The socket.io socket emulates a network socket over different transport mechanisms. Just as any other socket, it has various stages in its lifecycle, depending on the status of the connection. These are as follows: • connecting • connected • disconnecting • disconnected Chapter 5 [ 91 ] The socket is established when the client sends a connection request to the server and a handshake is initiated. Once the handshake is complete, a connection is opened using the transport negotiated during the handshake, and the state of the socket is set to connected. To check the liveliness of the socket depending on the server configuration, the server may require heartbeat messages to be sent from the client to the server in regular intervals. In the absence of such a message, or the failure of the underlying transport, the socket will be disconnected. In this case, the client will initiate a reconnect. If the connection is restored within the connection termination time or the timeout agreed at the time of the handshake, the buffered messages are sent across. In case the connection is not restored, the client will start a new connection request, beginning with a new handshake. Also, optionally, to ensure message delivery over the socket, we can make it compulsory for the socket to acknowledge the message delivery. The socket is terminated when the close method is called on either the client or the server. The Socket.IO connection The socket.io connection begins with the handshake. This makes the handshake a special part of the protocol. Apart from the handshake, all the other events and messages in the protocol are transferred over the socket. Socket.io is intended for use with web applications, and therefore it is assumed that these applications will always be able to use HTTP. It is because of this reasoning that the socket.io handshake takes place over HTTP. To initiate the connection and hence perform the handshake, the client performs a POST request on the handshake URI (built from the URI passed to the connect method). Let us take the same socket.io connection URI and try to understand its various parts. Let us say that the URI is as follows: http://myhost.com:8080/socket.io/1/ Let us break down and understand this URI. http is the protocol being used. We can set it to use https, using https in the client's connect method. myhost.com again comes from the connect method and is the name or IP address of the host you want to connect to. The default is localhost. The Socket.IO Protocol [ 92 ] 8080 is the port over which your server is listening. This is also passed to the connect method when we are invoking it. The default is 80. socket.io is the namespace that handles all the connect requests. 1 is the socket.io protocol version number. The server can respond to this in one of these three ways: • 200 OK – This will be the server's response when the handshake is successful. In addition to the status, the body of the response should be a colon-separated list of the session ID given to this connection, the heartbeat timeout, the connection closing timeout, and the list of supported transports separated by commas. A sample response body looks like this: 8749dke9387:20:10:websocket,flashsocket,xhr-polling • 401 Unauthorized – This will be the response from the server in case the authorization handler fails to authorize the client. As we saw in the previous chapter, this is the handler we attach to the authorize event on the server, and it uses the connection and cookie information to authorize the user. • 503 Service Unavailable – When the server has any other reason, including errors, to deny service to the client. If the handshake is successful, based on the transports provided by the server and the one supported by the client the socket.io client will start communicating with the server on a particular URI. This URI has the form [scheme]://[host]/ [namespace]/[version]/[transportId]/[sessionId]. • [scheme] is the protocol the client will be using to communicate. In the case of WebSockets, this is either ws or wss, while in the case of XHR, it is either http or https. • [host] is the server name or IP Address. • [namespace] is the socket.io namespace we want to send the message to. • [version] is the version of the socket.io protocol that we are using, currently 1. • [transportId] is the the name of the transport mechanism chosen for the communication. • [sessionId] is the session ID given to the client by the server during the handshake. In the case of bidirectional transport, such as WebSocket, the connection opened at this URI will be used to send and receive messages. Chapter 5 [ 93 ] For unidirectional transports such as XHR long polling, the client will perform a GET request on this URI, which the server will keep on hold till it has some data to send, while the client will perform a POST request on this URI whenever it has to send a message or an event to the server. Socket.IO messages Once the transport's connection is established, all the communication between the client and server happens using messaging over the socket. The messages need to be encoded in the format specified by socket.io. This format enables socket.io to determine the type of the message and the data sent in the message, and some metadata useful for operation. The message format is [type] : [id ('+')] : [endpoint] (: [data]). • type is a single digit integer, specifying what type of message it is. • id is the message ID, which is an incremental integer; it is used for ACKs. It is optional. • The + sign, if present, tells socket.io not to handle the ACKs, as the application intends to handle it. • endpoint is the socket endpoint that the message is intended to be delivered to. This is optional and is used when multiplexing the socket for namespacing. If omitted, the message will be sent to the default socket. • data is the associated data to be delivered to the socket. In the case of messages, it is treated as plain text, while in the case of events, it will be parsed as JSON. In the coming section, we will see what the types of messages are. Disconnect (0) When the type is zero (0), the message is a disconnect signal. This will tell socket.io to close the connection and the mentioned socket. If the endpoint is not specified, the message will be sent to the default socket, which will cause the whole socket to be closed and all the endpoints on that socket will be terminated. For example: • Message: 0 – The result is that the socket is closed and all the connections/endpoints are terminated. • Message: 0::/endpoint – The socket connection to /endpoint will be closed and no messages can be sent to or from that endpoint. Other endpoints will continue to operate. The Socket.IO Protocol [ 94 ] Connect (1) This message is only used for multiplexing, and is sent from the client to the server to open a new connection. Thus, this message must always have an endpoint. The first (default) socket connection is established by the handshake explained earlier. The endpoint may be followed by query parameters in a URL query format. If the connection is successful, the server will echo the same message, else the server can send an error message. For example: • Message: 1::/endpoint – Requests the server to open a multiplexed socket to the endpoint. • Message: 0::/endpoint?param=one – Requests the server to open a multiplexed socket to the endpoint, passing a parameter called param with the value one. Heartbeat (2) This is the heartbeat message. It must be sent from the client to the server within the timeout negotiated during the handshake. The server will reply with a heartbeat message too. In this case, we don't have an endpoint and nor is any other information required. This is because it serves the whole socket. For example: • Message: 2 – Sends a heartbeat message to the other end. Message (3) This is the message sent over the socket. In the API, this message will be sent when you are using socket.send, and will result in a message event on the receiving end. This message will carry data, treating it as plain text. For example: • Message: 3:1::Some message – This will send a message to the other end, where the message event handler will be triggered with the Some message message in the event data. • Message: 3:1:/endpoint:Some message – Again, the message will be sent to other end of the socket, but on the multiplexed endpoint. Chapter 5 [ 95 ] JSON message (4) This is similar to sending the message, but in this case the message has to be serialized using JSON, and it will be parsed at the other end before being sent to the handler. In version 0.6, this was done using the same API as send() for message, just passing a JSON message instead of a string message. But since this introduces a performance penalty over sending plain text from version 0.7 onwards, we have to use the json flag to send a JSON message; for example, socket.json.send. For example: • Message: 4:1::{"some":"content"} – Sends the JSON message to the other end of the socket. Event (5) The Event message is a special kind of JSON message that is used to send events over the socket. In events, the data payload is of the form {"name":"eventName", "args":{"some":"content"}}. Here, name is the name of the event and args are the parameters to be sent to the handler. The socket.emit call is used to send events in the applications. The following event names are reserved and cannot be used in applications: • message • connect • disconnect • open • close • error • retry • reconnect For example: • Message: 5:1::{"name": "myEvent", "args":{"some": "data"} – The result is that the event will be sent to the other end and the appropriate event handler will be invoked, passing the args to it. The Socket.IO Protocol [ 96 ] ACK (6) The acknowledgment (ACK) message will be sent when the message is received, with ACK request enabled; or, it can be sent out by the application. The data section in the ACK message can be the message ID for the message that is being acknowledged. If the message ID is followed by + and additional data, it is treated as an event packet. For example: • Message: 6:::1 – Sends an acknowledgment for the receipt of a message with ID 1. • Message: 6:::1+["A", "B"] – This will send an acknowledgment for the message along with the data. Error (7) This is sent by the server in case there's an error, such as failure during the processing of a connect request to an endpoint. The data section of this message will contain the error message and, optionally, advice, separated by the + sign. For example: • Message: 7:::Unauthorized – The result is that the error will be sent to the client. NOOP (8) This message implies no operation, and is used to close a poll after the polling times out. Summary In this chapter, we saw the communication mechanism for the socket.io server and client. Understanding the working and the message formats, helps us in debugging the issues we face during the development of socket.io applications. In the next chapter we will learn to deploy and scale socket.io applications in production. Also, we will get a few tips on how to minimize our troubles on the production server. Deploying and Scaling Running our application on the local server is fine, but making a web application really useful requires deploying it to a public server and making it accessible to others. To run our chat server application on Node.js, along with using protocols such as WebSocket, requires some special considerations. In this chapter, we will take a look at the following: • Things to consider while deploying our application • Recommendations for a production-ready deployment • Reason why scaling of socket.io applications is different than other web applications • How we can scale our chat application The production environment The first thing we should do before running an application on a production server is to set the environment to production. Every modern server or framework has separate development and production modes and so does node. In fact, in node you can set the environment to any name and then have different configurations for that name in your code. To set the environment our node server runs in, we set an environment variable NODE_ENV to the environment we want to run node in. So, to run node in the production environment, we use the following line: $ export NODE_ENV=production Deploying and Scaling [ 98 ] And then run your node application. In Chapter 2, Getting Started with Node.js, we saw how the first argument in app.configure is the environment variable we need to configure for: app.configure('development', function(){ app.use(express.errorHandler()); }); In this snippet we are setting the application to activate express.errorHandler in the development environment, which is the default environment. If we have set NODE_ENV to production, express.errorHandler will not be used. Running the application Running the application on the command line using node, like we have been doing until now, works during development; but on a production server where we connect remotely, it is generally not feasible or advisable to keep the console running. There are two ways to handle this, either we run node as a background process redirecting all console output to a file or we run it in a persistent console, to which we can reconnect, using screen or byobu. To run node as a background process, like any other process on Linux, we will use the & operator and to make sure that it keeps running even after we log out, we will use nohup: $ nohup npm start 2>&1 >> npmout.log & The preceding command will redirect the stdout and stderr commands to npmout. log and will put the npm process in the background. Another option is to run node on a long-lasting console, using utilities such as screen or byobu. To use this, start screen and then run your application, as shown here: $ screen $ npm start Now we can detach from this screen by using Ctrl +a and then hitting d. This will drop us to the default shell. We can then disconnect. When we connect back to the server, to see the server output, we can attach back to the screen by using the following command: $ screen -r Chapter 6 [ 99 ] Keeping it running Not only do we want the application to run when we log out, we want our application to keep running reliably. The production servers are not frequently restarted, and in general we will like to ensure that they come back up as soon as possible when there is a crash, a failure, or an error. For node, generally it means restarting the process as soon as it fails. There are many ways to keep the node server running. In this section we will see two of them: • Monit • Forever Here is how Monit is described on its website (http://mmonit.com/monit/): Monit is a free open source utility for managing and monitoring processes, programs, files, directories, and filesystems on a UNIX system. Monit conducts automatic maintenance and repair and can execute meaningful causal actions in error situations. Let us begin with installing Monit. On RPM-based or Yum-based systems such as RHEL, Fedora, or CentOS, you can install it using the yum command, as shown here: $ sudo yum install monit Or on a Debian- or apt-get-based system, you can install Monit using apt-get: $ apt-get install monit For other systems, you can check the installation instructions at the Monit website. Once Monit is installed, we can configure it to manage our node application. For this, we will create a configuration file (in our case we will call it awesome-chat) in /etc/ monit.d/ or /etc/monit/conf.d/, depending on your Monit installation: check host objdump with address 127.0.0.1 start program = "/bin/sh -c \ 'NODE_ENV=production \ node /opt/node_apps/awesome-chat/app.js 2>&1 \ >> /var/log/awesome-chat.log'" as uid nobody and gid nobody stop program = "/usr/bin/pkill -f \ 'node /opt/node_apps/awesome-chat/app.js'" if failed port 3000 protocol HTTP request / with timeout 10 seconds then restart Deploying and Scaling [ 100 ] In this file, you should notice the highlighted section. We are emphasizing the program or more importantly, the commands to start/stop our application and then finally configuring Monit to restart the application in case of a failure. This is detected by sending an HTTP request to fetch the page at port 3000. That is it; we can start our application with the following command: $ monit start awesome-chat And stop it with the following code: $ monit stop awesome-chat In case of a crash, Monit will take care of restarting the application. Monit can be used to run and watch any daemon service. It also has a web interface in case you want to check the status, which by default runs on port 2812. You can learn more about Monit on its website and in its manual online. Another, more node-specific way to keep our server up and running is Forever (https://github.com/nodejitsu/forever). Forever describes itself as: A simple CLI tool for ensuring that a given script runs continuously. And that's what is does. Given your node application script, Forever will start it and make sure it keeps running continuously. Since Forever itself is a node application, we will use npm to install it: $ sudo npm install forever -g Now, to start the application with Forever, it is just a matter of executing the app.js file with forever. Just run the following command: $ forever start app.js We can see the list of applications running forever with the following command: $ forever list 0 app.js [ 24597, 24596 ] To stop the application, use the forever stop command: $ forever stop 0 Visit Forever's github page for understanding more about Forever and its workings. Chapter 6 [ 101 ] There are several other tools on *nix systems to make node run as a daemon. Few of them are as follows: • Upstart (http://upstart.ubuntu.com/) • Supervisord (http://supervisord.org/) • Daemontools (http://cr.yp.to/daemontools.html) Scaling Now that we have made sure that our application will keep running and also will restart from failures, it's time we start looking at ways to handle millions of users flocking to our chat room. To begin with this, the first step is to put up a load- balancer proxy in front of our server. There are lots of options in this, we can use the Apache HTTP server, Nginx, and so on. All these servers work very well with balancing traditional HTTP traffic, but still have some time to catch up to work with WebSockets. So we will use a server that works on load-balancing TCP/IP itself. This is HAProxy (http://haproxy.1wt.eu/). This is how HAProxy is described in its official website: HAProxy is a free, very fast and reliable solution offering high availability, load balancing, and proxying for TCP and HTTP-based applications. It is particularly suited for web sites crawling under very high loads while needing persistence or Layer7 processing. Supporting tens of thousands of connections is clearly realistic with today's hardware. HAProxy works with frontends and backends. These are configured using the HAProxy configuration file present at /etc/haproxy/haproxy.cfg. The following file creates a frontend listener at port 80 and forwards it to a single server at 3000: global maxconn 4096 defaults environment http frontend all 0.0.0.0:80 default_backend www_Node.js backend www_Node.js environment http option forwardfor server Node.js 127.0.0.1:3000 weight 1 maxconn 10000 check Deploying and Scaling [ 102 ] In this file, we are defining a frontend listener at 0.0.0.0:80 with the default www_Node.js backend listening at 3000 on the same 127.0.0.1 server. But this configuration is not ready to handle WebSockets. To support and handle WebSockets, refer to the following code block: global maxconn 4096 defaults environment http frontend all 0.0.0.0:80 timeout client 86400000 default_backend www_Node.js acl is_websocket hdr(upgrade) -i websocket acl is_websocket hdr_beg(host) -i ws use_backend www_Node.js if is_websocket backend www_Node.js environment http option forwardfor timeout server 86400000 timeout connect 4000 server Node.js 127.0.0.1:3000 weight 1 maxconn 10000 check The first thing we did was to increase the client timeout value, so the client connection doesn't drop off if there is a long inactivity from the client. The acl lines of code instruct HAProxy to understand and check when we get a websocket request. By using the use_backend instruction, we configure HAProxy to use the www_Node. js backend to handle the websocket request. This is useful when you want to serve your static pages from any server, such as Apache HTTP, and want to use node exclusively to handle socket.io. Now we come to the part where we would like the request to be handled by more than one node server/process. To do this, first we will tell the proxy to round robin the requests by adding the following instruction to the backend: balance roundrobin Then we will add more server entries to the backend: server Node.js 127.0.0.1:4000 weight 1 maxconn 10000 check server Node.js 192.168.1.101:3000 weight 1 maxconn 10000 check Chapter 6 [ 103 ] Here we are adding two new node instances: one is a new process listening on port 4000 on the same server, while the other one is running on another server, which is accessible to the load-balancer at 192.168.1.101 on port 3000. We are done configuring the servers and the incoming requests will now be routed between the three node instances that we have configured. The node cluster Node now comes with its own completely rewritten cluster module. Cluster allows node to start multiple processes behind the cluster frontend and monitors and manages them. We will take a quick look at how to make an application cluster with this module, but note that this is only for creating multiple processes and we must still set up a tool to monitor the cluster master and also a proxy to forward requests to the node server. Let us see how we can use the cluster module. The best part about the cluster module is you don't need to actually change your application. Cluster will run a master instance, and we can start multiple instances of our application and they will all listen to a shared port. Here is the script that we can use for clustering the app.js file: var cluster = require('cluster'); if (cluster.isMaster) { var noOfWorkers = process.env.NODE_WORKERS || require('os').cpus().length; while(workers.length < noOfWorkers) { cluster.fork(); } } else { require('./app.js'); } So, what's happening here? The first thing we do is use require on the cluster module. In the next line, we are checking whether the instance that is started is the master process or the worker. If it is the master process, we check if the NODE_WORKERS environment variable is set, else we get the number of processors available on the system our server is running on. To set the NODE_WORKERS environment variable, you can run the following: $ export NODE_WORKERS=2 The previous command will tell the cluster to start two nodes. Deploying and Scaling [ 104 ] Now, in the loop, we call fork on the cluster. This calls child_process.fork so that the master and the started workers can communicate via IPC. When the cluster process is run from fork, cluster.isMaster is false and so our app.js script is in the current worker process. In our application, when we call server.listen(3000), the worker serializes this and sends over the request to the server, the server checks if it already is listening on that port, and returns the handle for the listener, if it is present. Else, the server starts listening on the port and passes on the handle to the newly created listener. Since all our workers request to listen on port 3000, the server will start listening on the port when the first worker starts and will pass on the same handler to all the workers. When a request comes in, it will be handled by any worker that can take it up and process it. Since our monitoring tool (Monit or Forever, or others) will now be monitoring only the master process, it becomes the master's responsibility to monitor the workers. This means that the cluster should restart any worker that happens to die. We will do this, by adding the following event handler in the master process: cluster.on('exit', function (worker, code, signal){ var exitCode = worker.process.exitCode; console.log('worker ' + worker.process.pid + ' died ('+exitCode+'). restarting...'); cluster.workers[worker.id].delete(); cluster.fork(); }); Monitoring of the process is done by listening to the exit event on the socket. This is the event that will be triggered when any worker dies. The event handler will get the worker, its exit code, and the signal that caused the process to be killed. In the handler, we log the death and we start a new worker process using cluster.fork(). Now we can start the new clustered application; we'll run cluster.js instead of app.js. So change the start script in package.json to run cluster.js: "scripts": { "start": "node cluster", } And then run the application with npm. npm start Chapter 6 [ 105 ] This will start the application and everything will look just as it was. But when you start using it, you'll notice that there are errors while trying to connect to a room, or while sending messages. These errors are because we are using an in-memory store for our Express.js sessions and socket.io uses an in-memory store to store and transfer all the messages. Scaling up the application In the previous section we saw how we can cluster a Node.js app and how it remains restricted due to our application mechanisms. In its current state, the application uses an in-memory store to keep the session data. This store is local to the Node. js instance and so won't be accessible in any another clustered instance. Also, the data will be lost in a Node.js instance restart. So, what we need is a way to store the session in a persistent store. Also, we want to configure socket.io such that all its instances use a shared pub-sub and data store. The Connect framework has an extension mechanism so a new store can be plugged in, and there is one store that is persistent as well as excels at pub-sub. It is the Redis Session Store. Redis (http://redis.io/) is a high performance, distributed, open source key- value store that can also be used as a queue. We will use Redis and corresponding Redis stores to provide a reliable, distributed, and shared store and pub-sub queue. Please check out the instructions to install the Redis server on your operating system and start it up. Let's make a few changes to our chat application, beginning with package.json: "connect-redis":"*", "redis":"*" This will add support for the Connect/Express.js Redis store and the Redis connection client. Let's first get Express.js to use Redis; to do so, edit app.js by referring to the following code snippet: var express = require('express') , routes = require('./routes') , http = require('http') , path = require('path') , connect = require('connect') , RedisStore = require('connect-redis')(express); var app = express(); var sessionStore = new RedisStore(); //Existing Code Deploying and Scaling [ 106 ] So the two changes we make here are pulling in the Redis session store and then we can replace the session store to be an instance of RedisStore. That's all that is needed to get Express running using the Redis store. The next thing we need to do is get socket.io using Redis. So, let us edit socket.js: var io = require('socket.io') , redis = require('redis') , RedisStore = require('socket.io/lib/stores/redis') , pub = redis.createClient() , sub = redis.createClient() , client = redis.createClient(); exports.initialize = function (server) { io = io.listen(server); io.set('store', new RedisStore({ redisPub : pub , redisSub : sub , redisClient : client })); //Existing Code } The first thing in the preceding code snippet that we are doing is require ('redis'), which provides the client and redisStore from socket.io, which provides redis backed for socket.io. Then we create three different Redis clients to use for pub-sub and the data store: io.set('store', new RedisStore({ redisPub : pub , redisSub : sub , redisClient : client })); In the previous code snippet, we configure socket.io to use Redis for the queue and data store. And we are ready to go! Now run the application again using the following command: npm start Chapter 6 [ 107 ] Tips for node in production Here are some tips to help us execute node in production: 1. Run the server in the production environment. 2. Never expose the node application directly on the Internet; always use a proxy. Servers such as Apache HTTP, Nginx, and HAProxy have been hardened and made robust over the years in production to make them secure against various kinds of attacks, especially DOS and DDOS. Node is new; it may become stable over time but today it is not recommended to be put directly on the front. 3. Never run node as root. Well, that is the advice for any application server, and it applies to node too. If we run node as root, there are chances of hackers gaining root access or running some harmful code as root. So, never ever run it as root! 4. Always run more than one node process. Node is a single-threaded, single- process application server. An error in the application can bring the server down. So, always have more than one process for reliability. Also, thinking in terms of 1+ processes keeps us ready for scaling out when the need comes. 5. Always use a monitor. Monit, Forever, Upstart pick one you like, but always use it. Better safe than sorry. 6. Never use MemoryStore in production; MemoryStore is for the development environment; I recommend using RedisStore even in development. 7. Log all errors. Everything runs fine until it doesn't! And when something goes wrong, logs are your best friend. Try to catch exceptions as close to the cause as possible and log all the relevant information in the context. Don't just log some error message, log all the relevant objects. 8. Never block unless there is no alternative. Node runs on an event loop, and blocking for one request will cause unwanted overheads and degrade performance for all requests. 9. Always keep your server, node, and all dependency modules up-to-date. Deploying and Scaling [ 108 ] Summary In this section, we saw the work involved in putting our application to production. We must remember that these are not the only ways to do it. For every task we did, there are many other ways of doing them, and there is no one solution that fits all scenarios. But now that we know what is expected out of a production environment, we can research the options and choose one according to our requirements. Socket.IO Quick Reference In this appendix we will take a look at the APIs provided by socket.io. The intention is to have a cursory glance through all the APIs so we know if there is a function that can help us while we are working. Socket.io is under active development and the APIs themselves are subject to change. Although the documented methods may not change, there are always new functions and features being added to socket.io. So always check with the socket.io website and wiki for the availability of a function that does what you want. The server As we already know by now, socket.io provides libraries to be used both in the server and the client. Let's first see the APIs provided for the server. Instantiating socket The socket.io module is instantiated, just like any other node module, by using require to import the module: var io = require('socket.io'); Starting Socket.IO The socket.io server component is started by using the listen method, which attaches the socket.io to the node HTTP server: var sio = io.listen(...
还剩139页未读
继续阅读
关键词
相关pdf
- Socket.io Real-time Web Application Developme
- Socket.IO Real-time Web Application Development
- SignalR Real-time Application Development
- real-time rendering.3rd
- Real Time Data Analysis
- Real-Time Big Data Analytics
- Big Data in Real-Time
- Real-Time Digital Signal Processing
- Storm Real-time Processing Cookbook
- Oracle Real Application Clusters (RAC)