Captainlonate
Captainlonate

Reputation: 5008

Hosting a nodejs server on dotcloud

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

Upvotes: 3

Views: 848

Answers (3)

indexzero
indexzero

Reputation: 1044

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

Upvotes: -1

Captainlonate
Captainlonate

Reputation: 5008

<<< 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.

Upvotes: 1

Ken Cochrane
Ken Cochrane

Reputation: 77395

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

Upvotes: 2

Related Questions