Learning about node.js and socket.io and working through this tutorial by Daniel Nill. The server starts no problem. However, when I navigate to localhost:8001/socket.html, I get the default error message. So I changed the switch statement to be '/socket.html' as opposed to 'socket.html'. The page now loads with status code 200, but nothing is rendered to screen. The screen should say "This is our socket.html file". What gives?
The server side js code is
var http = require("http");
var url = require('url');
var fs = require('fs');
var server = http.createServer(function(request, response){
console.log('Connection');
var path = url.parse(request.url).pathname;
switch(path){
case '/':
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('hello world');
break;
case 'socket.html':
fs.readFile(__dirname + path, function(error, data){
if (error){
response.writeHead(404);
response.write("opps this doesn't exist - 404");
}
else{
response.writeHead(200, {"Content-Type": "text/html"});
response.write(data, "utf8");
}
});
break;
default:
response.writeHead(404);
response.write("opps this doesn't exist - 404");
break;
}
response.end();
});
server.listen(8001);
Socket.html located in the same directory as server.js contains this
<html>
<head></head>
<body>This is our socket.html file</body>
</html>
Ok I gave up on this and moved over to this example which works right out of the box!
Beginner here. As far as I can tell Daniel Nill wrote a bunch of code for a tutorial, and it never worked. As a result, he just added to the confusion for beginners--something he claimed he was trying to alleviate.
So I changed the switch statement to be '/socket.html' as opposed to 'socket.html'.
That was one obvious error--good catch.
The page now loads with status code 200, but nothing is rendered to
screen. The screen should say "This is our socket.html file". What
gives?
Or, like I'm seeing, if the socket.html file doesn't exist, instead of getting a 404 error I get a status code of 200 (OK) and an empty web page.
The reason the code in the tutorial doesn't work is because Daniel Nill thought he would be clever and not write response.end()
after every response.write()
. He thought he could just write one response.end()
at the end of all the code.
It appears to me that Daniel Nill misunderstood how nodejs works. To wit, nodejs does NOT execute a function and then wait around for the handler function passed as an argument to finish executing before executing the next lines of code. If nodejs actually did that, then we wouldn't need to put our code inside handler functions. Instead, nodejs adds a handler function to a list of handler functions that are to be executed at some time in the future.
Look at the fs.readFile() function in this code:
switch(path){
case '/':
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('hello world');
break;
case 'socket.html':
fs.readFile(__dirname + path, function(error, data){
if (error){
response.writeHead(404);
response.write("opps this doesn't exist - 404");
}
else{
response.writeHead(200, {"Content-Type": "text/html"});
response.write(data, "utf8");
}
});
break;
default:
response.writeHead(404);
response.write("opps this doesn't exist - 404");
break;
}
response.end();
The handler function for fs.readFile() is this part:
function(error, data){
if (error){
response.writeHead(404);
response.write("opps this doesn't exist - 404");
}
else{
response.writeHead(200, {"Content-Type": "text/html"});
response.write(data, "utf8");
}
});
When a browser requests /socket.html
, nodejs executes fs.readFile()
and then nodejs adds its handler function to the list of handler functions awaiting execution, then nodejs continues on. The next line of code that will execute is the response.end()
here:
default:
response.writeHead(404);
response.write("opps this doesn't exist - 404");
break;
}
response.end(); //<====HERE ******
It's apparent to me that before the handler function from fs.readFile() has a chance to execute, nodejs executes that response.end()
.
According to the docs for response.end()
:
response.end([data], [encoding])
This method signals to the server
that all of the response headers and body have been sent; that server
should consider this message complete. The method, response.end(),
MUST be called on each response.
I tested it, and if you don't do any response.write()'s and you just call response.end()
, nodejs will create an empty response with a status code of 200, for example:
switch(path) {
case '/':
//resp.writeHead(200, {'Content-Type': 'text/html'} );
//resp.write('<h3>Root page</h3>');
resp.end();
break;
I think the lesson to learn from Daniel Nill's mistake is that after you give nodejs a handler function, you have no control over where execution will take up after the handler function executes. In fact, code written after the end of a handler function can execute before the handler function executes. As a result, a handler function needs to do everything that needs to be done itself.
Here are the modifications necessary to make the example in the tutorial work correctly:
var http = require('http');
var url = require('url');
var fs = require('fs');
var server = http.createServer(function(requ, resp) {
//requ.url => everything after the host--including the query string
//url.parse(requ.url).pathname => the portion of requ.url before the query string
var path = url.parse(requ.url).pathname;
//The following is so that the automatic request that all browsers make
//for favicon.ico (which for some reason is not logged by any developer
//tools) will not display a 'connection' message:
if (path == '/favicon.ico') {
resp.writeHead(200, {'Content-Type': 'image/x-icon'} );
resp.end();
return; //Terminate execution of this function, skipping the code below.
}
//Router:
switch(path) {
case '/':
resp.writeHead(200, {'Content-Type': 'text/html'} );
resp.write('<h3>Root page</h3>');
resp.end();
break;
case '/socket.html':
fs.readFile(__dirname + path, function(error, data) {
if (error) {
console.log('file error');
resp.writeHead(404);
resp.write("oops, this doesn't exist - 404");
resp.end();
}
else {
console.log('no file error');
resp.writeHead(200, {'Content-Type': 'text/html'} );
resp.write(data, 'utf8');
resp.end();
}
});
break;
default:
resp.writeHead(404);
resp.write("oops, this doesn't exist - 404");
resp.end();
break;
}
console.log('Connection');
});
port = 8888;
console.log('Server listening on port ' + port);
server.listen(port);
To minimize the number of times you have to call response.end()
, you could do this:
var http = require('http');
var url = require('url');
var fs = require('fs');
var server = http.createServer(function(requ, resp) {
//console.log('request url: ' + requ.url);
//requ.url => everything after the host--including the query string
//url.parse(requ.url).pathname => the portion of requ.url before the query string
var path = url.parse(requ.url).pathname;
//The following is so that the automatic request that all browsers make
//for favicon.ico (which for some reason is not logged by any developer
//tools) will not cause a 'connection' message:
if (path == '/favicon.ico') {
resp.writeHead(200, {'Content-Type': 'image/x-icon'} );
resp.end();
return;
}
//Router:
switch(path) {
case '/':
resp.writeHead(200, {'Content-Type': 'text/html'} );
resp.write('<h3>Root page</h3>');
resp.end();
break;
case '/socket.html':
fs.readFile(__dirname + path, function(error, data) {
if (error) {
console.log('file error');
resp.writeHead(404);
resp.write("oops, this doesn't exist - 404");
//resp.end();
}
else {
console.log('no file error');
resp.writeHead(200, {'Content-Type': 'text/html'} );
resp.write(data, 'utf8');
//resp.end();
}
resp.end();
});
break;
default:
resp.writeHead(404);
resp.write("oops, this doesn't exist - 404");
resp.end();
break;
}
//resp.end();
console.log('Connection');
});
port = 8888;
console.log('Server listening on port ' + port);
server.listen(port);
But you can't refactor the response.end()
completely out of the handler function--like Daniel Nill did; and you can't put a response.end()
after the switch statement because the response.end()
will execute before the handler function passed to fs.readFile() has a chance to execute, which will cause an empty request with a status code of 200 to be sent to the browser.
Additionally, I was getting two "Connection" messages for a single request. My developer tools only showed one request being sent by my browser when I typed in a url such as:
http://localhost:8888/
...but all browsers send an additional request which retrieves /favicon.ico
. You can prove that is the case by writing something like:
var server = http.createServer(function(requ, resp) {
console.log('request url: ' + requ.url);
To solve the double request problem, I added the if statement:
if (path == '/favicon.ico') {...
...which is described here:
http://tinyurl.com/odhs5le
=====
In the next part of the tutorial, in order to see the command line output when using socket.io, you have to start the server using a command like this:
$ DEBUG=socket.io:* node server.js
See the nodejs document "Upgrading from 0.9", section Log differences
here:
http://socket.io/docs/migrating-from-0-9/
=====
To get the socket.io part of the code to work, I put the following in server.js
:
var http = require('http');
var url = require('url');
var fs = require('fs');
var io = require('socket.io'); //New code
var server = http.createServer(function(requ, resp) {
...
...
...
});
port = 8888;
console.log('Server listening on port ' + port);
server.listen(port);
//New code:
var websockets_listener = io.listen(server);
websockets_listener.sockets.on('connection', function(socket){
socket.emit('server message', {"message": "hello world"});
});
Then in socket.html
, I have this:
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<div>This is our socket.html file</div>
<div id="message"></div>
<script>
var socket = io.connect();
//More recent versions of socket.io allow you to simply write:
//var socket = io();
//which both creates the socket and by default connects to
//the same host that served this page.
//See: http://socket.io/get-started/chat/, section Integrating Socket.IO
socket.on('server message', function(data) {
document.getElementById('message').innerHTML = data.message;
});
</script>
</body>
</html>
You could do this:
socket.on('server message', function(data) {
console.log(data.message);
});
...but you have to remember that in nodejs the console.log() output goes to the server window, but when javascript executes on a web page, like with socket.html, the console.log() output goes to the web browser's console (display your web browser's development tools to see the console)--so don't look for the output in the server window.
===
In the next part of the tutorial, to stream just the time, eliminating the date, milliseconds, utc offset, etc., which just clutters everything up, you can do this in server.js
:
var websockets_listener = io.listen(server);
websockets_listener.sockets.on('connection', function(socket){
setInterval(function() {
var now = new Date();
socket.emit('time', {local_time: now.toLocaleTimeString()})
}, 1000);
});
socket.html
:
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<div>This is our socket.html file</div>
<div id="message"></div>
<script>
var socket = io.connect();
socket.on('time', function(data) {
document.getElementById('message').innerHTML = data.local_time;
});
</script>
</body>
</html>
===
In the next part of the tutorial, to stream data from the client to the server you can do this(note the corrections to the jquery, which is not up to snuff):
socket.html
:
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
</head>
<body>
<div>This is our socket.html file</div>
<div id="time"></div>
<textarea id="text"></textarea>
<script>
var socket = io.connect();
socket.on('time', function(data) {
$('#time').html(data.local_time);
});
//Because the html above has already been parsed by the time this
//js executes, there is no need for document.ready():
$('#text').on('keypress', function(event) {
var keycode = event.which;
socket.emit('client data', {letter: String.fromCharCode(keycode)} );
});
</script>
</body>
</html>
server.js
:
var http = require('http');
var url = require('url');
var fs = require('fs');
var io = require('socket.io');
var server = http.createServer(function(requ, resp) {
...
...
...
});
port = 8888;
console.log('Server listening on port ' + port);
server.listen(port);
var websockets_listener = io.listen(server);
//websockets_listener.set('log level', 1);
//Rather than writing the previous line(which doesn't work anymore)
//just stop the server and restart it using this command:
//$ node server.js
//...instead of:
//$ DEBUG=socket.io:* node server.js
websockets_listener.sockets.on('connection', function(socket){
setInterval(function() {
var now = new Date();
socket.emit('time', {local_time: now.toLocaleTimeString()})
}, 1000);
socket.on('client data', function(data) {
process.stdout.write(data.letter);
});
});