From c95f92fa4c42ef86cd994ca0361cb3c1909e6f2d Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Wed, 4 Jul 2012 13:36:51 +0200 Subject: o Sending 405 on bad method, 406 on bad Accept values. --- app.js | 19 ++-- routes/index.js | 173 +++++++++++++++++++++++++++---------- views/department.jade | 2 +- views/employeesInDepartment.jade | 22 +++++ views/employees_in_department.jade | 22 ----- views/index.jade | 6 ++ 6 files changed, 170 insertions(+), 74 deletions(-) create mode 100644 views/employeesInDepartment.jade delete mode 100644 views/employees_in_department.jade diff --git a/app.js b/app.js index c9e8a1b..036fe61 100644 --- a/app.js +++ b/app.js @@ -14,6 +14,11 @@ app.configure(function(){ app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); + // Unfuck the stuff that express injects + app.use(function (req, res, next) { + res.removeHeader("X-Powered-By"); + next(); + }); app.use(urlgenerator); app.use(accept); app.use(app.router); @@ -42,7 +47,7 @@ function urlgenerator(req, res, next) { department: function(dept_no) { return 'http://' + host + '/department/' + dept_no; }, - employees_in_department: function(dept_no, query) { + employeesInDepartment: function(dept_no, query) { return url.format({ protocol: 'http', host: host, @@ -66,12 +71,12 @@ function urlgenerator(req, res, next) { next(); } -app.get('/', routes.index); -app.get('/department', routes.departments); -app.get('/department/:dept_no', routes.department); -app.get('/department/:dept_no/employees', routes.employees_in_department); -app.get('/employee', routes.employees); -app.get('/employee/:emp_no', routes.employee); +app.all('/', routes.index); +app.all('/department', routes.departments); +app.all('/department/:dept_no', routes.department); +app.all('/department/:dept_no/employees', routes.employeesInDepartment); +app.all('/employee', routes.employees); +app.all('/employee/:emp_no', routes.employee); http.createServer(app).listen(app.get('port'), function(){ console.log("Express server listening on port " + app.get('port')); diff --git a/routes/index.js b/routes/index.js index 23af53d..ba2ca23 100644 --- a/routes/index.js +++ b/routes/index.js @@ -45,23 +45,41 @@ function pager(req, count) { function after(res, callback) { return function(err, queryResult) { if(err) { - res.writeHead(500, {"Content-Type" : "text/plain"}); - return res.end("Error! " + util.inspect(err)) + res.writeHead(500, {'Content-Type' : 'text/plain'}); + return res.end('Error! ' + util.inspect(err)) } callback(queryResult) } } -exports.index = function(req, res) { - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": +function method(handlers) { + return function(req, res) { + var handler = handlers[req.method]; + if(handler) { + return handler(req, res) + } + // I'm just too lazy to include two rows + if(req.method == 'HEAD') { + var handler = handlers[req.method]; + if(handler) { + return handler(req, res) + } + } + res.header('Allow', _.keys(handlers)); + return res.send(405); + } +} + +function getIndex(req, res) { + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json'])) { + case 'text/html': res.render('index', { title: 'Employee DB', urlgenerator: res.urlgenerator }); break; - case "application/vnd.collection+json": - default: + case 'application/vnd.collection+json': + case '*/*': var c = {collection: { links: [ { rel: 'departments', @@ -76,10 +94,16 @@ exports.index = function(req, res) { res.contentType('application/vnd.collection+json'); res.send(JSON.stringify(collection_json.fromObject(c)), 200); break; + default: + res.send(406); } }; -exports.departments = function(req, res) { +exports.index = method({ + GET: getIndex +}); + +function getDepartments(req, res) { pg.connect(process.env.DATABASE_URL, function(err, client) { if(err) throw err; var sql = 'SELECT n_live_tup FROM pg_stat_user_tables WHERE relname=\'departments\''; @@ -87,45 +111,53 @@ exports.departments = function(req, res) { client.query(sql, after(res, function(rs) { var p = pager(req, parseInt(rs.rows[0].n_live_tup)); client.query(sql2, [ p.offset, p.limit ], after(res, function(rs2) { - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json'])) { + case 'text/html': res.render('departments', { title: 'Department List', urlgenerator: res.urlgenerator, pager: p, query: req.query, departments: rs2.rows }); break; - case "application/vnd.collection+json": - default: + case 'application/vnd.collection+json': + case '*/*': var c = {collection: { href: res.urlgenerator.departments(), items: mapDepartments(res, rs2.rows) }}; res.contentType('application/vnd.collection+json'); res.send(JSON.stringify(collection_json.fromObject(c)), 200); + break; + default: + res.send(406); } })); })); }); }; -exports.department = function(req, res) { +exports.departments = method({ + GET: getDepartments +}); + +function getDepartment(req, res) { var dept_no = req.params.dept_no; - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json'])) { + case 'text/html': res.render('department', { title: 'Department ' + dept_no, urlgenerator: res.urlgenerator, dept_no: dept_no }); - case "application/vnd.collection+json": - default: + break; + case 'application/vnd.collection+json': + case '*/*': var c = {collection: { href: res.urlgenerator.department(dept_no), links: [ { rel: 'employees', prompt: 'Employees in department ' + dept_no, - href: res.urlgenerator.employees_in_department(dept_no) + href: res.urlgenerator.employeesInDepartment(dept_no) },{ rel: 'departments', prompt: 'All departments', @@ -134,10 +166,17 @@ exports.department = function(req, res) { }}; res.contentType('application/vnd.collection+json'); res.send(JSON.stringify(collection_json.fromObject(c)), 200); + break; + default: + res.send(406); } } -exports.employees_in_department = function(req, res) { +exports.department = method({ + GET: getDepartment +}); + +function getEmployeesInDepartment(req, res) { pg.connect(process.env.DATABASE_URL, function(err, client) { if(err) throw err; var dept_no = req.params.dept_no; @@ -149,18 +188,19 @@ exports.employees_in_department = function(req, res) { client.query(sql1, [ dept_no ], after(res, function(rs1) { var p = pager(req, parseInt(rs1.rows[0].count)); client.query(sql2, [ dept_no, p.offset, p.limit ], after(res, function(rs2) { - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": - res.render('employees_in_department', { + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json'])) { + case 'text/html': + res.render('employeesInDepartment', { title: 'Department: #' + dept_no, urlgenerator: res.urlgenerator, pager: p, query: req.query, dept_no: dept_no, employees: rs2.rows }); - case "application/vnd.collection+json": - default: + break; + case 'application/vnd.collection+json': + case '*/*': var c = {collection: { - href: res.urlgenerator.employees_in_department(dept_no), + href: res.urlgenerator.employeesInDepartment(dept_no), links: [ { rel: 'department', prompt: 'Department: #' + dept_no, @@ -170,13 +210,22 @@ exports.employees_in_department = function(req, res) { }}; res.contentType('application/vnd.collection+json'); res.send(JSON.stringify(collection_json.fromObject(c)), 200); + break; + default: + res.send(406); } })); })); }); }; -exports.employees = function(req, res) { +exports.employeesInDepartment = method({ + GET: getEmployeesInDepartment +}); + +function getEmployees(req, res) { + var head = req.method == 'HEAD'; + pg.connect(process.env.DATABASE_URL, function(err, client) { if(err) throw err; var emp_no = req.params.emp_no; @@ -206,16 +255,27 @@ exports.employees = function(req, res) { sql2Params.push(p.offset); sql2Params.push(p.limit); client.query(sql2, sql2Params, after(res, function(rs2) { - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": - res.render('employees', { - title: 'Employee List', - urlgenerator: res.urlgenerator, pager: p, query: req.query, - employees: rs2.rows, - query: req.query - }); - case "application/vnd.collection+json": - default: + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json'])) { + case 'text/html': + if(head) { + // Can't be bothered to calculate Content-Length even if I + // should.. + res.writeHead(200, { + 'Content-Type': 'text/html', + }); + res.end(); + } + else { + res.render('employees', { + title: 'Employee List', + urlgenerator: res.urlgenerator, pager: p, query: req.query, + employees: rs2.rows, + query: req.query + }); + } + break; + case 'application/vnd.collection+json': + case '*/*': var links = []; if(_.isNumber(p.prevOffset)) { links.push({ @@ -240,41 +300,66 @@ exports.employees = function(req, res) { name: 'employee-search', prompt: 'Employee search', data: [ - { name: "name" } + { name: 'name' } ] }], items: mapEmployees(res, rs2.rows) }}; - res.contentType('application/vnd.collection+json'); - res.send(JSON.stringify(collection_json.fromObject(c)), 200); + var text = JSON.stringify(collection_json.fromObject(c)); + var headers = { + 'Content-Type': 'application/vnd.collection+json', + 'Content-Length': text.length + }; + if(head) { + res.writeHead(200, headers); + res.end(); + } + else { + res.send(text, headers, 200); + } + break; + default: + res.send(406); } })); })); }); }; -exports.employee = function(req, res) { +exports.employees = method({ + GET: getEmployees +}); + +function getEmployee(req, res) { pg.connect(process.env.DATABASE_URL, function(err, client) { if(err) throw err; var emp_no = req.params.emp_no; var sql = 'SELECT * FROM employees WHERE employees.emp_no=$1'; client.query(sql, [ emp_no ], after(res, function(rs) { - switch(req.accept.types.getBestMatch(["text/html", "application/vnd.collection+json"])) { - case "text/html": + switch(req.accept.types.getBestMatch(['text/html', 'application/vnd.collection+json', '*/*'])) { + case 'text/html': res.render('employee', { title: 'Employee: #' + emp_no, urlgenerator: res.urlgenerator, employee: rs.rows[0] }); - case "application/vnd.collection+json": - default: + break; + case 'application/vnd.collection+json': + case '*/*': var c = {collection: { href: res.urlgenerator.employee(emp_no), items: [ mapRow(rs.rows[0]) ], }}; res.contentType('application/vnd.collection+json'); res.send(JSON.stringify(collection_json.fromObject(c)), 200); + break; + default: + res.send(406); } })) }); } + +exports.employee = method({ + GET: getEmployee +}); diff --git a/views/department.jade b/views/department.jade index 32952a9..a29ef3a 100644 --- a/views/department.jade +++ b/views/department.jade @@ -10,6 +10,6 @@ block content | >> a(href=urlgenerator.department(dept_no)) Department ##{dept_no} - p: a(href=urlgenerator.employees_in_department(dept_no)) Employees in this department + p: a(href=urlgenerator.employeesInDepartment(dept_no)) Employees in this department p TODO: add links to manager and department name. diff --git a/views/employeesInDepartment.jade b/views/employeesInDepartment.jade new file mode 100644 index 0000000..f889c0c --- /dev/null +++ b/views/employeesInDepartment.jade @@ -0,0 +1,22 @@ +extends layout +include lib/pager + +block content + h1= title + + p + a(href=urlgenerator.start()) Employee DB + | >> + a(href=urlgenerator.departments()) Department List + | >> + a(href=urlgenerator.department(dept_no)) Department ##{dept_no} + | >> + a(href=urlgenerator.employeesInDepartment(dept_no)) Employees in Department ##{dept_no} + + table + each employee in employees + tr + td: a(href=urlgenerator.employee(employee.emp_no)) #{employee.first_name} #{employee.last_name} + + - var f = function(offset) { return urlgenerator.employeesInDepartment(dept_no, offset) } + mixin pager(pager, f, query) diff --git a/views/employees_in_department.jade b/views/employees_in_department.jade deleted file mode 100644 index 277c750..0000000 --- a/views/employees_in_department.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends layout -include lib/pager - -block content - h1= title - - p - a(href=urlgenerator.start()) Employee DB - | >> - a(href=urlgenerator.departments()) Department List - | >> - a(href=urlgenerator.department(dept_no)) Department ##{dept_no} - | >> - a(href=urlgenerator.employees_in_department(dept_no)) Employees in Department ##{dept_no} - - table - each employee in employees - tr - td: a(href=urlgenerator.employee(employee.emp_no)) #{employee.first_name} #{employee.last_name} - - - var f = function(offset) { return urlgenerator.employees_in_department(dept_no, offset) } - mixin pager(pager, f, query) diff --git a/views/index.jade b/views/index.jade index 7ff1bb1..363355c 100644 --- a/views/index.jade +++ b/views/index.jade @@ -25,6 +25,12 @@ block content code text/html | you will be served code application/vnd.collection+json + | if you include + code */* + | in your + code Accept + | header. If you request only unsupported types, you'll get a + a(href='http://httpstatus.es/406') 406 | . h4 Exploring with curl -- cgit v1.2.3