3

I am trying to host a nodejs app on the hosting service "dotcloud". My nodejs uses the package "websocket" to handle communications. ie. npm install websocket

My app works great when it's running on localhost on my laptop. But when I deploy the app on dotcloud it does not work correctly.

Here is what is going on: You point your browser to the url on dotcloud: pirate-captainlonate.dotcloud.com

Then, express handles the GET request with express.get('/'.......){} express serves the .html page to the client as you would expect. The .html file in turn tries to establish the websocket connection with the server. Again I can get this to work just fine on my local machine. However, no connection is being established. Specifically, the dotcloud is definitely serving me the .html file, but the .html file is not establishing the websocket connection with the server. But connection.onerror isn't being called either. It's weird.

Here is some code to help you understand what I'm doing:

Client Side:

this.connection = new WebSocket('ws://pirate-captainlonate.dotcloud.com:1337'); 

this.connection.onerror = function (error) {
        console.log("ERROR with the connection *sadface*");
    };

**** Note that I note the onerror function here to show that I do indeed have it set up, but it's not being called. It would seem that no error is being thrown.

Server Side:

var webSocketServer = require('websocket').server; // websocket
var server = require('http').createServer();
var expr = require("express"); // load the express module
var xpress = expr(); // xpress now holds the server object

// Helps Node serve the game.html page upon a get request
xpress.configure(function() {
    xpress.use(expr.static(__dirname + "/public"));
     xpress.set("view options", {layout: false});
});

// All requests to root serve the game.html page
xpress.get('/', function(req, res) {
    res.sendfile(__dirname + '/public/game.html');
});

// What ports to listen on
var webSocketsServerPort = 1337;
xpress.listen(8080);
server.listen(webSocketsServerPort, function() {
    console.log((new Date()) + " Server is listening on port " + webSocketsServerPort);
});

// WebSocket Server
var wsServer = new webSocketServer({
    httpServer: server
});

That should be enough code to show you guys how it's working. Now one of you will probably ask, what is ">> dotcloud logs" showing?

[www.0] ==> /var/log/supervisor/app.log <==
[www.0] Sat Feb 16 2013 02:57:59 GMT+0000 (UTC) Server is listening on port 1337
[www.0] ==> /var/log/supervisor/supervisord.log <==
[www.0] 2013-02-16 02:57:57,946 WARN Included extra file "/home/dotcloud/current/supervisord.conf" during parsing
[www.0] 2013-02-16 02:57:58,033 INFO RPC interface 'supervisor' initialized
[www.0] 2013-02-16 02:57:58,033 WARN cElementTree not installed, using slower XML parser for XML-RPC
[www.0] 2013-02-16 02:57:58,033 CRIT Server 'unix_http_server' running without any HTTP authentication checking
[www.0] 2013-02-16 02:57:58,038 INFO daemonizing the supervisord process
[www.0] 2013-02-16 02:57:58,039 INFO supervisord started with pid 140
[www.0] 2013-02-16 02:57:59,048 INFO spawned: 'app' with pid 154
[www.0] 2013-02-16 02:58:00,290 INFO success: app entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
[db.0] ==> /var/log/mongodb/mongodb.log <==
[db.0] Sat Feb 16 01:45:02 [conn4] end connection 127.0.0.1:51326 (0 connections now open)

Alright, well I'd really like to get this working. I've been at this forever. Let me know if there is anything else you guys need to help me answer my question.

Thanks,

--Nathan

Addendum: This is how server is sending the html file.

xpress.get('/', function(req, res) {
    res.sendfile(__dirname + '/public/game.html');
});
Captainlonate
  • 4,878
  • 4
  • 25
  • 35
  • Great question, lots of information, made it easy to find your problem. Basically you need a second http port. I explain how to do that in my answer below. – Ken Cochrane Feb 16 '13 at 12:24

3 Answers3

2

It looks like you are trying to use 2 http ports for your service, and dotCloud supports only 1 out of the box, so you need to let them know you want to have another one by adding a small snippet in your dotcloud.yml

Here is an example dotcloud.yml that is asking for a second tcp port called server

app:
    type: nodejs
    ports:
       server: tcp
    config:
       node_version: v0.8.x

Once you add this and push, your server will be given a 2nd TCP port you can use for your server, you just need to find out what port that is by getting the value from your environment file.

Here is a snippet that will get your port from the ENV, it will default to 4242 when not there so you can still run locally.

var webSocketsServerPort = process.env['PORT_SERVER'] || 4242;

If you are wondering how I got the ENV variable name, it is simple. it will be PORT_ and then the uppercase string of the name from the dotcloud.yml. Since I used server above it became PORT_SERVER, if I used node about it would have been PORT_NODE, so put what you want, but make sure those values match.

Client:

To find out what port you need to connect to on your client, You would need to go back to your environment variables again. This time you are looking for a variable that looks like this DOTCLOUD_APP_SERVER_PORT. Important: Your variable name might be different

How did I get that envirornment variable name?

The name of the variable looks like this DOTCLOUD_{{app_name}}_{{port_name}}_PORT all uppercase. Replace the {{variable}}'s with the info below.

{{app_name}} = the name of your app from your dotcloud.yml, in the example above it is app {{port_name}} = port name, server in dotcloud.yml example above.

To find this you can get it from your apps environment.json, environment.yml files, the shell ENV variables, or log into the dotCloud dashboard, click on your app, and then the Environment tab to see a list of your application variables.

If you make those three changes, your problems should go away.

If you need more code examples, please check out this github repo that doing something similar as what you are trying to do.

https://github.com/3on/node-tcp-on-dotcloud

Ken Cochrane
  • 75,357
  • 9
  • 52
  • 60
  • Ok, but if I do that, then what port does my client try to establish a connection on? I tried no port, 4242, 1337, then i tried to make some mumbo jumbo and the results were surprising. It didn't matter what i put in the WebSocket() constructor call, I got the same result every time: no error, no connection established.. – Captainlonate Feb 17 '13 at 05:43
  • @Captainlonate I updated the answer to include how to find out which port you can connect to from your client. – Ken Cochrane Feb 17 '13 at 13:44
  • Alright Ken, status update: I tried what you said, but I cannot use process.env[] within my client.js file. Regardless of what is in the brackets '[]', the function call "process.env" is not recognized. Chrome's console says: Uncaught ReferenceError: process is not defined . For clarity, this is what the line in my client.js file says: var thePort = process.env['DOTCLOUD_APP_SERVER_PORT']; Stick with me Ken, I desperately want this to work. – Captainlonate Feb 17 '13 at 17:53
  • Addendum: after I create the variable above, my intention was to use it like this: console.log('ws://pirate-captainlonate.dotcloud.com:' + thePort); – Captainlonate Feb 17 '13 at 17:55
  • Is there some possible way to "send" the port to along with the .html file so that the .html file can access the port via some variable. In my original post, I have edited the bottom section to include how my server sends the .html file to the client. – Captainlonate Feb 17 '13 at 18:00
  • @Captainlonate I'm not that familiar with express but since you are using express to serve that client page, if it supports templates then you can get the variable in express, which should have access to the variable, and then you can make your html page a template and pass the variable to the template and render the html before you send it back to the browser. – Ken Cochrane Feb 17 '13 at 20:33
  • Alright I'm sorta lost. I've been trying with ejs. I can pass data from the server to the template. Then the template can print out the data via console.log or some other method. For the life of me, I cannot figure out how to store that data in a variable. I've tried var variable = <% data %> and <%= data %> and <% return(data) %>. How do I store the data that I am rendering my template with, into a variable. – Captainlonate Feb 18 '13 at 01:08
  • if you use Gmail, would you be willing to help me figure this out over Gmail chat or just email in general? I would be willing to send you my client file and server file and have you help me look it over. If we could get the answer, then I could just post the final solution on here. – Captainlonate Feb 18 '13 at 01:09
  • Send an email to support@dotcloud.com and we will have our node.js experts help you. Make sure to reference this question so they have the history. – Ken Cochrane Feb 18 '13 at 12:05
  • Hi, to pass your variable to a the client side you have different ways to do so (cookie, printing JS snippet, data attribute etc...) https://gist.github.com/3on/4978893 – 3on Feb 18 '13 at 17:07
  • Thanks a lot Ken, you've been a trooper. I emailed them a couple days ago actually but they never responded. I finally solved the problem with your help and with some help from 3on. The complete solution is below. – Captainlonate Feb 18 '13 at 22:56
1

<<< Original Poster Here >>>

Alright, I got this to work on Dotcloud. I'm just going to post what you guys need to know. If you've been following this problem, the final solution is about to be posted. I want to thank Ken from dotcloud for getting me on the right path. Thanks to him I learned about the environments.yml, environments.json files. In addition, doing a

console.log(process.env);

on the server side was a HUUGE helper. KK here goes the solution:

First I want you to see how I declare my requires and variables:

var webSocketServer = require('websocket').server; // websocket
var server = require('http').createServer();
var expr = require("express"); // load the express module
var xpress = expr(); // xpress now holds the server object

Alright, now that you know what these things are, I need to tell you that I decided to use ejs to render a template. A problem I was facing is that I needed my client to be able to "know" on which port to connect to the server via WebSocket. Without a websocket connection already in place, how else was I going to give a variable like "port" to the client. Keep in mind the port might change, so I couldn't just hardcode a port like 50234 or something at the end of my ws:// url. The solution was to use "ejs".

ejs is a module ( i.e. "npm install ejs" ) I'm not really going to explain how to use it. But here is a website I used to learn: http://embeddedjs.com/

Here are some things you need to know: When the client points their browser to your dotcloud url, this is how you send them a file, in my case I changed my .html file to a .ejs file so that I could render it as a template.

xpress.get('/', function(req, res) {
    res.render('game', 
    {
        answer: superCoolPort
    });
});

'game' means that in whatever folder I told the server to look for templates, there should be a file named game.ejs. Notice how I am rendering the template called game.ejs with some data. In this case the data is a local variable in my server.js file called "superCoolPort". This is what that variable is:

var superCoolPort = process.env['DOTCLOUD_WWW_SERVER_PORT'];

Ok, now express ('xpress' in my case), needs to listen on port 8080.

xpress.listen(8080);

This is NOT the port that your WebSocket will try to connect on. This is the port on which your browser tries to connect to the page. But, Dotcloud does not allow you to host anything on port 80, so if you host it on 8080, they redirect it to 80 for you. This way, you do not need to type url:8080 in the browser.

Now let me explain how the http server turns into the wsServer. Basically you set up the http server and make it listen to a port. Then you mount this http server to a websocket server. See where I declare "server" at the top?

This is the port where the http server is going to listen on. Note that this means the websocket server will also listen on this port.

var webSocketsServerPort = process.env['PORT_SERVER'] || 4242;
server.listen(webSocketsServerPort, function() {
    console.log((new Date()) + "The http server is listening on port " + webSocketsServerPort);
});

// WebSocket Server
var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. WebSocket request is just
    // an enhanced HTTP request.
    httpServer: server
});

Before I move on to the client, I want you to know how I set up my express configurations.

xpress.configure(function() {
     // Sets the directory to look for templates
     xpress.set('views', __dirname + '/public');
     // This line tells Express that we are using the ejs engine to render templates
     xpress.set('view engine', 'ejs');
     xpress.use(expr.static(__dirname + "/public"));
     xpress.set("view options", {layout: false});
});

Everything above has been modifications to the server.js file.

Ok, next I'll talk about the template. I used to have a file called game.html. Well I wanted this to be a template that I could render with some data (the port number that the websocket needs to connect on). So first I changed the name of the file to game.ejs. Then, I made some modifications as follows:

<body onload="init()">

became

<body data-port="<%=answer%>" onload="init()">

See how onload="init()"? Well this means that init will not be called until the page loads. This is important because when we want to access the port, you cannot be guaranteed that it is available unless you are inside of "init()". I know this because I tried to access it before I defined init(), and it said the variable was null.

Now inside of init(), you can access the port number like this:

var port = $('body').data('port');

Now my client.js file can initialize a websocket connect like this:

this.connection = new WebSocket('ws://pirate-captainlonate.dotcloud.com:' + this.thePort);

where "this.thePort" is the same as "port" from above.

I want to make this solution as complete as I can so here is my dotcloud.yml file. It sits one directory above my server.js file:

www:
    type: nodejs
    approot: app
    ports:
        server: tcp
    processes:
        app: node app.js
    config:
        node_version: v0.8.x

db:
    type: mongodb

Here is my package.json file. It sits in the same directory as my server.js file ( which is actually called app.js in my case):

{
  "name": "app",
  "version": "0.0.0",
  "scripts": {
    "start" : "node app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies":{
      "express" : "",
      "mongodb" : "",
      "fs": "",
      "ejs": "",
      "ws": "",
      "websocket": ""
  },
  "repository": "",
  "author": "",
  "license": "BSD"
}

Finally, I honestly don't know if this is necessary or not, but here is my supervisord.conf file. It sits in the same directory as server.js.

[program:node]
command = node app.js
directory = /home/dotcloud/current

Well I think that is everything. I hope I didn't leave anything out. Ultimately though, these changes were what I needed to get my nodejs app using "websocket" to deploy and run on Dotcloud.

Captainlonate
  • 4,878
  • 4
  • 25
  • 35
-1

Looks like you're trying to access the WebSocket on port 1337, but you should be trying over port 80.

this.connection = new WebSocket('ws://pirate-captainlonate.dotcloud.com');

Most public platforms only reverse proxy to your application over port 80. Speaking of, have you tried running your application on Nodejitsu? http://nodejitsu.com

Preview
  • 35,317
  • 10
  • 92
  • 112
indexzero
  • 1,024
  • 9
  • 5
  • So here's the thing: if you look at http://docs.dotcloud.com/0.9/services/nodejs/ then you will see that dotcloud requires users to have their server listen on port 8080, since port 80 requires root and we don't have root. Sure enough, attempting this yields: "Error: listen EACCES". Now if express listens on 8080 AND http.createServer listens on 8080, then you get: "Error: listen EADDRINUSE" which you would expect since two things are occupying one port. It's kind of a pickle. Is nodejitsu better than dotcloud? – Captainlonate Feb 16 '13 at 04:55
  • And so, if the server listens on port 8080, and your websocketserver listens on a different port, everything should work out. It makes sense that this would work. But when I try it I get no errors at all. The page loads like normal, but the websocket connection isn't established... – Captainlonate Feb 16 '13 at 04:58
  • I'm not sure if that code would work on nodejitsu, correct me if I'm wrong, but according to this link: https://handbook.nodejitsu.com/a-quickstart/faq#faq-how-can-i-make-my-app-use-a-port-other-than-port-80 it says you can't listen to more then one port at a time per service, and the code above wants to listen to 2 different ports at once. Maybe you could use a proxy or something? – Ken Cochrane Feb 16 '13 at 12:33