| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /*
- * Basic caching tests
- */
- var fs = require('fs');
- var http = require('http');
- var path = require('path');
- var url = require('url');
- var util = require('util');
- var assert = require('assert-plus');
- var mkdirp = require('mkdirp');
- var rimraf = require('rimraf');
- var test = require('tape');
- var lib = require('./lib');
- var FsCachingServer = require('../').FsCachingServer;
- var f = util.format;
- var config = lib.readConfig();
- var cachingServerURL = f('http://%s:%d', config.cachingServer.host,
- config.cachingServer.port);
- var backendServerURL = f('http://%s:%d', config.backendServer.host,
- config.backendServer.port);
- var cachingServer;
- var backendServer;
- var dir = path.join(__dirname, 'tmp');
- /*
- * wrapper for making web requests to the caching server
- */
- function cacheRequest(p, opts, cb) {
- assert.string(p, 'p');
- assert.object(opts, 'opts');
- assert.string(opts.method, 'opts.method');
- assert.func(cb, 'cb');
- var uri = f('%s%s', cachingServerURL, p);
- var o = url.parse(uri);
- Object.keys(opts).forEach(function (key) {
- var val = opts[key];
- o[key] = val;
- });
- var req = http.request(o, function (res) {
- var data = '';
- res.setEncoding('utf-8');
- res.on('data', function (d) {
- data += d;
- });
- res.on('end', function () {
- cb(null, data, res);
- });
- res.once('error', function (err) {
- cb(err);
- });
- });
- req.end();
- }
- /*
- * start the caching frontend
- */
- test('start cachingServer', function (t) {
- var opts = {
- cacheDir: dir,
- host: config.cachingServer.host,
- port: config.cachingServer.port,
- backendUrl: backendServerURL
- };
- mkdirp.sync(dir);
- rimraf.sync(dir + '/*');
- t.pass(f('tmp dir "%s" cleared', dir));
- cachingServer = new FsCachingServer(opts);
- cachingServer.once('start', function () {
- t.pass('cachingServer started');
- t.end();
- });
- if (process.env.NODE_DEBUG) {
- cachingServer.on('log', console.log);
- }
- cachingServer.start();
- });
- /*
- * start the web server backend
- */
- test('start backendServer', function (t) {
- backendServer = http.createServer(onRequest);
- backendServer.listen(config.backendServer.port, config.backendServer.host,
- onListen);
- function onRequest(req, res) {
- var p = url.parse(req.url).pathname;
- var s = f('%s request by pid %d at %s\n', p, process.pid, Date.now());
- // handle: /statusCode/<num>
- var matches;
- if ((matches = p.match(/^\/statusCode\/([0-9]+)/))) {
- res.statusCode = parseInt(matches[1], 10);
- res.end(s);
- return;
- }
- switch (p) {
- case '/301.png':
- res.statusCode = 301;
- res.setHeader('Location', '/foo.png');
- res.end();
- break;
- case '/header.png':
- res.setHeader('x-fun-header', 'woo');
- res.end();
- default:
- res.end(s);
- break;
- }
- }
- function onListen() {
- t.pass(f('backendServer started on %s', backendServerURL));
- t.end();
- }
- });
- /*
- * Basic request that should be cached.
- */
- test('simple cached request', function (t) {
- var f = '/hello.png';
- cacheRequest(f, {method: 'GET'}, function (err, webData) {
- t.error(err, 'GET ' + f);
- cachingServer.onIdle(function () {
- // check to make sure the cache has this data
- var fileData = fs.readFileSync(path.join(dir, f), 'utf-8');
- t.equal(webData, fileData, 'file data in sync with web data');
- // request it again to ensure it's correct
- cacheRequest(f, {method: 'GET'}, function (err, webData2) {
- t.error(err, 'GET ' + f);
- t.equal(webData, webData2, 'both web requests same data');
- cachingServer.onIdle(function () {
- t.end();
- });
- });
- });
- });
- });
- /*
- * Basic request that should not be cached.
- */
- test('simple non-cached request', function (t) {
- var f = '/hello.txt';
- cacheRequest(f, {method: 'GET'}, function (err, webData) {
- t.error(err, 'GET ' + f);
- cachingServer.onIdle(function () {
- // check to make sure the cache DOES NOT have this data
- var file = path.join(dir, f);
- t.throws(function () {
- fs.statSync(file);
- }, file + ' should not exist');
- // request it again to ensure the data is different (time difference)
- cacheRequest(f, {method: 'GET'}, function (err, webData2) {
- t.error(err, 'GET ' + f);
- t.notEqual(webData, webData2, 'both web requests different data');
- cachingServer.onIdle(function () {
- t.end();
- });
- });
- });
- });
- });
- /*
- * Codes that *should not* be proxied.
- */
- test('statusCodes without proxy', function (t) {
- var codes = [301, 302, 403, 404, 500, 501, 502];
- var idx = 0;
- function go() {
- var code = codes[idx++];
- if (!code) {
- t.end();
- return;
- }
- var uri = f('/statusCode/%d/foo.png', code);
- cacheRequest(uri, {method: 'GET'}, function (err, webData) {
- t.error(err, 'GET ' + uri);
- t.equal(webData, '', 'webData should be empty from caching server');
- cachingServer.onIdle(function () {
- // ensure the file does NOT exist in the cache dir
- var file = path.join(dir, uri);
- t.throws(function () {
- fs.statSync(file);
- }, file + ' should not exist');
- go();
- });
- });
- }
- go();
- });
- /*
- * Codes that *should* be proxied.
- */
- test('statusCodes with proxy', function (t) {
- var codes = [200];
- var idx = 0;
- function go() {
- var code = codes[idx++];
- if (!code) {
- t.end();
- return;
- }
- var uri = f('/statusCode/%d/foo.png', code);
- cacheRequest(uri, {method: 'GET'}, function (err, webData) {
- t.error(err, 'GET ' + uri);
- t.ok(webData, 'webData should have data from server');
- cachingServer.onIdle(function () {
- // ensure the file exists with the corect data
- var file = path.join(dir, uri);
- var fileData = fs.readFileSync(file, 'utf-8');
- t.equal(webData, fileData, 'file data in sync with web data');
- go();
- });
- });
- }
- go();
- });
- /*
- * The first request to an item (cache-miss) will result in the request being
- * proxied directly to the backendServer. Subsequent requests will not be
- * proxied and instead will just be handed the cached file without any of the
- * original headers.
- */
- test('headers proxied only on first request', function (t) {
- var uri = '/header.png';
- var serverHeader = 'x-fun-header';
- // initial request (cache miss) should proxy headers from server
- cacheRequest(uri, {method: 'GET'}, function (err, webData, res) {
- t.error(err, 'GET ' + uri);
- var headers = res.headers;
- var customHeader = headers[serverHeader];
- t.equal(customHeader, 'woo', f('custom header %s seen', serverHeader));
- cachingServer.onIdle(function () {
- // second request (cache hit) won't remember headers from server
- cacheRequest(uri, {method: 'GET'}, function (err, webData, res) {
- t.error(err, 'GET ' + uri);
- var headers = res.headers;
- var customHeader = headers[serverHeader];
- t.ok(!customHeader, f('custom header %s not seen', serverHeader));
- cachingServer.onIdle(function () {
- t.end();
- });
- });
- });
- });
- });
- /*
- * FsCachingServer handles redirects by specifically choosing to not handle
- * them. Instead, the statusCodes and headers from the backendServer will be
- * sent directly to the caller, and it is up to that caller if they'd like to
- * follow the redirect. If the redirects eventually hit a GET or HEAD request
- * that falls within the 200 range, then it will be cached as normal.
- */
- test('301 redirect', function (t) {
- var uri = '/301.png';
- var redirect = '/foo.png';
- cacheRequest(uri, {method: 'GET'}, function (err, webData, res) {
- t.error(err, 'GET ' + uri);
- t.ok(!webData, 'body empty');
- t.equal(res.statusCode, 301, '301 seen');
- var headers = res.headers;
- var loc = headers.location;
- t.equal(loc, redirect, 'location is correct');
- cachingServer.onIdle(function () {
- t.end();
- });
- });
- });
- /*
- * Requesting a directory that exists in the cache should result in a 400.
- */
- test('GET directory in cache', function (t) {
- var uri = '/directory.png';
- fs.mkdirSync(path.join(dir, uri));
- cacheRequest(uri, {method: 'GET'}, function (err, webData, res) {
- t.error(err, 'GET ' + uri);
- t.ok(!webData, 'body empty');
- t.equal(res.statusCode, 400, '400 seen');
- cachingServer.onIdle(function () {
- t.end();
- });
- });
- });
- /*
- * Two simulataneous requests for a cache-miss. This will result in one of the
- * requests being responsible for downloading the file and getting it streamed
- * to them live, and the other request being paused until the data is fully
- * downloaded.
- *
- * To simulate this fs.stat will be artifically slown down so both requests
- * will block before the cache download begins.
- */
- test('Two simultaneous requests', function (t) {
- var originalStat = fs.stat.bind(fs);
- fs.stat = function slowStat(f, cb) {
- setTimeout(function () {
- originalStat(f, cb);
- }, 100);
- };
- var uri = '/simultaneous.png';
- var todo = 2;
- cacheRequest(uri, {method: 'GET'}, requestOne);
- setTimeout(function () {
- cacheRequest(uri, {method: 'GET'}, requestTwo);
- }, 30);
- var data1;
- var data2;
- function requestOne(err, data, res) {
- t.error(err, '1. GET ' + uri);
- data1 = data;
- finish();
- }
- function requestTwo(err, data, res) {
- t.error(err, '2. GET ' + uri);
- data2 = data;
- finish();
- }
- function finish() {
- if (--todo > 0) {
- return;
- }
- fs.stat = originalStat;
- cachingServer.onIdle(function () {
- t.equal(data1, data2, 'data the same');
- t.end();
- });
- }
- });
- /*
- * HEAD requests should only server cached results and not cache them itself.
- * i.e. if a HEAD request is seen first it should just be proxied to the
- * backend directly with no caching.
- */
- test('HEAD request', function (t) {
- var uri = '/head-cache-test.png';
- cacheRequest(uri, {method: 'HEAD'}, function (err, data, res) {
- t.error(err, 'HEAD ' + uri);
- cachingServer.onIdle(function () {
- // check to make sure the cache DOES NOT have this data
- var file = path.join(dir, uri);
- t.throws(function () {
- fs.statSync(file);
- }, file + ' should not exist');
- t.end();
- });
- });
- });
- /*
- * Close the backend HTTP server
- */
- test('close backendServer', function (t) {
- backendServer.once('close', function () {
- t.pass('backendServer closed');
- t.end();
- });
- backendServer.close();
- });
- /*
- * Stop the caching server
- */
- test('stop cachingServer', function (t) {
- cachingServer.once('stop', function () {
- t.pass('cachingServer stopped');
- t.end();
- });
- cachingServer.stop();
- });
|