Skip to content
Browse files

handle missing slashes and default filename. virtualhost support seem…

…s to work. bumped version in preparation for npm publish
  • Loading branch information...
1 parent a1a54ae commit 2099a4815384764eca157b0f63b21ec3133b77b9 Jeremy Bornstein committed Jan 29, 2012
Showing with 93 additions and 22 deletions.
  1. +6 −5 README.md
  2. +83 −15 bastard.js
  3. +1 −1 package.json
  4. +3 −1 start.js
View
11 README.md
@@ -5,7 +5,7 @@ The purpose of bastard is to serve static content over the web quickly, accordin
Additionally, bastard will automatically generate cryptographic fingerprints for all files it serves, and return this fingerprint as the value of the Etag header in its responses. Files are available at fingerprinted URLs so that they can be cached indefinitely by client browsers. You can programmatically ask it for the current fingerprinted URL for a file so that you can use that URL in HTML you generate external to the server. When bastard serves fingerprinted files, they are served with very long cache times because those URLs should always serve the same content.
-CSS, Javascript, and HTML are minified. Files of other types are not modified, though they will be compressed for transmission if they're not image files. (Image files are never compressed by this software.) Note that in some rare cases, HTML minification can cause problems. In bastard, the HTML minification is not extremely aggressive and so will probably be fine. You can turn it off with a future config option if you are worried or actually find a problem in practice.
+CSS, Javascript, and HTML are minified, including CSS and Javascript inside HTML files. Files of other types are not modified, though they will be compressed for transmission if they're not image files. (Image files are never compressed by this software.) Note that in some rare cases, HTML minification can cause problems. In bastard, the HTML minification is not extremely aggressive and so will probably be fine. You can turn it off with a future config option if you are worried or actually find a problem in practice.
Installing
@@ -78,6 +78,8 @@ Note that there are some not-too-complicated subtleties in URL matching. The ra
`base` Directory where files to be served reside. (Default: empty)
+`defaultFileName` The name of the default file when a path ending in '/' is specified. (Default: /index.html/)
+
`rawURLPrefix` The prefix for URLs from which raw files should be served. These will be just as they are on disk: not minified, not compressed. (Default: /raw/)
`fingerprintURLPrefix` The prefix for URLs from which fingerprinted files should be served. The fingerprint will appear in the URLs after this prefix followed by the relative pathname to the file in the base directory. (Default: /f/)
@@ -90,6 +92,9 @@ Note that there are some not-too-complicated subtleties in URL matching. The ra
`directories` If true, will generate directory listings. (Not yet implemented.) (Default: false)
+`virtualHostMode` If true, directories in `base` represent hostnames. Files will be served from the matching directory based on the HTTP 1.1 hostname received. If there is no match, the first compoent (e.g. "www." from "www.example.com") is removed and the match is tried again. If there is still no match, the default directory is used. (See below)
+
+`defaultHost` The name of the directory to be used when the HTTP 1.1 hostname matches no other directory. Used only when `virtualHostMode` is true.
Standalone Server
-----------------
@@ -109,8 +114,6 @@ Limitations
If the mime type for a file begins with "image/", it will not be gzipped. All other files will be gzipped if the client indicates that it can understand gzipped data. This may not be the best choice for all file types.
-Does not do virtual hosting. If you want virtual hosting, create a new Bastard object for each host.
-
Project Status
==============
@@ -125,8 +128,6 @@ Future features:
* Ability to use an API to upload processed files from base directory to a key/value store--including fingerprinted URLs. This would allow bastard to front for a CDN.
-* Minify CSS and Javascript embedded in HTML.
-
License
=======
View
98 bastard.js
@@ -50,18 +50,39 @@ function Bastard (config) {
console.info (config);
if (debug) console.info ("Debugging output enabled");
+ var defaultFileName = config.defaultFileName || 'index.html';
var alwaysCheckModTime = config.alwaysCheckModTime;
var baseDir = config.base;
var errorHandler = config.errorHandler;
var storageDir = config.workingDir || '/tmp/bastard.dat';
var urlPrefix = config.urlPrefix;
var rawURLPrefix = config.rawURLPrefix;
+ var virtualHostMode = config.virtualHostMode || false;
var fingerprintURLPrefix = config.fingerprintURLPrefix;
if (baseDir.charAt (baseDir.length-1) != '/') baseDir += '/';
me._emitter = new events.EventEmitter ();
+ var virtualHostDirs;
+ if (virtualHostMode) {
+ virtualHostDirs = {};
+ fs.readdir (baseDir, function (err, list) {
+ if (err) throw "Problem reading virtual host directories from " + baseDir;
+ for (var i = 0; i < list.length; i++) {
+ var item = list[i];
+ virtualHostDirs[item.toLowerCase ()] = baseDir + item + "/";
+ }
+ });
+ }
+
me.minifyHTML = function (data, filePath, basePath) {
+ console.info ("******** Minimizing HTML");
+ console.info ("file path: " + filePath);
+ console.info ("base path: " + basePath);
+ var baseDir = filePath.substring (0, filePath.length - basePath.length);
+ console.info ("base dir: " + baseDir);
+ console.info ("********");
+
var provisional = false; // set to true if we are missing a fingerprint
// identify embedded CSS and replace it with minimized CSS
@@ -531,7 +552,10 @@ function Bastard (config) {
cacheRecord.raw = data; // only keep it if we might be asked for it later
}
- if (!basePath) basePath = filePath.substring (baseDir.length);
+ if (!basePath) {
+ basePath = filePath.substring (baseDir.length);
+ if (virtualHostMode) basePath = basePath.substring (basePath.indexOf ('/') + 1);
+ }
// console.info ("Preprocessor: " + preprocessor);
if (preprocessor) {
@@ -587,7 +611,7 @@ function Bastard (config) {
'Content-Type': contentType,
'Vary': 'Accept-Encoding',
'Cache-Control': "max-age=" + maxAgeInSeconds,
- 'Server': 'bastard/0.5.11'
+ 'Server': 'bastard/0.6.0
};
if (encoding) responseHeaders['Content-Encoding'] = encoding;
if (modificationTime) responseHeaders['Last-Modified'] = modificationTime;
@@ -697,7 +721,7 @@ function Bastard (config) {
if (errorHandler) {
errorHandler (response, errorCode, errorMessage);
} else {
- response.writeHead (errorCode, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.5.11'});
+ response.writeHead (errorCode, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.6.0});
response.end (errorMessage, 'utf8');
}
return;
@@ -710,15 +734,15 @@ function Bastard (config) {
if (errorHandler) {
errorHandler (response, 404, errorMessage);
} else {
- response.writeHead (404, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.5.11'});
+ response.writeHead (404, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.6.0});
response.end (errorMessage, 'utf8');
}
return;
}
var modificationTime = cacheRecordParam.modified;
if (ifModifiedSince && modificationTime && modificationTime <= ifModifiedSince) {
- response.writeHead (304, {'Server': 'bastard/0.5.11'});
+ response.writeHead (304, {'Server': 'bastard/0.6.0});
response.end ();
} else {
if (headOnly) {
@@ -729,7 +753,7 @@ function Bastard (config) {
if (errorHandler) {
errorHandler (response, 404, errorMessage);
} else {
- response.writeHead (404, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.5.11'});
+ response.writeHead (404, {'Content-Type': 'text/plain; charset=utf-8', 'Server': 'bastard/0.6.0});
response.end (errorMessage, 'utf8');
}
} else {
@@ -753,7 +777,7 @@ function Bastard (config) {
var callbackOK = callback instanceof Function;
// if filePath is null but basePath is not, figure out filePath
- if (!filePath && basePath) filePath = baseDir + basePath;
+ if (!filePath && basePath) filePath = baseDir + basePath; // TODO: does not work for virtualhosts
if (debug) console.info ("Fingerprinting: " + filePath + " aka " + basePath);
var cacheRecord = cacheData[filePath];
@@ -769,7 +793,7 @@ function Bastard (config) {
}
function serveFromCacheRecord (cacheRecordParam) {
- response.writeHead (200, {'Content-Type': 'text/plain', 'Server': 'bastard/0.5.11'});
+ response.writeHead (200, {'Content-Type': 'text/plain', 'Server': 'bastard/0.6.0});
response.end (errorMessage, 'utf8');
}
@@ -782,17 +806,30 @@ function Bastard (config) {
}
}
+ function matchingVirtualHostDir (host) {
+ host = host.toLowerCase ();
+ if (host in virtualHostDirs) return virtualHostDirs[host];
+ host = host.substring (host.indexOf ('.') + 1);
+ if (host in virtualHostDirs) return virtualHostDirs[host];
+ return baseDir + config.defaultHost + "/";
+ }
+
+
var fingerprintPrefixLen = fingerprintURLPrefix.length;
var urlPrefixLen = urlPrefix.length;
var rawPrefixLen = rawURLPrefix.length;
+ var directoryCheck = {};
me.possiblyHandleRequest = function (request, response) {
+ if (virtualHostMode) request.baseDir = matchingVirtualHostDir (request.headers.host);
+ else request.baseDir = baseDir;
+
// console.info ("PFC maybe handling: " + request.url);
// console.info ('fup: ' + fingerprintURLPrefix);
// console.info ('up: ' + urlPrefix);
if (rawURLPrefix && request.url.indexOf (rawURLPrefix) == 0) {
var basePath = request.url.substring (rawPrefixLen);
console.info (" raw basePath: " + basePath);
- var filePath = baseDir + basePath;
+ var filePath = request.baseDir + basePath;
console.info (" raw filePath: " + filePath);
var acceptEncoding = request.headers['accept-encoding'];
var gzipOK = acceptEncoding && (acceptEncoding.split(',').indexOf ('gzip') >= 0);
@@ -807,7 +844,7 @@ function Bastard (config) {
var slashPos = base.indexOf ('/');
var basePath = base.substring (slashPos + 1);
var fingerprint = base.substring (0, slashPos)
- var filePath = baseDir + basePath;
+ var filePath = request.baseDir + basePath;
// console.info (" fingerprint filePath: " + filePath);
// console.info (" fingerprint: " + fingerprint);
var acceptEncoding = request.headers['accept-encoding'];
@@ -818,21 +855,52 @@ function Bastard (config) {
return true;
}
if (request.url.indexOf (urlPrefix) == 0) {
- var basePath = request.url.substring (urlPrefixLen);
-
- var filePath = baseDir + basePath;
- //console.info (" filePath: " + filePath);
var acceptEncoding = request.headers['accept-encoding'];
var gzipOK = acceptEncoding && (acceptEncoding.split(',').indexOf ('gzip') >= 0);
var ifModifiedSince = request.headers['if-modified-since']; // fingerprinted files are never modified, so what do we do here?
var headOnly = request.method == 'HEAD';
- serve (response, filePath, basePath, null, gzipOK, false, alwaysCheckModTime, ifModifiedSince, headOnly);
+
+ var basePath = request.url.substring (urlPrefixLen);
+
+ if (basePath.length == 0 || basePath.charAt (basePath.length - 1) == '/') basePath += defaultFileName;
+
+ var filePath = request.baseDir + basePath;
+ console.info (" filePath: " + filePath);
+ console.info (" basePath: " + basePath);
+
+ if (filePath in directoryCheck) {
+ if (directoryCheck[filePath]) {
+ console.info ("It is a directory (i already checked). Should do the special thing.");
+ filePath += "/" + defaultFileName;
+ basePath += "/" + defaultFileName;
+ } else {
+ console.info ("Not a directory. No special thing.");
+ }
+ serve (response, filePath, basePath, null, gzipOK, false, alwaysCheckModTime, ifModifiedSince, headOnly);
+ } else {
+ fs.stat (filePath, function (err, statObj) {
+ if (!err) {
+ var isDir = statObj.isDirectory ();
+ directoryCheck[filePath] = isDir;
+ if (isDir) {
+ console.info ("It is a directory. Should do the special thing.");
+ filePath += "/" + defaultFileName;
+ basePath += "/" + defaultFileName;
+ } else {
+ console.info ("Not a directory. No special thing.");
+ }
+ }
+ serve (response, filePath, basePath, null, gzipOK, false, alwaysCheckModTime, ifModifiedSince, headOnly);
+ });
+ }
+
return true;
}
// console.info ("NO MATCH: " + request.url);
return false; // do not want
}
+ // TODO: fix this for virtual host mode
var prefixLengthToRemove = baseDir.length;
me.urlForFile = function (filePath) {
var basePath = filePath.substring (prefixLengthToRemove);
View
2 package.json
@@ -1,6 +1,6 @@
{
"name": "bastard",
- "version": "0.5.11",
+ "version": "0.6.0",
"description": "A webserver for static files that does things right.",
"keywords": "webserver, fingerprint, static",
"homepage": "http://jeremy.org/bastard/",
View
4 start.js
@@ -23,6 +23,8 @@ function startBastard () {
var config = {
base: base,
debug: process.env.npm_package_config_debug == 'true',
+ virtualHostMode: process.env.npm_package_config_virtualHostMode == 'true',
+ defaultHost: process.env.npm_package_config_defaultHost || 'default',
alwaysCheckModTime: process.env.npm_package_config_alwaysCheckModTime == 'true',
directories: process.env.npm_package_config_directories == 'true',
rawURLPrefix: process.env.npm_package_config_rawURLPrefix,
@@ -57,7 +59,7 @@ function startBastard () {
if (!handled) {
console.warn ("Request not handled by bastard: " + request.method + " " + request.url);
response.writeHead (404, {
- 'Server': 'bastard/0.5.11',
+ 'Server': 'bastard/0.6.0,
'Content-Type': 'text/plain; charset=utf-8'
});
response.end ("Not found.");

0 comments on commit 2099a48

Please sign in to comment.
Something went wrong with that request. Please try again.