diff --git a/MIT-LICENSE.md b/MIT-LICENSE.md
deleted file mode 100644
index 635fdff..0000000
--- a/MIT-LICENSE.md
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Sam Eubank
-
-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.
\ No newline at end of file
diff --git a/README.md b/README.md
index 5ee5393..ae2d097 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,25 @@
-# Za
+# Za
+[![NPM Version](https://img.shields.io/npm/v/za.svg)](https://npmjs.org/package/za)
+[![Downloads](https://img.shields.io/npm/dm/za.svg)](https://npmjs.org/package/za)
+[![Build Status](https://img.shields.io/travis/lighterio/za.svg)](https://travis-ci.org/lighterio/za)
+[![Code Coverage](https://img.shields.io/coveralls/lighterio/za/master.svg)](https://coveralls.io/r/lighterio/za)
+[![Dependencies](https://img.shields.io/david/lighterio/za.svg)](https://david-dm.org/lighterio/za)
+[![Support](https://img.shields.io/gratipay/Lighter.io.svg)](https://gratipay.com/Lighter.io/)
-[![NPM Version](https://badge.fury.io/js/za.png)](http://badge.fury.io/js/za)
-[![Build Status](https://travis-ci.org/lighterio/za.png?branch=master)](https://travis-ci.org/lighterio/za)
-[![Code Coverage](https://coveralls.io/repos/lighterio/za/badge.png?branch=master)](https://coveralls.io/r/lighterio/za)
-[![Dependencies](https://david-dm.org/lighterio/za.png?theme=shields.io)](https://david-dm.org/lighterio/za)
-[![Support](http://img.shields.io/gittip/zerious.png)](https://www.gittip.com/lighterio/)
+
+## TL;DR
Za is a simple web server that exposes an Express-like API and is capable of
handling far more requests per second than Express.
-## Getting Started
+### Quick Start
Install `za` in your project:
-
```bash
npm install --save za
```
Use `za` to start an HTTP server:
-
```javascript
var server = require('./za')();
server.listen(8888);
@@ -29,6 +30,7 @@ console.log('Visit http://localhost:8888/ for "Hello World!"');
```
+
## Server and Router
### za()
@@ -226,6 +228,96 @@ When the multipart parser is finished, the request emits a `"za:finished"`
event. At that time, `request.multipart` (as well as `request.body`) contain
all of the data that was sent in the multipart request.
+
## Why is it called "Za"?
+
The term "za" is short for "pizza", and when it comes to web app responses and
pizza, everyone wants fast delivery. (Also, the name was available on NPM).
+
+
+## Acknowledgements
+
+We would like to thank all of the amazing people who use, support,
+promote, enhance, document, patch, and submit comments & issues.
+Za couldn't exist without you.
+
+Additionally, huge thanks go to [TUNE](http://www.tune.com) for employing
+and supporting [Za](http://lighter.io/za) project maintainers,
+and for being an epically awesome place to work (and play).
+
+
+## MIT License
+
+Copyright (c) 2014 Sam Eubank
+
+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.
+
+
+## How to Contribute
+
+We welcome contributions from the community and are happy to have them.
+Please follow this guide when logging issues or making code changes.
+
+### Logging Issues
+
+All issues should be created using the
+[new issue form](https://github.com/lighterio/za/issues/new).
+Please describe the issue including steps to reproduce. Also, make sure
+to indicate the version that has the issue.
+
+### Changing Code
+
+Code changes are welcome and encouraged! Please follow our process:
+
+1. Fork the repository on GitHub.
+2. Fix the issue ensuring that your code follows the
+ [style guide](http://lighter.io/style-guide).
+3. Add tests for your new code, ensuring that you have 100% code coverage.
+ (If necessary, we can help you reach 100% prior to merging.)
+ * Run `npm test` to run tests quickly, without testing coverage.
+ * Run `npm run cover` to test coverage and generate a report.
+ * Run `npm run report` to open the coverage report you generated.
+4. [Pull requests](http://help.github.com/send-pull-requests/) should be made
+ to the [master branch](https://github.com/lighterio/za/tree/master).
+
+### Contributor Code of Conduct
+
+As contributors and maintainers of Za, we pledge to respect all
+people who contribute through reporting issues, posting feature requests,
+updating documentation, submitting pull requests or patches, and other
+activities.
+
+If any participant in this project has issues or takes exception with a
+contribution, they are obligated to provide constructive feedback and never
+resort to personal attacks, trolling, public or private harassment, insults, or
+other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, edits, issues, and other contributions
+that are not aligned with this Code of Conduct. Project maintainers who do
+not follow the Code of Conduct may be removed from the project team.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by opening an issue or contacting one or more of the project
+maintainers.
+
+We promise to extend courtesy and respect to everyone involved in this project
+regardless of gender, gender identity, sexual orientation, ability or
+disability, ethnicity, religion, age, location, native language, or level of
+experience.
diff --git a/lib/Request.js b/lib/Request.js
new file mode 100644
index 0000000..518f4e1
--- /dev/null
+++ b/lib/Request.js
@@ -0,0 +1,46 @@
+var http = require('http');
+var parse = require('./parse');
+
+var proto = http.IncomingMessage.prototype;
+
+proto.header = function (name) {
+ return this.headers[(name || '').toLowerCase()];
+};
+
+if (!proto.hasOwnProperty('cookies')) {
+ Object.defineProperty(proto, 'cookies', {
+ enumerable: false,
+ get: function () {
+ var cookies = {};
+ var cookie = this.headers.cookie;
+ if (cookie) {
+ cookie.split(/; ?/).forEach(function (pair) {
+ pair = pair.split('=');
+ cookies[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+ });
+ }
+ this.cookies = cookies;
+ return cookies;
+ }
+ });
+}
+
+if (!proto.hasOwnProperty('query')) {
+ Object.defineProperty(proto, 'query', {
+ enumerable: false,
+ get: function () {
+ var query = parse(this.queryString);
+ this.query = query;
+ return query;
+ }
+ });
+}
+
+if (!proto.hasOwnProperty('body')) {
+ Object.defineProperty(proto, 'body', {
+ enumerable: false,
+ get: function () {
+ return this.multipart || parse(this.buffer);
+ }
+ });
+}
diff --git a/lib/http.js b/lib/Response.js
similarity index 50%
rename from lib/http.js
rename to lib/Response.js
index 451c34c..4434d17 100644
--- a/lib/http.js
+++ b/lib/Response.js
@@ -1,21 +1,18 @@
var http = require('http');
var escape = require('querystring').escape;
-var parse = require('./parse');
-
// TODO: Benchmark zlib performance against node-compress.
var zlib = require('zlib');
-var requestProto = http.IncomingMessage.prototype;
-var responseProto = http.ServerResponse.prototype;
+var proto = http.ServerResponse.prototype;
-responseProto.json = function (object) {
+proto.json = function (object) {
var json = JSON.stringify(object);
var res = this;
res.setHeader('content-type', 'application/json');
res.send(json);
};
-responseProto.send = function (data) {
+proto.send = function (data) {
var res = this;
var type = typeof data;
if (type == 'string') {
@@ -33,7 +30,7 @@ responseProto.send = function (data) {
}
};
-responseProto.zip = function (text, preZipped) {
+proto.zip = function (text, preZipped) {
// TODO: Determine whether 1e3 is the right threshold.
var res = this;
if (preZipped || (text.length > 1e3)) {
@@ -46,6 +43,7 @@ responseProto.zip = function (text, preZipped) {
else {
zlib.gzip(text, function (err, zipped) {
if (err) {
+ console.log(err);
res.end(text);
}
else {
@@ -60,56 +58,15 @@ responseProto.zip = function (text, preZipped) {
res.end(text);
};
-responseProto.cookie = function (name, value, options) {
+proto.cookie = function (name, value, options) {
var res = this;
res.setHeader('set-cookie', name + '=' + escape(value));
};
-responseProto.redirect =
-responseProto.redirect || function (location) {
+proto.redirect =
+proto.redirect || function (location) {
var res = this;
res.statusCode = 302;
res.setHeader('location', location);
res.end();
};
-
-requestProto.header = function (name) {
- return this.headers[(name || '').toLowerCase()];
-};
-
-if (!requestProto.hasOwnProperty('cookies')) {
- Object.defineProperty(requestProto, 'cookies', {
- get: function () {
- var cookies = {};
- var cookie = this.headers.cookie;
- if (cookie) {
- cookie.split(/; ?/).forEach(function (pair) {
- pair = pair.split('=');
- cookies[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
- });
- }
- this.cookies = cookies;
- return cookies;
- }
- });
-}
-
-if (!requestProto.hasOwnProperty('query')) {
- Object.defineProperty(requestProto, 'query', {
- enumerable: false,
- get: function () {
- var query = parse(this.queryString);
- this.query = query;
- return query;
- }
- });
-}
-
-if (!requestProto.hasOwnProperty('body')) {
- Object.defineProperty(requestProto, 'body', {
- enumerable: false,
- get: function () {
- return this.multipart || parse(this.buffer);
- }
- });
-}
diff --git a/lib/Router.js b/lib/Router.js
index e92119f..557ef6d 100644
--- a/lib/Router.js
+++ b/lib/Router.js
@@ -1,9 +1,24 @@
var multipart = require('./multipart');
+var doNothing = function () {};
-var paths;
-var patterns;
+// Maximum number of bytes we can receive (to avoid storage attacks).
+var MAX_BYTES = 1e8; // ~100MB.
-var MAX_BYTES = 1e8; // ~100MB
+/**
+ * Turn a string into a RegExp pattern if it has asterisks.
+ */
+function patternify(str, start, end) {
+ if (typeof str == 'string') {
+ str = str.toLowerCase();
+ if (str.indexOf('*') > -1) {
+ str = str.replace(/\*/g, '@');
+ str = str.replace(/([^\d\w_-])/gi, '\\$1');
+ str = str.replace(/\\@/g, '.*');
+ return new RegExp(start + str + end, 'i');
+ }
+ }
+ return str;
+}
/**
* A Lighter Router sets up HTTP routing.
@@ -13,20 +28,8 @@ module.exports = function Router(protocol) {
var app;
- var paths = this.paths = {
- GET: {},
- PUT: {},
- POST: {},
- DELETE: {}
- };
-
- // TODO: Support routing patterns with wildcards.
- var patterns = this.patterns = {
- GET: {},
- PUT: {},
- POST: {},
- DELETE: {}
- };
+ // Paths are keys/items on/in the routes array.
+ var routes = this.routes = {};
// Middlewares are functions that chain asynchronously.
var middlewares = this.middlewares = [];
@@ -43,36 +46,52 @@ module.exports = function Router(protocol) {
this.server = server;
};
+ /**
+ * Add a function to handle a specific HTTP method and URL path.
+ */
this.add = function add(method, path, fn) {
- path = path.toLowerCase();
- fn._ZA_MAX_BYTES = fn._ZA_MAX_BYTES || MAX_BYTES;
- if (path.indexOf('*') > -1) {
- throw "Za routing does not yet support wildcards in paths. Cannot route " + path + ".";
- }
- else {
- paths[method][path] = fn;
+ method = method.toUpperCase();
+ path = patternify(path, '^', '$');
+ var map = routes[method] = routes[method] || [];
+
+ // Each route ultimately maps to a function.
+ map[path] = fn;
+
+ // In addition to being a map, the map is an array of RegExp patterns.
+ if (path instanceof RegExp) {
+ map.push(path);
}
};
+ /**
+ * Add a middleware, with an optional path.
+ */
this.use = function use(path, fn) {
var middleware;
if (typeof path == 'function') {
middleware = path;
}
else {
- var length = path.length;
- middleware = function (request, response, next) {
- if (request.url.substr(0, length) == path) {
- fn.call(app, request, response, next);
- }
- else {
- next();
- }
- };
+ path = patternify(path, '^', '');
+ if (path instanceof RegExp) {
+ middleware = function (request) {
+ var isMatch = path.test(request.url);
+ return isMatch ? fn.apply(app, arguments) : true;
+ };
+ }
+ else {
+ middleware = function (request) {
+ var isMatch = (request.url.indexOf(path) === 0);
+ return isMatch ? fn.apply(app, arguments) : true;
+ };
+ }
}
middlewares.push(middleware);
};
+ /**
+ * Process a request and write to the response.
+ */
this.serve = function (request, response) {
response.app = app;
response.request = request;
@@ -86,33 +105,64 @@ module.exports = function Router(protocol) {
url = url.toLowerCase();
var m = middlewares;
- var n = m.length;
- var i = -1;
+ var i = 0;
+ /**
+ * Iterate over middlewares that return truthy values.
+ * Wait for falsy-returning middlewares to call `next`.
+ */
function next() {
- if (++i < n) {
- m[i].call(app, request, response, next);
- }
- else {
- finish();
- }
+ var ok, fn;
+ do {
+ fn = m[i++];
+ if (fn) {
+ ok = fn.call(app, request, response, next);
+ }
+ else {
+ return finish();
+ }
+ } while (ok);
}
+ /**
+ * Finish the request.
+ */
function finish() {
- var fn = paths[request.method][url];
+ var method = request.method;
+
+ // TODO: Add automagic support for CONNECT/OPTIONS/TRACE?
+ if (method == 'HEAD') {
+ method = 'GET';
+ response.write = doNothing;
+ }
+ var map = routes[method] || routes.GET;
+ var fn = map[url];
+
+ // If the path didn't map to a route, iterate over wildcard routes.
+ if (!fn) {
+ for (var i = 0, l = map.length; i < l; i++) {
+ var p = map[i];
+ if (p.test(url)) {
+ fn = map[p];
+ break;
+ }
+ }
+ }
+
if (fn) {
- if (request.method[0] == 'P') {
+ if (method[0] == 'P') {
+ var maxBytes = fn._MAX_BYTES || MAX_BYTES;
if (/multipart/.test(request.headers['content-type'])) {
request.multipart = {};
fn(request, response);
- multipart(request, response, fn._ZA_MAX_BYTES);
+ multipart(request, response, maxBytes);
}
else {
var buffer;
buffer = '';
request.on('data', function (data) {
buffer += data;
- if (buffer.length > fn._ZA_MAX_BYTES) {
+ if (buffer.length > maxBytes) {
request.connection.destroy();
}
});
@@ -126,8 +176,8 @@ module.exports = function Router(protocol) {
fn(request, response);
}
}
- else if (response.error404) {
- response.error404();
+ else if (response.error) {
+ response.error(404);
}
else {
response.statusCode = 404;
@@ -138,4 +188,4 @@ module.exports = function Router(protocol) {
next();
};
-};
+};
\ No newline at end of file
diff --git a/lib/Server.js b/lib/Server.js
index 8af5066..43a8c12 100644
--- a/lib/Server.js
+++ b/lib/Server.js
@@ -1,134 +1,161 @@
var Router = require('./Router');
-var protocols = ['http', 'https'];
var http = require('http');
var https = require('https');
+var protocols = ['http', 'https'];
+var defaultPort = 8080;
/**
- * The Za server is an http/https web server.
+ * A Za Server is a fast, simple HTTP + HTTPS server.
*/
-module.exports = function Server() {
+var Server = module.exports = function (config) {
+ var self = this;
- // Za can stand alone if it needs to.
- this.app = this;
-
- // Or it can be part of a Lighter app.
- this.setApp = function setApp(app) {
- this.app = app;
- for (var key in this.routers) {
- this.routers[key].setApp(app);
- }
- };
+ // Za can have a parent `app`, but it defaults to being its own app.
+ self.app = this;
- this.routers = {
- http: new Router('http'),
- https: new Router('https')
- };
+ // The server does not take requests until the `listen` method is called.
+ self.isListening = false;
- this._addRoute = function _addRoute(method, path, fn, protocol) {
- var self = this;
- var isRouted = false;
- protocols.forEach(function (p) {
- if (!protocol || (protocol == p)) {
- if (self.routers[p]) {
- isRouted = true;
- self.routers[p].add(method, path, fn);
+ // The `routers` method returns an array, optionally filtered by protocol.
+ var routers = self.routers = function (protocol) {
+ if (self.isListening) {
+ var list = [];
+ protocols.forEach(function (p) {
+ if ((!protocol || (protocol == p)) && routers[p]) {
+ list.push(routers[p]);
}
- }
- });
- if (!isRouted) {
- throw "Could not route " + method + " to " + path + " over " + (protocol || protocols.join(' or ')) + ".";
+ });
+ return list;
+ }
+ else {
+ return protocol ? [routers[protocol]] : routers.list;
}
};
- this.get = function get(path, fn, protocol) {
- this._addRoute('GET', path, fn, protocol);
- return this;
- };
+ // The `routers` property is also a map.
+ routers.http = new Router('http');
+ routers.https = new Router('https');
+ routers.list = [routers.http, routers.https];
+
+ // If configuration is passed in, start listening.
+ if (config) {
+ self.listen(config);
+ }
- this.put = function get(path, fn, protocol) {
- this._addRoute('PUT', path, fn, protocol);
+};
+
+/**
+ * Za servers share chainable methods.
+ */
+var proto = Server.prototype = {
+
+ /**
+ * Inject a parent app (such as Lighter MVC).
+ */
+ setApp: function (app) {
+ this.app = app;
+ this.routers().forEach(function (router) {
+ router.setApp(app);
+ });
return this;
- };
+ },
- this.post = function get(path, fn, protocol) {
- this._addRoute('POST', path, fn, protocol);
+ /**
+ * Set an HTTP method to route a path to a function.
+ */
+ route: function (method, path, fn, protocol) {
+ var routers = this.routers(protocol);
+ this.routers(protocol).forEach(function (router) {
+ router.add(method, path, fn);
+ });
return this;
- };
+ },
- this.delete = function get(path, fn, protocol) {
- this._addRoute('DELETE', path, fn, protocol);
+ /**
+ * Set a middleware function to be used, optionally on a given path.
+ */
+ use: function (path, fn, protocol) {
+ this.routers(protocol).forEach(function (router) {
+ router.use(path, fn);
+ });
return this;
- };
+ },
- this.use = function use(path, middleware, protocol) {
+ /**
+ * Listen for HTTP/HTTPS requests based on a `config` object.
+ * For example:
+ * ```json
+ * {
+ * http: 80,
+ * https: 443, // Optional.
+ * key: '/private/key.pem', // Optional unless `https` is set.
+ * cert: 'private/cert.pem' // Optional unless `https` is set.
+ * }
+ * ```
+ */
+ listen: function (config) {
var self = this;
- var isUsing = false;
- protocols.forEach(function (p) {
- if (!protocol || (protocol == p)) {
- if (self.routers[p]) {
- self.routers[p].use(path, middleware);
- isUsing = true;
- }
+ var args = arguments;
+ config = config || 0;
+ var ssl = {
+ key: config.key ? fs.readFileSync(config.key) : 0,
+ cert: config.cert ? fs.readFileSync(config.cert) : 0
+ };
+ var routers = self.routers;
+ routers.list = [];
+ protocols.forEach(function (protocol, i) {
+ // Support `listen(httpPort, httpsPort, httpsOptions)` for backward compatibility.
+ var port = config[protocol] || args[i] || (i ? 0 : defaultPort++);
+ if (port) {
+ var router = routers[protocol] || new Router(protocol);
+ var lib = require(protocol);
+ var server = i ?
+ lib.createServer(ssl, router.serve) :
+ lib.createServer(router.serve);
+ routers[protocol] = router;
+ router.setServer(server);
+ server.listen(port);
+ self.isListening = true;
+ routers.list.push(router);
+ }
+ else {
+ delete routers[protocol];
}
});
- if (!isUsing) {
- throw "Could not use middleware over " + (protocol || protocols.join(' or ')) + ".";
- }
- };
-
- this.listen = function listen(httpPort, httpsPort, httpsOptions) {
- var self = this;
- var router, server;
- if (httpPort) {
- router = self.routers.http;
- router.setPort(httpPort);
- server = http.createServer(router.serve);
- router.setServer(server);
- server.listen(httpPort);
- }
- else {
- delete self.routers.http;
- }
- if (httpsPort) {
- router = self.routers.https;
- router.setPort(httpsPort);
- server = https.createServer(httpsOptions, router.serve);
- router.setServer(server);
- server.listen(httpsPort);
- }
- else {
- delete self.routers.https;
- }
return self;
- };
+ },
- this.close = function close() {
+ /**
+ * Shut the server down by stopping its routers' servers.
+ */
+ close: function () {
var self = this;
- protocols.forEach(function (protocol) {
- var router = self.routers[protocol];
- if (router) {
+ if (self.isListening) {
+ self.routers().forEach(function (router) {
router.server.close();
- }
- });
+ });
+ }
+ self.isListening = false;
return self;
- };
+ },
/**
- * returns requestListener compatible interface -> function (req, res)
- * which can be used to create server using node http or https require
- *
- * It is useful to have an easy interface to get request listener without knowing za's inside
- * It is also useful to setup a testing environment using supertest
- * @see https://github.com/visionmedia/supertest
+ * Get a requestListener-compatible interface.
+ * @see https://github.com/visionmedia/supertest
* @see http://nodejs.org/api/http.html#http_http_createserver_requestlistener
- *
- * @param {[type]} protocol za server contains routers for multiple protocol. specify protocol to choose specific za router to be returned as requestListener
- * @return {[requestListener]} interface function (request, response)
*/
- this.requestListener = function requestListener(protocol) {
- protocol = protocol || 'http'; // default protocol
- var router = this.routers[protocol];
- return router.serve.bind(this);
- };
+ requestListener: function (protocol) {
+ var self = this;
+ return self.routers(protocol)[0].serve.bind(self);
+ }
};
+
+// Create HTTP method properties so that (e.g.) `app.get('/', fn)` can be called.
+['CONNECT', 'DELETE', 'GET', 'HEAD',
+ 'OPTIONS', 'POST', 'PUT', 'TRACE'].forEach(function (method) {
+ var key = method.toLowerCase();
+ proto[key] = function (path, fn, protocol) {
+ return this.route(method, path, fn, protocol);
+ };
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index e38de64..00b4273 100644
--- a/package.json
+++ b/package.json
@@ -1,49 +1,35 @@
{
"name": "za",
+ "version": "0.1.0",
"description": "A fast Node.js web server",
+ "dependencies": {},
+ "devDependencies": {
+ "coveralls": "2.10.0",
+ "istanbul": "0.2.11",
+ "qs": "0.6.6",
+ "supertest": "0.13.0",
+ "zeriousify": "^0.1.10",
+ "exam": "^0.1.2"
+ },
"keywords": [
"za"
],
- "version": "0.0.21",
"main": "za.js",
- "homepage": "http://github.com/lighterio/za",
- "repository": "http://github.com/lighterio/za.git",
- "bugs": {
- "url": "http://github.com/lighterio/za/issues"
- },
- "author": {
- "name": "Sam Eubank",
- "email": "sameubank@gmail.com"
+ "engines": {
+ "node": ">=0.2.6"
},
- "contributors": [
- {
- "name": "Sam Eubank",
- "email": "sameubank@gmail.com"
- }
- ],
- "licenses": [
- {
- "type": "MIT",
- "url": "http://github.com/lighterio/za/blob/master/MIT-LICENSE.md"
- }
- ],
- "engines": [
- "node >= 0.2.6"
- ],
"scripts": {
"test": "./node_modules/exam/exam.js",
- "retest": "./node_modules/exam/exam.js --watch",
"cover": "istanbul cover ./node_modules/exam/exam.js",
"report": "open coverage/lcov-report/index.html",
- "coveralls": "istanbul cover ./node_modules/exam/exam.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
+ "coveralls": "istanbul cover ./node_modules/exam/exam.js && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
},
- "dependencies": {},
- "devDependencies": {
- "coveralls": "2.10.0",
- "istanbul": "0.2.11",
- "qs": "0.6.6",
- "supertest": "0.13.0",
- "zeriousify": "0.1.7",
- "exam": "0.0.7"
- }
+ "repository": "https://github.com/lighterio/za.git",
+ "bugs": "https://github.com/lighterio/za/issues",
+ "homepage": "http://github.com/lighterio/za",
+ "author": "Sam Eubank ",
+ "contributors": [
+ "Sam Eubank "
+ ],
+ "license": "MIT"
}
diff --git a/test/.exam.js b/test/.exam.js
new file mode 100644
index 0000000..9d3d012
--- /dev/null
+++ b/test/.exam.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ignore: /^(fixtures)$/
+};
\ No newline at end of file
diff --git a/test/.examignore b/test/.examignore
deleted file mode 100644
index 116caa1..0000000
--- a/test/.examignore
+++ /dev/null
@@ -1 +0,0 @@
-fixtures
diff --git a/test/fixtures/http-test-server.js b/test/fixtures/http-test-server.js
index 10afc4d..8702b76 100644
--- a/test/fixtures/http-test-server.js
+++ b/test/fixtures/http-test-server.js
@@ -1,67 +1,66 @@
-'use strict';
-var za = require('../../za');
-var responses = require('./response-fixtures');
-var bodies = require('./body-fixtures');
-var codes = [200, 401, 500];
-var jsonBody = responses.shortJson;
-var longJsonBody = responses.longJson;
-var unzipped = responses.longString;
-var shortString = responses.shortString;
+var za = require('../../za');
+var responses = require('./response-fixtures');
+var bodies = require('./body-fixtures');
+var codes = [200, 401, 500];
+var jsonBody = responses.shortJson;
+var longJsonBody = responses.longJson;
+var unzipped = responses.longString;
+var shortString = responses.shortString;
var server = za();
while (unzipped.length < 2e3) {
- unzipped += 'a';
+ unzipped += 'a';
}
var i = 0;
while (i++ < 1e3) {
- var key = 'key' + i;
- longJsonBody[key] = 'value' + i;
+ var key = 'key' + i;
+ longJsonBody[key] = 'value' + i;
}
server.get('/zip', function (req, res) {
- res.zip(unzipped);
+ res.zip(unzipped);
});
server.get('/unzipped', function (req, res) {
- res.zip(shortString);
+ res.zip(shortString);
});
server.get('/json', function (req, res) {
- res.json(jsonBody);
+ res.json(jsonBody);
});
server.get('/jsonzip', function (req, res) {
- res.json(longJsonBody);
+ res.json(longJsonBody);
});
server.get('/send/400', function (req, res) {
- res.send(400);
+ res.send(400);
});
// making send end with all status code
codes.forEach(function (code) {
- server.get('/send/' + code, function (req, res) {
- res.send(parseInt(code));
- });
+ server.get('/send/' + code, function (req, res) {
+ res.send(parseInt(code));
+ });
});
server.get('/send/json', function (req, res) {
- res.send(jsonBody);
+ res.send(jsonBody);
});
server.get('/header', function (req, res) {
- res.send(req.header('content-type'));
+ res.send(req.header('content-type'));
});
server.get('/cookie', function (req, res) {
- res.cookie('coo', 'kie');
- res.send(200);
+ res.cookie('coo', 'kie');
+ res.send(200);
});
server.get('/redirect', function (req, res) {
- res.redirect('http://google.com');
+ res.redirect('http://google.com');
});
module.exports = server;
diff --git a/test/fixtures/router-test-server.js b/test/fixtures/router-test-server.js
index d4550ec..88a21fc 100644
--- a/test/fixtures/router-test-server.js
+++ b/test/fixtures/router-test-server.js
@@ -8,6 +8,15 @@ server.get('/', function (req, res) {
res.json(responses.shortJson);
});
+server.get('/wild/*', function (req, res) {
+ var wild = req.url.replace(/^\/wild\//, '');
+ res.json({wild: wild});
+});
+
+server.get('/*/profile', function (req, res) {
+ res.json({profile: req.url.split('/')[1]});
+});
+
server.post('/json', function (req, res) {
res.json(req.body);
});
@@ -30,6 +39,30 @@ server.use('/middleware', function (req, res, next) {
next();
});
+
+server.use(/sync/, function (req, res) {
+ req.middle = [1];
+ return true;
+});
+
+server.use(/async/, function (req, res, next) {
+ req.middle.push(2);
+ next();
+});
+
+server.use('/sync', function (req, res) {
+ return req.middle.push(3);
+});
+
+server.use('/sync/*/ok', function (req, res) {
+ return req.middle.push(4);
+});
+
+server.get(/sync/, function (req, res) {
+ res.json(req.middle);
+});
+
+
server.get('/query', function (req, res) {
var q = req.query;
q.hello = q.hello;
diff --git a/test/httpTest.js b/test/httpTest.js
index b5f617a..9a3fd29 100644
--- a/test/httpTest.js
+++ b/test/httpTest.js
@@ -7,6 +7,13 @@ var codes = server.codes;
describe('http', function () {
+ it('supports HEAD', function (done) {
+ request(server.requestListener()).head('/send/json')
+ .expect(200)
+ .expect('')
+ .end(done);
+ });
+
describe('zip', function () {
it('returns zipped content', function (done) {
request(server.requestListener()).get('/zip')
diff --git a/test/routerTest.js b/test/routerTest.js
index 212594f..67e1c67 100644
--- a/test/routerTest.js
+++ b/test/routerTest.js
@@ -4,11 +4,41 @@ var server = require('./fixtures/router-test-server');
var bodies = require('./fixtures/body-fixtures');
var responses = require('./fixtures/response-fixtures');
-describe('Server', function () {
- it('should execute middleware', function (done) {
- request(server.requestListener()).get('/middleware')
- .expect(200)
- .expect(JSON.stringify([1,2]), done);
+describe('Router', function () {
+
+ describe('middlewares', function () {
+ it('execute in order', function (done) {
+ request(server.requestListener())
+ .get('/middleware')
+ .send()
+ .expect(200)
+ .expect(JSON.stringify([1,2]))
+ .end(done);
+ });
+ it('support async mode', function (done) {
+ request(server.requestListener())
+ .get('/async')
+ .send()
+ .expect(200)
+ .expect(JSON.stringify([1,2]))
+ .end(done);
+ });
+ it('support sync mode', function (done) {
+ request(server.requestListener())
+ .get('/sync')
+ .send()
+ .expect(200)
+ .expect(JSON.stringify([1,3]))
+ .end(done);
+ });
+ it('support wildcards', function (done) {
+ request(server.requestListener())
+ .get('/sync/me/ok')
+ .send()
+ .expect(200)
+ .expect(JSON.stringify([1,3,4]))
+ .end(done);
+ });
});
describe('@requestListener', function () {
@@ -19,13 +49,15 @@ describe('Server', function () {
});
});
- describe('router', function () {
- it('should return 404 response', function (done) {
+ describe('.serve', function () {
+
+ it('should return a 404 if the route is not found', function (done) {
request(server.requestListener()).get('/notfound')
.expect(404)
.end(done);
});
- it('should parse query param', function (done) {
+
+ it('should parse query params', function (done) {
var queryObj = {
hello: '12',
world: 'too',
@@ -36,6 +68,7 @@ describe('Server', function () {
.expect(JSON.stringify(queryObj))
.end(done);
});
+
});
describe('body parser', function () {
@@ -53,4 +86,22 @@ describe('Server', function () {
.end(done);
});
});
+
+ describe('wildcards', function () {
+ it('match anything', function (done) {
+ request(server.requestListener())
+ .get('/wild/hello')
+ .send()
+ .expect('{"wild":"hello"}')
+ .end(done);
+ });
+ it('can appear in the middle', function (done) {
+ request(server.requestListener())
+ .get('/something/profile')
+ .send()
+ .expect('{"profile":"something"}')
+ .end(done);
+ });
+ });
+
});
diff --git a/za.js b/za.js
index 5e8e11c..3b61306 100644
--- a/za.js
+++ b/za.js
@@ -1,12 +1,13 @@
-// Decorate http objects.
-require('./lib/http');
+// Decorate Request and Response prototypes.
+require('./lib/Request');
+require('./lib/Response');
// Allow us to instantiate HTTP and HTTPS servers.
var Server = require('./lib/Server');
// The API is a function which returns a server.
var za = module.exports = function () {
- return new Server();
+ return new Server();
};
// Expose the version number, but only load package JSON if a get is performed.