Permalink
Browse files

First working version

  • Loading branch information...
0 parents commit aee35f1e9c90ab8f65b1dec093110b9a297d7eaa @felixge committed Jan 17, 2010
Showing with 377 additions and 0 deletions.
  1. +19 −0 LICENSE.txt
  2. +71 −0 README.md
  3. +27 −0 example/basic.js
  4. BIN example/webroot/img/paperboy.jpg
  5. +11 −0 example/webroot/index.html
  6. +249 −0 lib/paperboy.js
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Debuggable Limited <felix@debuggable.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,71 @@
+# Paperboy
+
+## Purpose
+
+A node.js module for delivering static files.
+
+## Example
+
+Example from example/basic.js:
+
+ var
+ sys = require('sys'),
+ path = require('path'),
+ http = require('http'),
+ paperboy = require('../lib/paperboy'),
+
+ PORT = 8003,
+ WEBROOT = path.join(path.dirname(__filename), 'webroot');
+
+ http.createServer(function(req, res) {
+ paperboy
+ .deliver(WEBROOT, req, res)
+ .before(function() {
+ sys.puts('About to deliver: '+req.url);
+ })
+ .after(function() {
+ sys.puts('Delivered: '+req.url);
+ })
+ .error(function() {
+ sys.puts('Error delivering: '+req.url);
+ })
+ .otherwise(function() {
+ res.sendHeader(404, {'Content-Type': 'text/plain'});
+ res.sendBody('Sorry, no paper this morning!');
+ res.finish();
+ });
+ }).listen(PORT);
+
+## API Docs
+
+### paperboy.deliver(webroot, req, res)
+
+Checks the `webroot` folder if it has a file that matches the `req.url` and streams it to the client. If `req.url` ends with a '/' (slash), 'index.html' is appended automatically.
+
+Parameters:
+
+* `webroot`: Absolute path where too look for static files to serve
+* `req`: A `http.ServerRequest` object
+* `res`: A `http.ServerResponse` object
+
+The function returns a delegate object that provides the methods below. Each of those methods returns the delegate itself, allowing for chaining.
+
+#### delegate.before(callback)
+
+Fires if a matching file was found in the `webroot` and is about to be delivered. The delivery can be canceled by returning `false` from within the callback.
+
+#### delegate.after(callback)
+
+Fires after a file has been successfully delivered from the `webroot`.
+
+#### delegate.error(callback)
+
+Fires if there was an error delivering a file from the `webroot`.
+
+#### delegate.otherwise(callback)
+
+Fires if no matching file was found in the `webroot`. Also fires if `false` was returned in the `delegate.before()` callback.
+
+## License
+
+Dirty is licensed under the MIT license.
@@ -0,0 +1,27 @@
+var
+ sys = require('sys'),
+ path = require('path'),
+ http = require('http'),
+ paperboy = require('../lib/paperboy'),
+
+ PORT = 8003,
+ WEBROOT = path.join(path.dirname(__filename), 'webroot');
+
+http.createServer(function(req, res) {
+ paperboy
+ .deliver(WEBROOT, req, res)
+ .before(function() {
+ sys.puts('About to deliver: '+req.url);
+ })
+ .after(function() {
+ sys.puts('Delivered: '+req.url);
+ })
+ .error(function() {
+ sys.puts('Error delivering: '+req.url);
+ })
+ .otherwise(function() {
+ res.sendHeader(404, {'Content-Type': 'text/plain'});
+ res.sendBody('Sorry, no paper this morning!');
+ res.finish();
+ });
+}).listen(PORT);
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Paperboy Example</title>
+</head>
+<body>
+ <h1>It works!</h1>
+ <img src="/img/paperboy.jpg">
+</body>
+</html>
@@ -0,0 +1,249 @@
+var
+ Promise = require('events').Promise,
+ posix = require('posix'),
+ path = require('path');
+
+exports.streamFile = function(filepath, req, res, stat) {
+ var
+ promise = new Promise(),
+ encoding = "binary",
+ extension = filepath.split('.').slice(-1);
+ contentType = exports.contentTypes[extension] || 'application/octet-stream',
+ charset = exports.charsets[contentType];
+
+ if (charset) {
+ contentType += '; charset: '+charset;
+ }
+
+ res.sendHeader(200, {
+ 'Content-Type': contentType,
+ 'Content-Length': stat.size,
+ });
+
+ posix.open(filepath, process.O_RDONLY, 0666)
+ .addCallback(function (fd) {
+ var pos = 0;
+
+ function streamChunk () {
+ posix.read(fd, 16*1024, pos, encoding)
+ .addCallback(function (chunk, bytesRead) {
+ if (!chunk) {
+ posix.close(fd);
+ res.finish();
+ promise.emitSuccess();
+ return;
+ }
+
+ res.sendBody(chunk, encoding);
+ pos += bytesRead;
+
+ // Should be done on 'drain' event, but that seems broken right now
+ streamChunk();
+ })
+ .addErrback(function () {
+ promise.emitError.apply(promise, arguments);
+ });
+ }
+ streamChunk(); })
+ .addErrback(function (e) {
+ promise.emitError(e);
+ });
+
+ return promise;
+}
+
+exports.deliver = function(webroot, req, res) {
+ var
+ beforeCallback,
+ afterCallback,
+ otherwiseCallback,
+ errorCallback,
+ filepath = (req.url === '/')
+ ? path.join(webroot, 'index.html')
+ : path.join(webroot, req.url),
+ delegate = {
+ error: function(callback) {
+ errorCallback = callback;
+ return delegate;
+ },
+ before: function(callback) {
+ beforeCallback = callback;
+ return delegate;
+ },
+ after: function(callback) {
+ afterCallback = callback;
+ return delegate;
+ },
+ otherwise: function(callback) {
+ otherwiseCallback = callback;
+ return delegate;
+ },
+ };
+
+ posix
+ .stat(filepath)
+ .addCallback(function(stat) {
+ if (!stat.isFile()) {
+ return otherwiseCallback && otherwiseCallback();
+ }
+
+ var cancel = beforeCallback && (beforeCallback() === false);
+ if (cancel) {
+ return otherwiseCallback && otherwiseCallback();
+ }
+
+ exports
+ .streamFile(filepath, req, res, stat)
+ .addCallback(function() {
+ afterCallback && afterCallback();
+ })
+ .addErrback(function() {
+ errorCallback && errorCallback();
+ });
+ })
+ .addErrback(function() {
+ otherwiseCallback && otherwiseCallback();
+ });
+
+ return delegate;
+};
+
+exports.contentTypes = {
+ "aiff":"audio/x-aiff",
+ "arj":"application/x-arj-compressed",
+ "asf":"video/x-ms-asf",
+ "asx":"video/x-ms-asx",
+ "au":"audio/ulaw",
+ "avi":"video/x-msvideo",
+ "bcpio":"application/x-bcpio",
+ "ccad":"application/clariscad",
+ "cod":"application/vnd.rim.cod",
+ "com":"application/x-msdos-program",
+ "cpio":"application/x-cpio",
+ "cpt":"application/mac-compactpro",
+ "csh":"application/x-csh",
+ "css":"text/css",
+ "deb":"application/x-debian-package",
+ "dl":"video/dl",
+ "doc":"application/msword",
+ "drw":"application/drafting",
+ "dvi":"application/x-dvi",
+ "dwg":"application/acad",
+ "dxf":"application/dxf",
+ "dxr":"application/x-director",
+ "etx":"text/x-setext",
+ "ez":"application/andrew-inset",
+ "fli":"video/x-fli",
+ "flv":"video/x-flv",
+ "gif":"image/gif",
+ "gl":"video/gl",
+ "gtar":"application/x-gtar",
+ "gz":"application/x-gzip",
+ "hdf":"application/x-hdf",
+ "hqx":"application/mac-binhex40",
+ "html":"text/html",
+ "ice":"x-conference/x-cooltalk",
+ "ief":"image/ief",
+ "igs":"model/iges",
+ "ips":"application/x-ipscript",
+ "ipx":"application/x-ipix",
+ "jad":"text/vnd.sun.j2me.app-descriptor",
+ "jar":"application/java-archive",
+ "jpeg":"image/jpeg",
+ "jpg":"image/jpeg",
+ "js":"text/javascript",
+ "json":"application/json",
+ "latex":"application/x-latex",
+ "lsp":"application/x-lisp",
+ "lzh":"application/octet-stream",
+ "m":"text/plain",
+ "m3u":"audio/x-mpegurl",
+ "man":"application/x-troff-man",
+ "me":"application/x-troff-me",
+ "midi":"audio/midi",
+ "mif":"application/x-mif",
+ "mime":"www/mime",
+ "movie":"video/x-sgi-movie",
+ "mp4":"video/mp4",
+ "mpg":"video/mpeg",
+ "mpga":"audio/mpeg",
+ "ms":"application/x-troff-ms",
+ "nc":"application/x-netcdf",
+ "oda":"application/oda",
+ "ogm":"application/ogg",
+ "pbm":"image/x-portable-bitmap",
+ "pdf":"application/pdf",
+ "pgm":"image/x-portable-graymap",
+ "pgn":"application/x-chess-pgn",
+ "pgp":"application/pgp",
+ "pm":"application/x-perl",
+ "png":"image/png",
+ "pnm":"image/x-portable-anymap",
+ "ppm":"image/x-portable-pixmap",
+ "ppz":"application/vnd.ms-powerpoint",
+ "pre":"application/x-freelance",
+ "prt":"application/pro_eng",
+ "ps":"application/postscript",
+ "qt":"video/quicktime",
+ "ra":"audio/x-realaudio",
+ "rar":"application/x-rar-compressed",
+ "ras":"image/x-cmu-raster",
+ "rgb":"image/x-rgb",
+ "rm":"audio/x-pn-realaudio",
+ "rpm":"audio/x-pn-realaudio-plugin",
+ "rtf":"text/rtf",
+ "rtx":"text/richtext",
+ "scm":"application/x-lotusscreencam",
+ "set":"application/set",
+ "sgml":"text/sgml",
+ "sh":"application/x-sh",
+ "shar":"application/x-shar",
+ "silo":"model/mesh",
+ "sit":"application/x-stuffit",
+ "skt":"application/x-koan",
+ "smil":"application/smil",
+ "snd":"audio/basic",
+ "sol":"application/solids",
+ "spl":"application/x-futuresplash",
+ "src":"application/x-wais-source",
+ "stl":"application/SLA",
+ "stp":"application/STEP",
+ "sv4cpio":"application/x-sv4cpio",
+ "sv4crc":"application/x-sv4crc",
+ "swf":"application/x-shockwave-flash",
+ "tar":"application/x-tar",
+ "tcl":"application/x-tcl",
+ "tex":"application/x-tex",
+ "texinfo":"application/x-texinfo",
+ "tgz":"application/x-tar-gz",
+ "tiff":"image/tiff",
+ "tr":"application/x-troff",
+ "tsi":"audio/TSP-audio",
+ "tsp":"application/dsptype",
+ "tsv":"text/tab-separated-values",
+ "unv":"application/i-deas",
+ "ustar":"application/x-ustar",
+ "vcd":"application/x-cdlink",
+ "vda":"application/vda",
+ "vivo":"video/vnd.vivo",
+ "vrm":"x-world/x-vrml",
+ "wav":"audio/x-wav",
+ "wax":"audio/x-ms-wax",
+ "wma":"audio/x-ms-wma",
+ "wmv":"video/x-ms-wmv",
+ "wmx":"video/x-ms-wmx",
+ "wrl":"model/vrml",
+ "wvx":"video/x-ms-wvx",
+ "xbm":"image/x-xbitmap",
+ "xlw":"application/vnd.ms-excel",
+ "xml":"text/xml",
+ "xpm":"image/x-xpixmap",
+ "xwd":"image/x-xwindowdump",
+ "xyz":"chemical/x-pdb",
+ "zip":"application/zip",
+};
+
+exports.charsets = {
+ 'text/javascript': 'UTF-8',
+ 'text/html': 'UTF-8',
+};

0 comments on commit aee35f1

Please sign in to comment.