From 4635b1894442e47b14c7192723c413b3589a629a Mon Sep 17 00:00:00 2001 From: David Beale Date: Mon, 4 May 2015 21:57:25 +0100 Subject: [PATCH] Add indexDocument option - which servers static web content --- lib/app.js | 4 +- lib/controllers.js | 93 ++++++++++++++++++++++++++-------- lib/index.js | 14 ++++- package.json | 1 + test/test.js | 124 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 23 deletions(-) diff --git a/lib/app.js b/lib/app.js index 9ffc0908..26e0d418 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,10 +1,10 @@ 'use strict'; -var app = function (hostname, port, directory, silent) { +var app = function (hostname, port, directory, silent, indexDocument, errorDocument) { var express = require('express'), app = express(), logger = require('./logger')(silent), Controllers = require('./controllers'), - controllers = new Controllers(directory, logger), + controllers = new Controllers(directory, logger, indexDocument, errorDocument), concat = require('concat-stream'); /** diff --git a/lib/controllers.js b/lib/controllers.js index 558622e7..04ff9080 100644 --- a/lib/controllers.js +++ b/lib/controllers.js @@ -1,5 +1,5 @@ 'use strict'; -module.exports = function (rootDirectory, logger) { +module.exports = function (rootDirectory, logger, indexDocument, errorDocument) { var FileStore = require('./file-store'), fileStore = new FileStore(rootDirectory), templateBuilder = require('./xml-template-builder'); @@ -10,6 +10,23 @@ module.exports = function (rootDirectory, logger) { return res.send(template); }; + var buildResponse = function (req, res, status, object, data) { + res.header('Etag', object.md5); + res.header('Last-Modified', new Date(object.modifiedDate).toUTCString()); + res.header('Content-Type', object.contentType); + res.header('Content-Length', object.size); + if (object.customMetaData.length > 0) { + object.customMetaData.forEach(function (metaData) { + res.header(metaData.key, metaData.value); + }); + } + res.status(200); + if (req.method === 'HEAD') { + return res.end(); + } + return res.end(data); + }; + /** * The following methods correspond the S3 api. For more information visit: * http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html @@ -37,6 +54,7 @@ module.exports = function (rootDirectory, logger) { return buildXmlResponse(res, 200, template); }, getBucket: function (req, res) { + var options = { marker: req.query.marker || null, prefix: req.query.prefix || null, @@ -46,8 +64,36 @@ module.exports = function (rootDirectory, logger) { logger.info('Fetched bucket "%s" with options %s', req.bucket.name, options); fileStore.getObjects(req.bucket, options, function (err, results) { logger.info('Found %d objects for bucket "%s"', results.length, req.bucket.name); - var template = templateBuilder.buildBucketQuery(options, results); - return buildXmlResponse(res, 200, template); + + var match = false; + if (indexDocument) + { + results.forEach(function(result){ + if (result.key === indexDocument) + { + match = true; + fileStore.getObject(req.bucket, result.key, function (err, object, data) { + if (err) + { + var template = templateBuilder.buildKeyNotFound(keyName); + logger.error('Object "%s" in bucket "%s" does not exist', keyName, req.bucket.name); + return buildXmlResponse(res, 404, template); + } + else + { + logger.info('Serving Page: %s', object.key); + return buildResponse(req, res, 200, object, data); + } + }); + } + }); + } + + if (!match) + { + var template = templateBuilder.buildBucketQuery(options, results); + return buildXmlResponse(res, 200, template); + } }); }, putBucket: function (req, res) { @@ -107,9 +153,28 @@ module.exports = function (rootDirectory, logger) { } fileStore.getObject(req.bucket, keyName, function (err, object, data) { if (err) { - var template = templateBuilder.buildKeyNotFound(keyName); - logger.error('Object "%s" in bucket "%s" does not exist', keyName, req.bucket.name); - return buildXmlResponse(res, 404, template); + + if (indexDocument) + { + return fileStore.getObject(req.bucket, keyName + indexDocument, function (err, object, data) { + if (err) + { + var template = templateBuilder.buildKeyNotFound(keyName); + logger.error('Object "%s" in bucket "%s" does not exist', keyName, req.bucket.name); + return buildXmlResponse(res, 404, template); + } + else + { + return buildResponse(req, res, 200, object, data); + } + }); + } + else + { + var template = templateBuilder.buildKeyNotFound(keyName); + logger.error('Object "%s" in bucket "%s" does not exist', keyName, req.bucket.name); + return buildXmlResponse(res, 404, template); + } } var noneMatch = req.headers['if-none-match']; @@ -124,20 +189,8 @@ module.exports = function (rootDirectory, logger) { return res.status(304).end(); } } - res.header('Etag', object.md5); - res.header('Last-Modified', new Date(object.modifiedDate).toUTCString()); - res.header('Content-Type', object.contentType); - res.header('Content-Length', object.size); - if (object.customMetaData.length > 0) { - object.customMetaData.forEach(function (metaData) { - res.header(metaData.key, metaData.value); - }); - } - res.status(200); - if (req.method === 'HEAD') { - return res.end(); - } - return res.end(data); + + return buildResponse(req, res, 200, object, data); }); }, putObject: function (req, res) { diff --git a/lib/index.js b/lib/index.js index 814afb57..5325417e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,6 +4,8 @@ var S3rver = function () { this.port = 4578; this.hostname = 'localhost'; this.silent = false; + this.indexDocument = ''; + this.errorDocument = ''; }; S3rver.prototype.setPort = function (port) { @@ -26,8 +28,18 @@ S3rver.prototype.setSilent = function (silent) { return this; }; +S3rver.prototype.setIndexDocument = function (indexDocument) { + this.indexDocument = indexDocument; + return this; +}; + +S3rver.prototype.setErrorDocument = function (errorDocument) { + this.errorDocument = errorDocument; + return this; +}; + S3rver.prototype.run = function (done) { - var app = new App(this.hostname, this.port, this.directory, this.silent); + var app = new App(this.hostname, this.port, this.directory, this.silent, this.indexDocument, this.errorDocument); return app.serve(done); }; diff --git a/package.json b/package.json index 2a9e586a..a91ade45 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "fs-extra": "^0.14.0", "mocha": "^2.1.0", "moment": "^2.8.4", + "request": "^2.55.0", "should": "^4.4.2", "xml2js": "^0.4.4" }, diff --git a/test/test.js b/test/test.js index ececa6b9..c830dd6c 100644 --- a/test/test.js +++ b/test/test.js @@ -11,6 +11,7 @@ var path = require('path'); var md5 = require('MD5'); var S3rver = require('../lib'); var util = require('util'); +var request = require('request'); describe('S3rver Tests', function () { var s3Client; @@ -550,3 +551,126 @@ describe('S3rver Tests', function () { }); }); }); + + +describe('S3rver Tests with Static Web Hosting', function () { + var s3Client; + before(function (done) { + /** + * Start the server + */ + var s3rver = new S3rver(); + s3rver.setHostname('localhost') + .setPort(5694) + .setDirectory('/tmp/s3rver_test_directory') + .setSilent(true) + .setIndexDocument('index.html') + .run(function (err, hostname, port, directory) { + if (err) { + return done('Error starting server', err); + } + var config = { + accessKeyId: '123', + secretAccessKey: 'abc', + endpoint: util.format('%s:%d', hostname, port), + sslEnabled: false, + s3ForcePathStyle: true + }; + AWS.config.update(config); + s3Client = new AWS.S3(); + s3Client.endpoint = new AWS.Endpoint(config.endpoint); + /** + * Remove if exists and recreate the temporary directory + */ + fs.remove(directory, function (err) { + if (err) { + return done(err); + } + fs.mkdirs(directory, function (err) { + if (err) { + return done(err); + } + done(); + }); + }); + }); + }); + + + + it('should create a site bucket', function (done) { + s3Client.createBucket({Bucket: 'site'}, function (err) { + if (err) { + return done(err); + } + done(); + }); + }); + + + it('should upload a html page to / path', function (done) { + var params = {Bucket: 'site', Key: 'index.html', Body: 'Hello'}; + s3Client.putObject(params, function (err, data) { + /[a-fA-F0-9]{32}/.test(data.ETag).should.equal(true); + if (err) { + return done(err); + } + done(); + }); + }); + + + it('should upload a html page to a directory path', function (done) { + var params = {Bucket: 'site', Key: 'page/index.html', Body: 'Hello'}; + s3Client.putObject(params, function (err, data) { + /[a-fA-F0-9]{32}/.test(data.ETag).should.equal(true); + if (err) { + return done(err); + } + done(); + }); + }); + + + it('should get an index page at / path', function (done) { + request('http://localhost:5694/site/', function (error, response, body) { + if (error) + { + return done(error); + } + + if (response.statusCode !== 200) { + return done(new Error('Invalid status: ' + response.statusCode)); + } + + if (body !== 'Hello') + { + return done(new Error('Invalid Content: ' + body)); + } + + done(); + }); + }); + + + it('should get an index page at /page/ path', function (done) { + request('http://localhost:5694/site/page/', function (error, response, body) { + if (error) + { + return done(error); + } + + if (response.statusCode !== 200) { + return done(new Error('Invalid status: ' + response.statusCode)); + } + + if (body !== 'Hello') + { + return done(new Error('Invalid Content: ' + body)); + } + + done(); + }); + }); + +});