WebFund 2016W: Assignment 3
This assignment is not yet finalized (needs proofreading).
Please submit your answers by 2:30 PM on Tuesday, February 9th, 2016 via cuLearn.
The first lines of your assignment should be:
COMP 2406 2016W Assignment #3 <name> <student #>
Submit your answers as a text or PDF file only. MS Word .doc files, OpenOffice .odt, and other word processing files are not acceptable: convent to text or PDF!
If you used any outside resources or got help from any other students, please cite them with each answer or at the end of your assignment.
Questions
- [1] In simplegrep_async.js:14, what is the type of all of the symbols referenced in the function? Specfiically, what is the type of lines, rawContents, and split?
- [1] If I create a line 24 in simplegrep_async.js that says 'console.log("finished!");', when will finished! be printed relative to the other output of the program?
- [1] What is a one-line change to tinywebserver.js that would make every HTTP response have a 404 response code?
- [1] How you could return a 403 error (access denied) to all requests from iPhones in tinywebserver.js? Remember the User-Agent HTTP header!
- [6] How would the behavior of form-demo changed when you make the following changes?
- Delete form-demo/bin/www:7
- Change form-demo/bin/www:15 to be "var port=3000;".
- Change form-demo/app.js:13 to be "app.set('views', '.');"
- Delete form-demo/routes/index.js:24
- Delete form-demo/views/layout.jade:8
- Change the indentation of form-demo/views/index.jade:24 such that it is at the same indentation as form in line 8.
Code
Full source: simplegrep_async.js, tinywebserver.js, form-demo.zip
simplegrep_async.js
var fs = require('fs');
if (process.argv.length < 4) {
console.error('Not enough parameters given. Try this: ' +
'"node simplegrep_async term filename.txt"');
process.exit(1);
}
var searchterm = process.argv[2];
var filename = process.argv[3];
var returnMatches = function(err, rawContents) {
var lines = rawContents.split('\n');
lines.forEach(function(theLine) {
if (theLine.indexOf(searchterm) > -1) {
console.log(theLine);
}
});
}
fs.readFile(filename, 'utf-8', returnMatches);
tinywebserver.js
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 + "/");
});
form-demo/bin/www
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('form-demo:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
form-demo/app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
if (app.get('env') === 'development') {
// development error handler
// will print stacktrace
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
app.locals.pretty = true;
} else {
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
}
module.exports = app;
form-demo/routes/index.js
var express = require('express');
var router = express.Router();
var state = [];
router.get('/', function(req, res, next) {
res.render('index', { title: 'COMP 2406 Simple form demo' });
});
router.post('/add', function(req, res) {
var obj = { name: req.body.name,
city: req.body.city,
country: req.body.country,
birthday: req.body.birthday,
email: req.body.email };
state.push(obj);
res.render('add', { title: 'Person just added', item: obj });
});
router.get('/list', function(req, res) {
res.render('list', { title: 'People Listing', items: state});
});
module.exports = router;
form-demo/views/layout.jade
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
block header
body
block content
form-demo/views/index.jade
extends layout
block content
h1= title
div
p Fill out your info
form(method="post", action="/add")
div
input#name(type="text", name="name")
label Name
div
input#country(type="text", name="city")
label City
div
input#country(type="text", name="country")
label Country
div
input#birthday(type="text", name="birthday")
label Birthday
div
input#email(type="text", name="email")
label Email
button(type="submit") Submit
form-demo/views/add.jade
extends layout
block content
h1= title
p Name: #{item.name}
p City: #{item.city}
p Country: #{item.country}
p Birthday: #{item.birthday}
p Email: #{item.email}
form-demo/views/list.jade
extends layout
block content
h1= title
div
div
table
thead
th Name
th City
th Country
th Birthday
th Email
tbody
each item in items
tr
td #{item.name}
td #{item.city}
td #{item.country}
td #{item.birthday}
td #{item.email}
form(method="get", action="/")
button(type="submit") Home
form-demo/views/error.jade
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}