WebFund 2015W: Assignment 3
Answer the following questions about tinywebserver.js. There are 10 points in 7 questions. There is also 1 point for a bonus question. This assignment is due by 10 AM on Monday, February 2, 2015.
Please submit your answers as a single text or PDF file called "<username>-comp2406-assign3.txt" or "<username>-comp2406-assign3.pdf", where username is your MyCarletonOne username. The first four lines of this file should be "COMP 2406 Assignment 3", your name, student number, and the date. You may wish to format answers.txt in Markdown to improve its appearance. If you do so, you may convert your text file to PDF using pandoc.
No other formats will be accepted. Submitting in another format will likely result in your assignment not being graded and you receiving no marks for this assignment. In particular do not submit an MS Word or OpenOffice file as your answers document!
Questions
- [4] Explain how and why the behavior of tinywebserver changes when you delete the following lines:
- Line 50: 'html': 'text/html',
- Line 64: docroot: '.'
- Line 95: return response.end();
- Line 126: requestpath += options.index;
- [1] Who calls exists_callback() defined on lines 115-121? When is this call made?
- [1] What does the path.join() call do on line 139? Why call this method rather than implenting this functionality inline?
- [1] When serve_file() returns, what work has it accomplished? Specifically what data (if any) has been returned to the requesting web client? Why?
- [1] What is the purpose of the code on lines 150-156 (the else clause of the "err != null" if test)?
- [1] How can you access the incoming HTTP request headers in tinywebserver?
- [1] Which part of tinywebserver would you change to add a new HTTP response header?
- [BONUS 1] What do you find most confusing about the material covered so far in COMP 2406? Note you do NOT get a bonus mark for saying "nothing"!
Solutions
-
- Line 50: 'html': 'text/html',
- Line 64: docroot: '.'
- Line 95: return response.end();
- Line 126: requestpath += options.index;
- The fs.exists() method calls exists_callback() after having determined whether or not the file (given in requestpath) does exist.
- path_join() prepends the path in request.url with options.docroot. This could have been done using the + for string concatenation, but then the code would only work on UNIX-like and Mac systems (which use forward slashes to separate directories in file paths). The path_join() call converts /'s to \'s on Windows systems. (See http://shapeshed.com/writing-cross-platform-node/)
- When serve_file() has returned it has done nothing except call fs.readFile(), a function that returns immediately. fs.readFile() doesn't return anything so the return value of serve_file() is undefined. Data is only returned to the client when the callback given to fs.readFile() is called (and it calls respond()).
Code
// tinywebserver.js
//
// A modification of Rod Waldhoff's tiny node.js webserver
// original written in coffeescript
// simplified and made more native-ish by Anil Somayaji
// March 19, 2014
//
// original headers of coffeescript version:
//
// A simple static-file web server implemented as a stand-alone
// Node.js/CoffeeScript app.
//---------------------------------------------------------------------
// For more information, see:
// <https://github.com/rodw/tiny-node.js-webserver>
//---------------------------------------------------------------------
// This program is distributed under the "MIT License".
// (See <http://www.opensource.org/licenses/mit-license.php>.)
//---------------------------------------------------------------------
// Copyright (c) 2012 Rodney Waldhoff
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//---------------------------------------------------------------------
var path = require('path');
var http = require('http');
var fs = require('fs');
var MIME_TYPES = {
'css': 'text/css',
'gif': 'image/gif',
'htm': 'text/html',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'png': 'image/png',
'txt': 'text/text'
};
var options = {
host: 'localhost',
port: 8080,
index: 'index.html',
docroot: '.'
};
var get_mime = function(filename) {
var ext, type;
for (ext in MIME_TYPES) {
type = MIME_TYPES[ext];
if (filename.indexOf(ext, filename.length - ext.length) !== -1) {
return type;
}
}
return null;
};
var respond = function(request, response, status, content, content_type) {
if (!status) {
status = 200;
}
if (!content_type) {
content_type = 'text/plain';
}
console.log("" + status + "\t" +
request.method + "\t" + request.url);
response.writeHead(status, {
"Content-Type": content_type
});
if (content) {
response.write(content);
}
return response.end();
};
var serve_file = function(request, response, requestpath) {
return fs.readFile(requestpath, function(error, content) {
if (error != null) {
console.error("ERROR: Encountered error while processing " +
request.method + " of \"" + request.url +
"\".", error);
return respond(request, response, 500);
} else {
return respond(request, response, 200,
content, get_mime(requestpath));
}
});
};
var return_index = function(request, response, requestpath) {
var exists_callback = function(file_exists) {
if (file_exists) {
return serve_file(request, response, requestpath);
} else {
return respond(request, response, 404);
}
}
if (requestpath.substr(-1) !== '/') {
requestpath += "/";
}
requestpath += options.index;
return fs.exists(requestpath, exists_callback);
}
var request_handler = function(request, response) {
var requestpath;
if (request.url.match(/((\.|%2E|%2e)(\.|%2E|%2e))|(~|%7E|%7e)/) != null) {
console.warn("WARNING: " + request.method +
" of \"" + request.url +
"\" rejected as insecure.");
return respond(request, response, 403);
} else {
requestpath = path.normalize(path.join(options.docroot, request.url));
return fs.exists(requestpath, function(file_exists) {
if (file_exists) {
return fs.stat(requestpath, function(err, stat) {
if (err != null) {
console.error("ERROR: Encountered error calling" +
"fs.stat on \"" + requestpath +
"\" while processing " +
request.method + " of \"" +
request.url + "\".", err);
return respond(request, response, 500);
} else {
if ((stat != null) && stat.isDirectory()) {
return return_index(request, response, requestpath);
} else {
return serve_file(request, response, requestpath);
}
}
});
} else {
return respond(request, response, 404);
}
});
}
};
var server = http.createServer(request_handler);
server.listen(options.port, options.host, function() {
return console.log("Server listening at http://" +
options.host + ":" + options.port + "/");
});