├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGES.md
├── Jakefile
├── README.md
├── benchmarks
├── alternative-routing
│ ├── alt-router.js
│ └── app.js
├── hello-world
│ └── hello-world.js
├── mustache-layout
│ └── app.js
├── route-generator.js
├── run
└── views
│ ├── index.html
│ └── layout.html
├── cli
└── templates
│ ├── app.js.template
│ └── package.json.template
├── docs
├── app.md
├── bogart.md
├── index.md
├── routing.md
└── view-engine.md
├── engines
└── node
│ └── lib
│ └── fileWatcher.js
├── examples
├── body-adapter.js
├── config.js
├── cors.js
├── facebook-authentication
│ ├── app.js
│ └── public
│ │ ├── favicon.ico
│ │ └── login-with-facebook.png
├── flash-test.js
├── gzip-response.js
├── hello-world.js
├── multipart-form
│ └── app.js
├── mustache-layout
│ ├── app.js
│ ├── package.json
│ └── views
│ │ ├── index.html
│ │ └── layout.html
├── pipe-file.js
├── proxy.js
├── session.js
├── share-javascript
│ ├── app.js
│ └── views
│ │ └── index.html
├── static-server
│ ├── app.js
│ ├── package.json
│ └── public
│ │ └── images
│ │ └── ninja-cat.jpg
└── task-list
│ ├── app.js
│ └── templates
│ ├── index.html
│ └── layout.html
├── lib
├── bogart.js
├── flash
│ ├── flashCookieDataProvider.js
│ └── flashIdCookieProvider.js
├── forEachStream.js
├── forEachable.js
├── fsp.js
├── middleware.js
├── middleware
│ ├── bodyAdapter.js
│ ├── cascade.js
│ ├── directory.js
│ ├── error.js
│ ├── facebook.js
│ ├── flash.js
│ ├── google.js
│ ├── gzip.js
│ ├── methodOverride.js
│ ├── oauth.js
│ ├── oauth2.js
│ ├── parseForm.js
│ ├── parseJson.js
│ ├── parted.js
│ ├── renderView.js
│ ├── session
│ │ ├── cookieDataProvider.js
│ │ ├── idProvider.js
│ │ └── session.js
│ ├── stringReturnAdapter.js
│ ├── twitter.js
│ └── validateResponse.js
├── mimetypes.js
├── q.js
├── request.js
├── router.js
├── security.js
├── stream.js
├── util.js
└── view.js
├── package.json
└── spec
├── appSpec.coffee
├── fixtures
├── chrome.part
├── greeting.mustache
├── index.mustache
├── partial-test.mustache
├── test.jpg
└── text.txt
├── helpers
└── JsgiRequestHelper.coffee
├── middleware
├── facebookSpec.coffee
└── session
│ └── cookieDataProviderSpec.coffee
├── middlewareSpec.coffee
├── requestSpec.coffee
├── responseBuilderSpec.coffee
├── responseHelperSpec.coffee
├── routerSpec.coffee
├── streamSpec.coffee
├── utilSpec.coffee
└── viewSpec.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | downloaded-modules/
3 | results/
4 | hello-world.txt
5 | *.dat
6 | forEachableToFileStream.txt
7 | *.sublime-project
8 | *.sublime-workspace
9 | lib-cov
10 | node_modules
11 |
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.sublime-*
2 | forEachableStream.txt
3 | npm-debug.log
4 | hello-world.dat
5 | hello-world.txt
6 | examples/
7 | _site/
8 | spec/
9 |
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## v0.5.9
2 |
3 | * Ignores `examples` directory in npm releases.
4 |
5 | ## v0.5.8
6 |
7 | * Uses default `directory` middleware configuration if `config` is provided but no
8 | no value for `directory` is present in call to `bogart.batteries`.
9 |
10 | ## v0.5.7
11 |
12 | * Updates Q dependency to version 0.9.6. This version of Q has many performance enhancements.
13 |
14 | ## v0.5.6
15 |
16 | * Updates static server example to use `bogart.app`.
17 | * Switches Travis configuration to only run tests in node v0.10.x.
18 | * Updates stream specs for node v0.10.x style streams.
19 | * Fixes a bug with `bogart.pump` resolving its promise at wrong time.
20 |
21 | ## v0.5.5
22 |
23 | * Falls back to `process.nextTick` when setImmediate is unavailable in `ForEachStream`.
24 | * Resolves `ResponseBuilder` promise for the response on first call to `send` to support streaming.
25 | * Add node v0.10.x to Travis configuration.
26 |
27 | ## v0.5.4
28 |
29 | * Changes ForEachStream to use `setImmediate` instead of `process.nextTick`.
30 |
31 | ## v0.5.3
32 |
33 | * Fixes bug preventing passing of options to `bogart.batteries`.
34 |
35 | ## v0.5.2
36 |
37 | * Updates examples to use `bogart.app`.
38 | * Normalizes the return value of `bogart.router` to match other middleware.
39 | * Changes `bogart.error` middleware to match middleware conventions.
40 | * Updates `bogart.session` example to use a custom configuration key.
41 | * Allows configuration of session's `SessionIdProvider` and `SessionDataProvider`
42 | encryption key from the options parameter to `bogart.session`.
43 | * Removes `bogart.build`.
44 | * Removes `binary` middleware.
45 | * Removes `cascade` middleware.
46 |
47 | ## v0.5.1
48 |
49 | * `SessionDataProvider.loadSession` now returns the session.
50 |
51 | ## v0.5.0
52 |
53 | * Changes order of parameters to `bogart.parted` middleware to match middleware conventions.
54 | * Creates a helper module called `fsp` where node.js `fs` methods are adapted to return promises.
55 | * General housekeeping on the middleware code base to make it more maintainable.
56 | * Changes `bogart.middelware` to be a function that makes creating JSGI middleware easier.
57 | All bogart middleware is still attached to the `bogart.middleware` function as properties.
58 | * Removes the deprecated support for passing a configuration function to `bogart.router`.
59 |
60 | ## v0.3.38
61 |
62 | * Changed `bogart.response()` to `bogart.res()` to better fit design philosophy and Node conventions.
63 |
64 | ## v0.3.37
65 |
66 | * Changed signature of view engine renderer function so that the caching mechanism is readily available for view engine implementations.
67 | * ResponseBuilder helper, `bogart.response()`, can now be piped to successfully.
68 |
69 | ## v0.3.36
70 |
71 | * Publishing v0.3.35 from windows seemed to cause issues when installing on unix. Therefore; I am republishing with only a version bump from linux.
72 |
73 | ## v0.3.35
74 |
75 | * Back out change to package.json to add new CLI.
76 |
77 | ## v0.3.34
78 |
79 | * Removed dependency on Q library.
80 |
81 | ## v0.3.33
82 |
83 | * Changed the unit tests in the project from Expresso to Tap.
84 |
85 | ## v0.3.32
86 |
87 | * Did not update package.json in my tag of 0.3.31, went to 0.3.32 for consistency.
88 |
89 | ## v0.3.31
90 |
91 | * Fixed a bug where Parted middleware was not bubbling rejections.
92 |
93 | ## v0.3.30
94 |
95 | * Fixed a bug causing before callbacks to cause errors.
96 |
97 | ## v0.3.29
98 |
99 | * Missed a whenCall, same issue from v0.3.28.
100 |
101 | ## v0.3.28
102 |
103 | * Added dependency on request 2.2.9.
104 | * Removed dependency on Deflate as Node.JS 0.6.x includes zlib.
105 | * Added gzip middleware to `bogart.batteries`
106 | * Added reject callbacks for all cases where whenCall is invoked as it tries to invoke the rejectCallback even if one is not provided.
107 |
108 | ## v0.3.27
109 |
110 | * `bogart.middleware.session` assumed that `req.env` would be unique per request; however, it is not. Corrected issues caused by this.
111 |
112 | ## v0.3.26
113 |
114 | * `bogart.middleware.bodyAdapter` now adapts responses that are of type Buffer or Stream to JSGI responses.
115 | * Fixed a bug in `bogart.middleware.bodyAdatper` where Stream returns were not being handled properly.
116 | * Added `bogart.config`. The default environment is 'development' and may be overridden with the BOGART_ENV environment variable.
117 | * Expose `DefaultIdProvider` and `DefaultDataProvider` as properties of `bogart.middleware.session`.
118 |
119 | ## v0.3.25
120 |
121 | * No longer reject a return that does not include all properties of a valid JSGI response.
122 | This change facilitates Bogart as a middleware platform.
123 | * Added `bogart.middleware.batteries`, a batteries included JSGI stack for rapid application development.
124 | * The deprecated `bogart.app` has been reclaimed for the purpose of creating application stacks more easily than chaining
125 | JSGI middleware manually or using `bogart.build`.
126 | * `bogart.build` is deprecated.
127 | * Added `bogart.q` which exposes the promise implementation used by Bogart.
128 | * Added `bogart.promisify` which adapts node-style asynchronous functions to promises.
129 | * Added `bogart.proxy`, a helper to create a JSGI response that proxies a URL.
130 | * Added `viewEngine.share`, a helper for serializing JavaScript to views. See the new example in 'examples/share-javascript'.
131 |
132 | ## v0.3.24
133 |
134 | * Bug fixes in Session middleware.
135 |
136 | ## v0.3.23
137 |
138 | * Added support for string-based paths with * for splat like /foo/*
139 | * Before callbacks may now return promises that must be resolved before the route handler is executed.
140 | * After callbacks may now return promises that must be resolved before the response from the route handler is returned.
141 |
142 | ## v0.3.22
143 |
144 | * `ViewEngine` is now an `EventEmitter`.
145 | * the built-in `Mustache` view engine now emits `beforeRender` and `afterRender` events.
146 | * Fixed bugs in `Flash` middleware.
147 |
148 | ## v0.3.21
149 |
150 | * `after` had been left off of the public API of router, added it.
151 |
152 | ## v0.3.20
153 |
154 | * Simplified and corrected code for `pipe` method on the request object for the `Parted` middleware.
155 | * Added `Session` middleware.
156 |
157 | ## v0.3.19
158 |
159 | * Updated Parted dependency to 0.8.0.
160 |
161 | ## v0.3.18
162 |
163 | * Mustache partials now work properly when using layouts.
164 |
165 | ## v0.3.17
166 |
167 | * Routes now match in order added instead of longest-first.
168 | * Added `Flash` middleware to emulate the flash method of Rails.
169 |
170 | ## v0.3.16
171 |
172 | * Added `Error` middleware to translate rejected promises and thrown errors into an error response.
173 | * `Error` middleware is included by default in JSGI stacks constructed with `bogart.build`.
174 | * `ParseForm` and `ParseJson` have been replaced with `Parted` in JSGI middleware stacks constructed with `bogart.build`.
175 |
176 | ## v0.3.15
177 |
178 | * Added `Parted` middleware to take advantage of the excellent streaming parsers provided by [Parted](https://github.com/chjj/parted).
179 | * Added multipart-form example to the examples directory to demonstrate usage of the Parted middleware.
180 |
181 | ## v0.3.14
182 |
183 | * use Buffer.byteLength to determine the value for `Content-Length` headers, resolves Issue #11
184 |
185 | ## v0.3.13
186 |
187 | * `bogart.redirect` now accepts a 2nd optional parameter which if present will be merged into the returned response object
188 |
189 | ## v0.3.12
190 |
191 | * Add `before` method to the return from `bogart.router`
192 |
193 | ## v0.3.10
194 |
195 | * Added support for mustache partials to the mustache template engine. This is unrelated to Bogart partials.
196 | * Match `pathInfo` of "" to "/" if no route found for ""
197 | * Support dot in named parameters
198 |
199 | ## v0.3.9
200 |
201 | * Made view engines registerable.
202 | * Moved 'jade' and 'haml' renderers to their own packages: 'bogart-jade' and 'bogart-haml'.
203 |
204 | ## v0.3.8
205 |
206 | * Removed node-deflate dependency becuase it was sometimes failing to compile when installed with npm.
207 |
--------------------------------------------------------------------------------
/Jakefile:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 |
3 | desc("generate a new bogart application skeleton");
4 | task("generate", [], function(dir, name) {
5 | console.log("generating bogart app in " + dir);
6 | var fs = require("fs"),
7 | path = require("path");
8 |
9 | dir = dir || ".";
10 |
11 | var appTemplate = fs.readFileSync(path.join(__dirname, "cli", "templates", "app.js.template"), 'utf8');
12 | fs.mkdirSync(dir, 0777);
13 | fs.writeFileSync(path.join(dir, "app.js"), appTemplate, 'utf8');
14 |
15 | var packageJsonTemplate = fs.readFileSync(path.join(__dirname, "cli", "templates", "package.json.template"), 'utf8');
16 | fs.writeFileSync(path.join(dir, "package.json"), packageJsonTemplate, 'utf8');
17 | });
18 |
--------------------------------------------------------------------------------
/benchmarks/alternative-routing/alt-router.js:
--------------------------------------------------------------------------------
1 | var
2 | util = require('../../lib/util'),
3 | EventEmitter = require('events').EventEmitter,
4 | Q = require('promised-io/promise'),
5 | when = Q.when,
6 | bogart = require('../../lib/bogart');
7 |
8 | var
9 | httpMethod = {
10 | GET: "get",
11 | POST: "post",
12 | PUT: "put",
13 | DELETE: "delete"
14 | },
15 | restMethod = {
16 | SHOW: httpMethod.GET,
17 | CREATE: httpMethod.POST,
18 | UPDATE: httpMethod.PUT,
19 | DESTROY: httpMethod.DELETE
20 | },
21 | PATH_PARAMETER_REPLACEMENT = "([^\.\/\?]+)",
22 | PATH_PARAMETERS = /@([\w\d]+)/g;
23 |
24 | exports.bogartEvent = {
25 | BEFORE_ADD_ROUTE: "beforeAddRoute",
26 | AFTER_ADD_ROUTE: "afterAddRoute"
27 | };
28 |
29 | exports.Router = Router;
30 |
31 | exports.init = function(config, notFoundApp) {
32 | var
33 | defaultNotFoundApp = function(req) {
34 | var body = 'Not Found';
35 | if (req.pathInfo) {
36 | body += ': ' + req.pathInfo;
37 | }
38 |
39 | return {
40 | status: 404,
41 | body: [body],
42 | headers: { 'Content-Length': body.length, 'Content-Type': 'text/html' }
43 | };
44 | },
45 | router = new Router();
46 |
47 | function bindToRouter(method) {
48 | return function() { method.apply(router, arguments); };
49 | }
50 | config.call(router, bindToRouter(router.show), bindToRouter(router.create), bindToRouter(router.update), bindToRouter(router.destroy));
51 |
52 | return function(req) {
53 | var resp;
54 | try {
55 | resp = router.respond(bogart.request(this, req));
56 | if(util.no(resp) && req.pathInfo !== '/routes') {
57 | if(notFoundApp) { return notFoundApp(req); }
58 | else return defaultNotFoundApp(req);
59 | }
60 | if ((util.no(resp) || resp.status === 404)&& req.pathInfo === '/routes') {
61 | var str = 'GET ';
62 |
63 | for(var rte in router.routes['get']) {
64 | str += '
';
65 | str += 'path: ' + router.routes['get'][rte].path + ' ' + 'paramNames: ' + router.routes['get'][rte].paramNames;
66 | str += '
';
67 | }
68 |
69 | return { status: 200, headers: { 'Content-Length': str.length, "Content-Type": "text/html" }, body: [ str ] };
70 | }
71 |
72 | return resp;
73 | } catch (err) {
74 | var str = 'Error '+err.toString()+' Stack Trace: '+JSON.stringify(err.stack);
75 | return { status: 500, headers: { 'Content-Type': 'text/html', "Content-Length": str.length }, body: [ str ] };
76 | }
77 | };
78 | };
79 |
80 | function Router() {
81 | var
82 | emitter = new EventEmitter(),
83 | settings = {}, k;
84 |
85 | for (k in emitter) {
86 | this[k] = emitter[k];
87 | }
88 |
89 | this.setting = function(name, val) {
90 | if (val === undefined) {
91 | return settings[name];
92 | }
93 |
94 | settings[name] = val;
95 | return this;
96 | };
97 | this.routes = {};
98 | }
99 |
100 | /**
101 | * Register a route
102 | * @param {String} method Http Verb e.g. 'GET', 'POST', 'PUT', 'DELETE'
103 | * @param {String} path Path for the route
104 | * @param {Function} handler Function to execute when the route is accessed
105 | */
106 | Router.prototype.route = function(method, path, handler) {
107 | var paramNames, route, originalPath = path;
108 |
109 | if (path.constructor === String) {
110 | paramNames = path.match(PATH_PARAMETERS) || [];
111 | paramNames = paramNames.map(function(x) { return x.substring(1); });
112 |
113 | path = new RegExp(path.replace(/\./, '\\.').replace(PATH_PARAMETERS, PATH_PARAMETER_REPLACEMENT));
114 | }
115 |
116 | route = { path: path, paramNames: paramNames, handler: handler, originalPath: originalPath, pathName: originalPath.replace(/@[^\/\?]+/g, "@PARAM")};
117 |
118 | this.emit(exports.bogartEvent.BEFORE_ADD_ROUTE, this, route);
119 |
120 | this.routes[method] = this.routes[method] || {};
121 | this.routes[method][route.pathName] = route;
122 |
123 | this.emit(exports.bogartEvent.AFTER_ADD_ROUTE, this, route);
124 |
125 | return this;
126 | };
127 |
128 | Router.prototype.handler = function(verb, path) {
129 | return this.routes[verb.toLowerCase()] ? this.routes[verb.toLowerCase()][path.replace(/@[^\/\?]+/g, "@PARAM")] : null;
130 | };
131 |
132 | Router.prototype.respond = function(reqPromise) {
133 | var
134 | self = this;
135 |
136 | return when(reqPromise, function(req) {
137 | var
138 | route = self.handler(req.method, req.pathInfo),
139 | routeParams = Object.create(Object.prototype),
140 | routeParamValues = null;
141 |
142 | if (util.no(route)) {
143 | return null;
144 | }
145 |
146 | routeParamValues = route.path.exec(req.pathInfo);
147 | if (routeParamValues) {
148 | routeParamValues.shift(); // Remove the initial match
149 |
150 | routeParamValues.forEach(function(val, indx) {
151 | val = decodeURIComponent(val);
152 | val = val.substring(1);
153 | if (route.paramNames.length > indx) {
154 | routeParams[route.paramNames[indx]] = val;
155 | } else if (val !== undefined) {
156 | routeParams.splat = routeParams.splat || [];
157 | routeParams.splat.push(val);
158 | }
159 | });
160 |
161 | // console.log(routeParams);
162 | }
163 |
164 | Object.defineProperty(req, 'routeParams', { value: routeParams, enumerable: true, readonly: true });
165 | Object.defineProperty(req, 'params', { value: util.merge({}, req.routeParams, req.search, req.body), enumerable: true, readonly: true });
166 |
167 | var hold = [];
168 | for(var name in routeParams) {
169 | hold.push(routeParams[name]);
170 | }
171 |
172 | var handlerResp = route.handler.apply(self, [req].concat(hold || []));
173 | if (util.no(handlerResp)) {
174 | throw new Error("Handler returned empty response:" + JSON.stringify(route));
175 | }
176 |
177 | return handlerResp;
178 | });
179 | };
180 |
181 | Router.prototype.show = function(path, handler) {
182 | return this.route(restMethod.SHOW, path, handler);
183 | };
184 |
185 | Router.prototype.create = function(path, handler) {
186 | return this.route(restMethod.CREATE, path, handler);
187 | };
188 |
189 | Router.prototype.update = function(path, handler) {
190 | return this.route(restMethod.UPDATE, path, handler);
191 | };
192 |
193 | Router.prototype.destroy = function(path, handler) {
194 | return this.route(restMethod.DESTROY, path, handler);
195 | };
--------------------------------------------------------------------------------
/benchmarks/alternative-routing/app.js:
--------------------------------------------------------------------------------
1 | var
2 | bogart = require('../../lib/bogart'),
3 | router = require('./alt-router'),
4 | sys = require('sys');
5 |
6 | var config = function(show, create, update, destroy) {
7 | show('/hello/@firstname/@lastname', function(req, fname, lname) {
8 | return bogart.html('Hello '+fname + " "+lname);
9 | });
10 |
11 | // show('/stream', function(req) {
12 | // var streamer = bogart.stream();
13 | //
14 | // setInterval(function() {
15 | // var currentTime = new Date();
16 | // streamer(currentTime.getHours()+':'+currentTime.getMinutes()+':'+currentTime.getSeconds()+"\n");
17 | // }, 10);
18 | //
19 | // setTimeout(function() {
20 | // streamer.end();
21 | // }, 10000);
22 | //
23 | // return streamer.respond();
24 | // });
25 |
26 | show('/1179/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
27 | show('/9398/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
28 | show('/680/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
29 | show('/8617/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
30 | show('/4008/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
31 | show('/1425/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
32 | show('/9404/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
33 | show('/3896/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
34 | show('/7512/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
35 | show('/3543/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
36 | show('/5685/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
37 | show('/2109/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
38 | show('/332/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
39 | show('/8617/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
40 | show('/6889/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
41 | show('/8274/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
42 | show('/8289/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
43 | show('/4454/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
44 | show('/4305/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
45 | show('/5223/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
46 | show('/4602/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
47 | show('/3084/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
48 | show('/1491/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
49 | show('/2727/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
50 | show('/4104/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
51 | show('/664/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
52 | show('/2042/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
53 | show('/4168/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
54 | show('/2583/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
55 | show('/490/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
56 | show('/6060/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
57 | show('/9095/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
58 | show('/3441/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
59 | show('/8376/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
60 | show('/8906/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
61 | show('/4239/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
62 | show('/2094/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
63 | show('/2338/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
64 | show('/8557/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
65 | show('/6740/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
66 | show('/1321/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
67 | show('/3768/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
68 | show('/7059/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
69 | show('/1431/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
70 | show('/9329/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
71 | show('/5440/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
72 | show('/4610/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
73 | show('/2181/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
74 | show('/6647/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
75 | show('/2342/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
76 | show('/7792/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
77 | show('/7018/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
78 | show('/113/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
79 | show('/3956/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
80 | show('/6116/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
81 | show('/1305/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
82 | show('/3673/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
83 | show('/1411/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
84 | show('/7164/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
85 | show('/6766/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
86 | show('/9508/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
87 | show('/9726/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
88 | show('/7935/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
89 | show('/9898/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
90 | show('/4478/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
91 | show('/5212/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
92 | show('/1110/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
93 | show('/3937/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
94 | show('/2833/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
95 | show('/4712/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
96 | show('/4554/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
97 | show('/233/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
98 | show('/255/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
99 | show('/3202/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
100 | show('/8267/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
101 | show('/6722/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
102 | show('/1157/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
103 | show('/2224/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
104 | show('/4185/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
105 | show('/5762/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
106 | show('/1187/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
107 | show('/2963/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
108 | show('/1876/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
109 | show('/8034/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
110 | show('/2134/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
111 | show('/5503/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
112 | show('/9011/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
113 | show('/565/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
114 | show('/5069/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
115 | show('/5060/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
116 | show('/2986/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
117 | show('/2009/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
118 | show('/1376/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
119 | show('/8790/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
120 | show('/8756/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
121 | show('/9349/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
122 | show('/1622/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
123 | show('/1899/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
124 | show('/9071/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
125 | show('/6736/@fname/@lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });
126 |
127 |
128 |
129 | };
130 |
131 | bogart.start(router.init(config));
--------------------------------------------------------------------------------
/benchmarks/hello-world/hello-world.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../../lib/bogart');
2 |
3 | var router = bogart.router();
4 | router.get('/', function(req) {
5 | return bogart.html('Hello World');
6 | });
7 |
8 | bogart.start(router);
9 |
--------------------------------------------------------------------------------
/benchmarks/mustache-layout/app.js:
--------------------------------------------------------------------------------
1 | var
2 | bogart = require('../../lib/bogart'),
3 | path = require('path')
4 |
5 | var app = bogart.router(function(show) {
6 | var viewEngine = bogart.viewEngine('mustache', path.join(bogart.maindir(), '..', 'views'));
7 |
8 | show('/', function(req, res) {
9 | return viewEngine.respond('index.html', { locals: { description: 'This is content' } });
10 | });
11 | });
12 |
13 | bogart.start(app);
14 |
--------------------------------------------------------------------------------
/benchmarks/route-generator.js:
--------------------------------------------------------------------------------
1 | /*
2 | USAGE: node route-generator.js [vaiable prefix] [number of routes to generate] >> generated-routes.txt
3 | */
4 |
5 |
6 | var head = "show('/",
7 | tail = "/"+process.argv[2]+"fname/"+process.argv[2]+"lname', function(req, fname, lname) { return bogart.html('Hello '+fname+' '+lname); });";
8 |
9 | for(var i = 0; i
9 | #
10 | #
11 | #
12 |
13 | log(){
14 | echo "... $@"
15 | }
16 |
17 | #
18 | # Benchmark the given and [path].
19 | #
20 | # [path]
21 | #
22 |
23 | bm(){
24 | local dir=$1;
25 | local path=${2-/};
26 | for file in benchmarks/$dir/*; do
27 | log running $file
28 | case $file in
29 | *.js)
30 | node $file &
31 | sleep 2
32 | ;;
33 | *.thin.ru)
34 | thin -R $file -p 3000 start &
35 | sleep 2
36 | ;;
37 | *.mongrel.ru)
38 | rackup $file -p 3000 -s mongrel &
39 | sleep 2
40 | ;;
41 | esac
42 | local pid=$!
43 | local dirname=results/$(dirname $file)
44 | mkdir -p $dirname
45 | $AB $ABFLAGS -g results/$file.dat $ADDR/$path > results/$file.out
46 | log $(cat results/$file.out | grep Requests)
47 | kill -9 $pid
48 | done
49 | }
50 |
51 | # Make ./results
52 | mkdir -p results
53 |
54 | # Store flags
55 | echo $ABFLAGS > results/flags
56 |
57 | # Run benchmarks
58 | log $AB $ABFLAGS $ADDR
59 | bm hello-world
60 | #bm alternative-routing
61 | bm mustache-layout
62 |
--------------------------------------------------------------------------------
/benchmarks/views/index.html:
--------------------------------------------------------------------------------
1 |
2 | {{ description }}
3 |
4 |
--------------------------------------------------------------------------------
/benchmarks/views/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mustache with Layout
4 |
5 |
6 | Hello
7 | {{{ body }}}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/cli/templates/app.js.template:
--------------------------------------------------------------------------------
1 | /**
2 | * A bogart application skeleton
3 | */
4 |
5 | var bogart = require("bogart");
6 |
7 | var homeRouter = bogart.router(function(get, post, put, del) {
8 | get("/", function() {
9 | return bogart.html("Hello World");
10 | });
11 | });
12 |
13 | bogart.start(homeRouter);
14 |
--------------------------------------------------------------------------------
/cli/templates/package.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "email": "",
4 | "version": "0.0.1",
5 | "directories": {
6 | "lib": "./lib"
7 | },
8 | "main": "./app.js",
9 | dependencies: {
10 | "bogart": ">=0.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/docs/app.md:
--------------------------------------------------------------------------------
1 | # App
2 |
3 | The Bogart Application constructor, `bogart.app`, exposes a helper for creating JSGI
4 | servers.
5 |
6 | ## app.start([port], [hostname])
7 |
8 | Start a server to accept connections on port and hostname. If hostname is ommitted,
9 | connections on any IPv4 address will be accepted.
10 |
11 | This function is asynchronous and returns a promise.
12 |
13 | ## app.use(middleware)
14 |
15 | Add a JSGI application (middleware) or Bogart Router to the server's JSGI stack.
16 |
17 | ## Event: 'beforeAddMiddleware'
18 | `function(app, args) { }`
19 |
20 | Emitted before `app.use` inserts the application or router into the JSGI stack.
21 |
22 | ## Event: 'afterAddMiddleware'
23 | `function(app, middleware) { }`
24 |
25 | Emitted after `app.use` inserts the application or router into the JSGI stack.
26 |
--------------------------------------------------------------------------------
/docs/bogart.md:
--------------------------------------------------------------------------------
1 | # Bogart
2 |
3 | ## Utility
4 |
5 | ### bogart.q
6 |
7 | Namespace for promise functions. See the q module documentation for more information.
8 |
9 | ### bogart.promisify(nodeAsynchronousFunction, context)
10 |
11 | Adapts a NodeJS style asynchronous function to return a promise.
12 | A NodeJS style asynchronous function is a function that takes as its last parameter a callback.
13 | The callback expects that the first parameter when it is called will be an error if it exists
14 | or falsey if there is no error. The second parameter is the success value for the callback.
15 |
16 | Promisify works on NodeJS asynchronous functions that only call their callback one time. If a
17 | functions calls its callback multiple times, it is not a candidate to be wrapped in a promise as
18 | promises may only be resolved one time.
19 |
20 | ## Core
21 |
22 | ### bogart.app()
23 |
24 | Create a Bogart Application, a helper for composing stacks of JSGI middleware and Bogart routers.
25 |
26 | Please see [Application](/app/) for more information.
27 |
28 | ### bogart.router([config], nextApp)
29 |
30 | Creates a Bogart Router, a JSGI middleware appliance that makes it easy to execute functions
31 | depending on the request path.
32 |
33 | Please see [Routing](/routing/) for more information.
34 |
35 | ### bogart.start(jsgiApp, [options])
36 |
37 | Begin listening on the default port, 8080, or the port specified in options.port if options are provided.
38 | jsgiApp should be a JSGI function that serves as the entry point for the server.
39 |
40 | ### bogart.viewEngine(engine)
41 |
42 | Forwarded from `view.viewEngine`. Please see the [View Engine documentation](/view-engine/) for more information.
43 |
44 | ## View Helpers
45 |
46 | ### bogart.error(msg, opts)
47 |
48 | Creates a JSGI response with a body of msg, a "Content-Type" of "text/html", and
49 | a status of 500. The options parameter will be merged with the resulting JSGI object if
50 | provided to allow overriding or addition of other properties.
51 |
52 | ### bogart.html(html, [options])
53 |
54 | Creates a JSGI response with a body of html. The options parameter may be used to
55 | override properties of the JSGI response like headers and status.
56 |
57 | ### bogart.text(str, [options])
58 |
59 | Create a JSGI response with a "Content-Type" of "text/plain" and a body containing str. The
60 | options parameter will be merged with the resulting JSGI object if provided to allow overriding
61 | or addition of other properties.
62 |
63 | ### bogart.json(obj, [options])
64 |
65 | Creates a JSGI response with a "Content-Type" of "application/json" and a body containing the
66 | JSON representation of obj. The options parameter will be merged with the resulting JSGI object
67 | if provided to allow overriding or addition of other properties.
68 |
69 | ### bogart.redirect(url, [options])
70 |
71 | Creates a JSGI response with a status of 302, temporary redirect. The options parameter will be
72 | merged with the resulting JSGI object if provided to allow overriding or addition of other
73 | properties.
74 |
75 | ### bogart.response([viewEngine])
76 |
77 | Instantiates a ResponseBuilder, a utility object that aids in constructing responses imperatively.
78 |
79 | ### bogart.permanentRedirect(url, [options])
80 |
81 | Creates a JSGI response with a status of 301, permanent redirect. The options parameter will be
82 | merged with the resulting JSGI object if provided to allow overriding or addition of other
83 | properties.
84 |
85 | ### bogart.notModified([options])
86 |
87 | Creates a JSGI response with a status of 304, not modified. The options parameter will be
88 | merged with the resulting JSGI object if provided to allow overriding or addition of other
89 | properties.
90 |
91 | ## ResponseBuilder
92 |
93 | ResponseBuilder is returned by `bogart.response`. ResponseBuilder is a utilty class
94 | for composing JSGI responses imperatively.
95 |
96 | ### ResponseBuilder.end()
97 |
98 | The end method must be called when you are finished constructing the response.
99 |
100 | ### ResponseBuilder.headers(headers)
101 |
102 | Overwrite the response headers.
103 |
104 | ### ResponseBuilder.render(view, [options])
105 |
106 | Renders view to the response body. If a View Engine was not provided, raises an error.
107 |
108 | ### ResponseBuilder.status(num)
109 |
110 | Set the status of the response to num.
111 |
112 | ### ResponseBuilder.send(content)
113 |
114 | Add content to the response body.
115 |
116 | ### ResponseBuilder.setHeader(header, value)
117 |
118 | Set header to value.
119 |
120 | ### ResponseBuilder.statusCode
121 |
122 | Property for getting and setting the status code of the response.
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Bogart
2 |
3 | ## Features
4 |
5 | * Intuitive Routing
6 | * Extensible View Engine that supports Jade, Mustache, and HAML out of the box
7 | * View Engine supports partials
8 | * [JSGI](http://wiki.commonjs.org/wiki/JSGI) compatible
9 | * Good test coverage
10 | * Fast!
11 |
12 | ## Learn
13 |
14 | * [Routing]() Setup Bogart routes to handle URLs.
15 | * [ViewEngine]() Extensible ViewEngine with partials and layout support.
16 | * [Middleware]() Compose responses with reusable middleware.
--------------------------------------------------------------------------------
/docs/routing.md:
--------------------------------------------------------------------------------
1 | # Routing
2 |
3 | Routing in Bogart is simple and intuitive. A route is a HTTP method paried with a
4 | URL matching pattern and a function to call to handle requests to the route.
5 |
6 | var router = bogart.router();
7 | router.get('/', function(req) {
8 | return bogart.html('Hello World');
9 | });
10 |
11 | Routes are tested for matches in the order in which they were defined.
12 |
13 | ## Route Patterns
14 |
15 | Route patterns are matched vs URLs. They may include named parameters that will
16 | be accessible via the `params` object of the `req` object passed to the route handler.
17 |
18 | var router = bogart.router();
19 | router.get('/hello/:name', function(req) {
20 | var greeting = 'Hello '+req.params.name;
21 | return bogart.html(greeting);
22 | });
23 |
24 | It is also possible to access named parameters via arguments passed to the handler function.
25 | Named parameters will be passed in the order they are speicifed in the route pattern.
26 |
27 | var router = bogart.router();
28 | router.get('/hello/:name', function(req, name) {
29 | return bogart.html('Hello '+name);
30 | });
31 |
32 | Route patterns support wildcards. Wildcards will match anything whereas regular named parameters
33 | will not match beyond a path separator ("/").
34 |
35 | var router = bogart.router();
36 | router.get('/hello/*', function(req, name) {
37 | return bogart.html('Hello '+req.params.splat[0]);
38 | });
39 |
40 | ## Regex Routes
41 |
42 | When a route pattern is not powerful enough, regular expressions may be used to specify which
43 | URLs are to be matched.
44 |
45 | var app = bogart.router();
46 | app.get(/\/posts?/, function(req) {
47 | // Matches 'post' or 'posts'
48 | return bogart.html('Regex Route');
49 | });
50 |
51 | Parameters are via regular expression groups in regular expression routes. The parameter values
52 | are put in an `Array` in `req.params.splat` of the `req` object passed to the route handler.
53 |
54 | var app = bogart.router();
55 | app.get(/hello-(.*)/, function(req) {
56 | var name = req.params.splat[0];
57 | return bogart.html('Hello '+name);
58 | });
59 |
--------------------------------------------------------------------------------
/docs/view-engine.md:
--------------------------------------------------------------------------------
1 | # View Engine
2 |
3 | The Bogart `ViewEngine` renders view. It handles partials and layouts as well.
4 | The `ViewEngine` ships with support for [Mustache](http://mustache.github.com/),
5 | [Jade](http://jade-lang.com/), and [Haml](http://haml-lang.com/).
6 |
7 | ## view.viewEngine(engine)
8 |
9 | Creates a `view.ViewEngine`.
10 |
11 | ## ViewEngine.render(template, [options])
12 |
13 | The `render` method uses the selected templating engine to render the specified view
14 | with options specified in the options `locals` object.
15 |
16 | Rendering a view with mustache and replacement variables:
17 |
18 | var viewEngine = bogart.viewEngine('mustache');
19 | viewEngine.render('index.html', { locals: { title: 'Hello Mustache' } });
20 |
21 | ## ViewEngine.respond(template, [options])
22 |
23 | The `respond` method returns a promise for a JSGI response that will render the
24 | specified view.
25 |
26 | var viewEngine = bogart.viewEngine('mustache');
27 | viewEngine.respond('index.html', { locals: { title: 'Hello Mustache' } });
28 |
29 | ## view.RenderOptions
30 |
31 | Options passed to `viewEngine.render` and `viewEngine.respond` should quack like view.RenderOptions.
32 |
33 | * opts.layout The name of the layout or a boolean. If `true` then the name is defaulted to 'layout.html'. If `false` then no layout should be used.
34 | * opts.locals Context in which to render the template. This should contain the replacement values of variables in your templates.
35 |
36 | ## Layouts
37 |
38 | Layouts are supported. By default, if there is a file named 'layout.{ext}' where
39 | `ext` is the extension of the view (haml, jade, mustache, html, etc...) then this file
40 | is used as the layout for the view. The layout is expected to render a variable named
41 | `body`. The `body` variable will contain the contents of view being rendered into the
42 | layout. The `body` variable should not be HTML escaped.
43 |
44 | Specifying the file extension of a template is optional as long as the file extension
45 | matches the default file extension associated with the template type. For example, if
46 | using Jade then `viewEngine.render('index')` is equivalent to `viewEngine.render('index.jade')`.
47 |
48 | ### Mustache Example
49 |
50 | Mustache is the default template engine of Bogart.
51 |
52 | View (index.html):
53 |
54 | Welcome {{firstName}}!
55 |
56 | Layout (layout.html):
57 |
58 |
59 | {{title}}
60 |
61 |
62 | {{title}}
63 |
64 | {{{body}}}
65 |
66 |
67 |
68 | App (app.js):
69 |
70 | var bogart = require('bogart');
71 | var viewEngine = bogart.viewEngine('mustache');
72 | var router = bogart.router();
73 |
74 | router.get('/hello/:firstName', function(req) {
75 | return viewEngine.respond('index.html', {
76 | locals: { title: 'Hello', firstName: req.params.firstName }
77 | });
78 | });
79 |
80 | bogart.start(router);
81 |
82 | Execute `node app.js` and visit [http://localhost:8080/hello/bogart](http://localhost:8080/hello/bogart).
83 |
84 | ### Jade Example
85 |
86 | Make sure to install bogart-jade from npm before running these examples.
87 |
88 | View (index.jade):
89 |
90 | Welcome #{firstName}!
91 |
92 | Layout (layout.jade):
93 |
94 | html
95 | head
96 | title #{title}
97 | body
98 | h1 #{title}
99 | !{body}
100 |
101 | App (app.js):
102 |
103 | var bogart = require('bogart');
104 | var viewEngine = bogart.viewEngine('jade');
105 | var router = bogart.router();
106 |
107 | router.get('/hello/:firstName', function(req) {
108 | return viewEngine.respond('index', {
109 | locals: { title: 'Hello', firstName: req.params.firstName }
110 | });
111 | });
112 |
113 | bogart.start(router);
114 |
115 | Execute `node app.js` and visit [http://localhost:8080/hello/bogart](http://localhost:8080/hello/bogart).
116 |
117 | ## Partials
118 |
119 | Sometimes, especially with AJAX requests, it is desirable to return a view without rendering it inside of a
120 | layout even if a layout is present. To acccomodate this, the View Engine has a `partial` method. The `partial`
121 | method takes the same arguments as the `render` method but does not render its template insdie of
122 | a layout.
123 |
124 | var viewEngine = bogart.viewEngine('mustache');
125 | viewEngine.partial('index.html');
126 |
127 |
--------------------------------------------------------------------------------
/engines/node/lib/fileWatcher.js:
--------------------------------------------------------------------------------
1 | exports.watchFile = require('fs').watchFile;
--------------------------------------------------------------------------------
/examples/body-adapter.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart')
2 | , util = require('util')
3 | , Stream = require('stream').Stream;
4 |
5 | function HelloWorldStream() {
6 | var self = this;
7 |
8 | Stream.call(this);
9 |
10 | this.readable = true;
11 |
12 | process.nextTick(function() {
13 | self.emit('data', 'Stream ');
14 | process.nextTick(function() {
15 | self.emit('data',
16 | 'This is an example of a Bogart route that returns a stream. '+
17 | 'See a route that returns a Buffer ');
18 | self.emit('end');
19 | });
20 | });
21 | }
22 |
23 | util.inherits(HelloWorldStream, Stream);
24 |
25 | var router = bogart.router();
26 | router.get('/', function() {
27 | return new HelloWorldStream();
28 | });
29 |
30 | router.get('/buffer', function() {
31 | return new Buffer('Buffer ' +
32 | 'This is an example of a Bogart route that returns a buffer.');
33 | });
34 |
35 | var app = bogart.app();
36 | app.use(bogart.middleware.bodyAdapter);
37 | app.use(router);
38 |
39 | app.start();
--------------------------------------------------------------------------------
/examples/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Bogart `config` example.
3 | *
4 | * Environment is determined by the OS environment variable BOGART_ENV.
5 | * The default value for environment if BOGART_ENV is not set is 'development'.
6 | */
7 |
8 | var bogart = require('../lib/bogart');
9 |
10 | function developmentApp() {
11 | return function(req) {
12 | return 'You are running in development mode.';
13 | };
14 | };
15 |
16 | function productionApp() {
17 | return function(req) {
18 | return 'You are running in production mode.';
19 | };
20 | };
21 |
22 | var app = bogart.app();
23 |
24 | bogart.config(function() {
25 | // Executed in all environments.
26 | app.use(bogart.batteries);
27 | });
28 |
29 | bogart.config('development', function() {
30 | app.use(developmentApp);
31 | });
32 |
33 | bogart.config('production', function() {
34 | app.use(productionApp);
35 | });
36 |
37 | app.start();
--------------------------------------------------------------------------------
/examples/cors.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var router = bogart.router();
4 | router.get('/', function(req) {
5 | return 'Hello Root';
6 | });
7 |
8 | router.get('/:name', function(req) {
9 | return bogart.cors({message:'Hello, ' + req.params.name + '!'});
10 | });
11 |
12 | var app = bogart.app();
13 | app.use(bogart.batteries);
14 | app.use(router);
15 |
16 | app.start();
17 |
18 | /* From another domain:
19 | *
20 | * $.ajax({
21 | * url: 'http://localhost:8080/Duncan',
22 | * success: function(data) {
23 | * console.log(data);
24 | * }
25 | * });
26 | *
27 | */
--------------------------------------------------------------------------------
/examples/facebook-authentication/app.js:
--------------------------------------------------------------------------------
1 | const PORT = 1337;
2 |
3 | var path = require('path');
4 | var bogart = require('../../lib/bogart');
5 |
6 | var config = require('./config.json');
7 |
8 | var facebookConfig = {
9 | clientId: config.appId,
10 | clientSecret: config.secret,
11 | host: 'http://localhost:'+PORT
12 | };
13 |
14 | var router = bogart.router();
15 | router.get('/profile', function(req) {
16 | return bogart.html(JSON.stringify(req.session('profile')));
17 | });
18 |
19 | var frontRouter = bogart.router();
20 | frontRouter.get('/', function(req) {
21 | return bogart.html('Profile | Login ');
22 | });
23 |
24 | frontRouter.get('/login', function(req) {
25 | return bogart.html(' ');
26 | });
27 |
28 | var app = bogart.app();
29 | app.use(bogart.middleware.session({ secret: 'ABC123' }));
30 | app.use(bogart.middleware.directory(path.join(__dirname, 'public')));
31 | app.use(frontRouter);
32 | app.use(bogart.middleware.facebook(facebookConfig));
33 | app.use(router);
34 |
35 | app.start(PORT);
36 |
--------------------------------------------------------------------------------
/examples/facebook-authentication/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nrstott/bogart/a6e3b01b2e6f1247dde697038101fd6fa94e76b0/examples/facebook-authentication/public/favicon.ico
--------------------------------------------------------------------------------
/examples/facebook-authentication/public/login-with-facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nrstott/bogart/a6e3b01b2e6f1247dde697038101fd6fa94e76b0/examples/facebook-authentication/public/login-with-facebook.png
--------------------------------------------------------------------------------
/examples/flash-test.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var flashConfig = {
4 | encryptionKey: 'db0031e2-04e7-11e1-86ad-000c290196f7'
5 | };
6 |
7 | var router = bogart.router();
8 | router.get('/', function (req) {
9 | var html = 'Bogart Flash Bogart Flash Middleware ';
10 | html += 'A random number has been inserted into flash. ' +
11 | 'On the first request, the value should be undefined. Subsequent requests should ' +
12 | 'display the random number set on the previous request. ' +
13 | 'Refresh the page to see the flash value change.
' +
14 | 'Existing Flash: '+ req.flash('foo') +' ';
15 |
16 | req.flash('foo', Math.random() * 10);
17 |
18 | return bogart.html(html);
19 | });
20 |
21 | var app = bogart.app();
22 | app.use(bogart.middleware.flash(flashConfig));
23 | app.use(router);
24 |
25 | app.start();
26 |
--------------------------------------------------------------------------------
/examples/gzip-response.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var router = new bogart.router();
4 | router.get('/', function() {
5 | return bogart.html('This response is compressed by the Deflate middleware! ');
6 | });
7 |
8 | var app = bogart.app();
9 | app.use(bogart.middleware.gzip);
10 | app.use(router);
11 |
12 | app.start();
--------------------------------------------------------------------------------
/examples/hello-world.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var router = bogart.router();
4 | router.get('/', function(req) {
5 | return 'Hello Root';
6 | });
7 |
8 | router.get('/:name', function(req) {
9 | return 'Hello '+req.params.name;
10 | });
11 |
12 | var app = bogart.app();
13 | app.use(bogart.batteries({ secret: 'my-secret' }));
14 | app.use(router);
15 |
16 | app.start();
17 |
--------------------------------------------------------------------------------
/examples/multipart-form/app.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../../lib/bogart')
2 | , util = require('util');
3 |
4 | var router = bogart.router();
5 |
6 | router.get('/', function(req) {
7 | var html = '' +
8 | 'multi ' +
9 | '' +
10 | '';
16 |
17 | return bogart.html(html);
18 | });
19 |
20 | router.post('/', function(req) {
21 | console.log(req.body);
22 |
23 | return bogart.text(util.inspect(req.body));
24 | });
25 |
26 | var app = bogart.app();
27 | app.use(bogart.middleware.parted);
28 | app.use(router);
29 |
30 | app.start();
31 |
--------------------------------------------------------------------------------
/examples/mustache-layout/app.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../../lib/bogart')
2 | , path = require('path');
3 |
4 | var viewEngine = bogart.viewEngine('mustache', path.join(bogart.maindir(), 'views'));
5 |
6 | var router = bogart.router();
7 |
8 | router.get('/', function(req, res) {
9 | return viewEngine.respond('index.html', { locals: { description: 'This is content' } });
10 | });
11 |
12 | var app = bogart.app();
13 | app.use(router);
14 |
15 | app.start();
16 |
--------------------------------------------------------------------------------
/examples/mustache-layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mustache-layout-example",
3 | "version": "1.0.0",
4 | "author": "Nathan Stott",
5 | "email": "nathan.stott@whiteboard-it.com",
6 | "main": "./app.js",
7 | "directories": { "lib": "./lib" },
8 | "dependencies": {
9 | "promised-io": "v0.2.1",
10 | "jsgi": "v0.2.2",
11 | "mustache": "0.3.1-dev"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/mustache-layout/views/index.html:
--------------------------------------------------------------------------------
1 |
2 | {{ description }}
3 |
4 |
--------------------------------------------------------------------------------
/examples/mustache-layout/views/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mustache with Layout
4 |
5 |
6 | Hello
7 | {{{ body }}}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/pipe-file.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 | var router = bogart.router();
6 |
7 | router.get('/', function(req) {
8 | return bogart.html(' ');
9 | });
10 |
11 | router.get('/image.jpg', function(req) {
12 | var filePath = path.join(__dirname, 'static-server', 'public', 'images', 'ninja-cat.jpg')
13 | , stat = fs.statSync(filePath);
14 |
15 | return bogart.pipe(fs.createReadStream(filePath), {
16 | headers: { 'Content-Type': 'image/jpeg', 'Content-Length': stat.size }
17 | });
18 | });
19 |
20 | router.get('/cat.jpg', function(req) {
21 | var filePath = path.join(__dirname, 'static-server', 'public', 'images', 'ninja-cat.jpg');
22 |
23 | return bogart.file(filePath);
24 | });
25 |
26 | var app = bogart.app();
27 | app.use(router);
28 |
29 | app.start();
30 |
--------------------------------------------------------------------------------
/examples/proxy.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var router = bogart.router();
4 | router.get('/', function() {
5 | return bogart.proxy('http://google.com');
6 | });
7 |
8 | var app = bogart.app();
9 | app.use(router);
10 |
11 | app.start();
12 |
--------------------------------------------------------------------------------
/examples/session.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../lib/bogart');
2 |
3 | var router = bogart.router();
4 |
5 | router.get('/', function (req) {
6 | var html = 'Bogart Session Demo ';
7 |
8 | html += ''
9 | html += 'Session Values ';
10 | html += '';
11 |
12 | req.session.keys().forEach(function (key) {
13 | html += ''+key+': '+req.session(key)+' ';
14 | });
15 |
16 | html += ' ';
17 |
18 | html += '';
26 | html += '';
32 |
33 | return bogart.html(html);
34 | });
35 |
36 | router.post('/', function (req) {
37 | req.session(req.params.key, req.params.val);
38 |
39 | return bogart.redirect('/');
40 | });
41 |
42 | router.del('/destroy', function (req) {
43 | req.session.destroy();
44 |
45 | return bogart.redirect('/');
46 | });
47 |
48 | var sessionConfig = {
49 | lifetime: 600,
50 | secret: 'ABC1234'
51 | };
52 |
53 | var app = bogart.app();
54 | app.use(bogart.middleware.parted());
55 | app.use(bogart.middleware.methodOverride());
56 | app.use(bogart.middleware.session(sessionConfig));
57 | app.use(router);
58 |
59 | app.start(1337);
60 |
--------------------------------------------------------------------------------
/examples/share-javascript/app.js:
--------------------------------------------------------------------------------
1 | var bogart = require('../../lib/bogart');
2 |
3 | var viewEngine = bogart.viewEngine('mustache');
4 |
5 | viewEngine.share(new Date(), 'now');
6 |
7 | viewEngine.share({
8 | app: 'JavaScript Sharing Example',
9 | framework: 'Bogart',
10 | author: 'Nathan Stott'
11 | }, 'settings');
12 |
13 | viewEngine.share(function() {
14 | return 'Shared on the server, executed on the client';
15 | }, 'fn');
16 |
17 | var router = bogart.router()
18 | router.get('/', function(req) {
19 | return viewEngine.respond('index.html', { locals: { title: 'Hello World' }, layout: false });
20 | });
21 |
22 | var app = bogart.app();
23 | app.use(router);
24 | app.start();
25 |
--------------------------------------------------------------------------------
/examples/share-javascript/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 |
6 | {{title}}
7 |
8 |
9 | Server Time
10 |
11 |
12 |
13 | Server Object
14 |
15 |
16 |
17 | Calling a Server-Provided Function
18 |
19 |
20 |
21 | {{{javascript}}}
22 |
23 |
30 |
--------------------------------------------------------------------------------
/examples/static-server/app.js:
--------------------------------------------------------------------------------
1 | var
2 | bogart = require("../../lib/bogart");
3 |
4 | var router = bogart.router();
5 |
6 | router.get("/", function(req) {
7 | return bogart.html(" ");
8 | });
9 |
10 | var root = require("path").join(__dirname, "public");
11 |
12 | var app = bogart.app();
13 | app.use(bogart.middleware.directory(root));
14 | app.use(router);
15 |
16 | app.start();
17 |
--------------------------------------------------------------------------------
/examples/static-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static-server-example",
3 | "version": "1.0.0",
4 | "author": "Nathan Stott",
5 | "email": "nathan.stott@whiteboard-it.com",
6 | "main": "./app.js",
7 | "directories": { "lib": "./lib" },
8 | "dependencies": {
9 | "promised-io": "v0.2.1",
10 | "jsgi": "v0.2.2",
11 | "mustache": "0.3.1-dev"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/static-server/public/images/ninja-cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nrstott/bogart/a6e3b01b2e6f1247dde697038101fd6fa94e76b0/examples/static-server/public/images/ninja-cat.jpg
--------------------------------------------------------------------------------
/examples/task-list/app.js:
--------------------------------------------------------------------------------
1 | var bogart = require("../../lib/bogart")
2 | , path = require("path");
3 |
4 | // Mustache view engine, templates are located in the "task-list/templates" directory
5 | var viewEngine = bogart.viewEngine("mustache", path.join(__dirname, "templates"));
6 |
7 | // Tasks storage, replace with a database in a real application.
8 | var tasks = {};
9 |
10 | // Most Bogart applications will have a router. Router's generally contain the business logic
11 | // for the application.
12 | var router = bogart.router();
13 |
14 | router.get("/", function(req) {
15 | var
16 | errors = req.params.errors,
17 | taskList = [];
18 |
19 | // The template needs the tasks as an array, transform the tasks into an array
20 | for (var taskName in tasks) {
21 | taskList.push(tasks[taskName]);
22 | }
23 |
24 | // The index.html file is a mustache template located in the "templates" directory
25 | return viewEngine.respond("index.html", { locals: { tasks: taskList, errors: errors } });
26 | });
27 |
28 | router.post("/", function(req) {
29 | var
30 | task = { name: req.params.name, description: req.params.description },
31 | errors = [];
32 |
33 | if (!task.name || task.name.trim() === "") {
34 | errors.push("name is required");
35 | }
36 |
37 | // PRG pattern http://en.wikipedia.org/wiki/Post/Redirect/Get
38 | if (errors.length > 0) {
39 | return bogart.redirect("/?errors="+JSON.stringify(errors));
40 | }
41 |
42 | tasks[task.name] = task;
43 |
44 | return bogart.redirect("/");
45 | });
46 |
47 | router.del("/:name", function(req) {
48 | console.log('deleting '+req.params.name);
49 | console.log(tasks);
50 | delete tasks[req.params.name];
51 |
52 | return bogart.redirect("/");
53 | });
54 |
55 | // Create a Bogart Application. Application makes it easy for us to manage our JSGI appliances and routers.
56 | var app = bogart.app();
57 |
58 | // Frameworks are better when batteries are included. Put the batteries into this app!
59 | app.use(bogart.batteries, {
60 | session: {
61 | secret: "some secret key.."
62 | }
63 | });
64 |
65 | // Add our router to the app. NOTE: It is important to add batteries first.
66 | app.use(router);
67 |
68 | // Start the server
69 | app.start();
70 | console.log("Task Server Started!");
71 |
--------------------------------------------------------------------------------
/examples/task-list/templates/index.html:
--------------------------------------------------------------------------------
1 | Task List
2 |
3 |
15 |
16 |
17 |
18 | Add Task
19 |
20 | Task Name
21 |
22 |
23 |
24 | Description
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/task-list/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{{ body }}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/flash/flashCookieDataProvider.js:
--------------------------------------------------------------------------------
1 | var security = require("../security"),
2 | util = require("../util"),
3 | DEFAULT_ENCRYPTION_KEY = "a720efa0-04e8-11e1-a8b5-000c290196f7",
4 | COOKIE_NAME = "jsgi_flash_data";
5 |
6 |
7 |
8 | module.exports = CookieDataProvider;
9 |
10 | function CookieDataProvider(options) {
11 | this.options = options || {};
12 | this.flashData = {};
13 | }
14 |
15 | CookieDataProvider.prototype.previousFlash = function(req, flashId) {
16 | var cookies = util.parseCookies(req);
17 | if (cookies && cookies[COOKIE_NAME]) {
18 | var hold = security.decrypt(decodeURIComponent(cookies[COOKIE_NAME]), this.options.encryptionKey || DEFAULT_ENCRYPTION_KEY);
19 | return hold === "" ? {} : JSON.parse(hold);
20 | } else return null;
21 | };
22 |
23 | CookieDataProvider.prototype.setter = function(req, flashId) {
24 | this.flashData[flashId] = {};
25 | var self = this;
26 | return function(data) {
27 | Object.keys(data).forEach(function(key) {
28 | self.flashData[flashId][key] = data[key];
29 | });
30 | };
31 | };
32 |
33 | CookieDataProvider.prototype.finalize = function(req, res, flashId) {
34 | res = res || {};
35 | res.headers = res.headers || {};
36 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
37 | var data = this.flashData[flashId];
38 | delete this.flashData[flashId];
39 |
40 | var cookie = COOKIE_NAME + "=" + encodeURIComponent(security.encrypt(JSON.stringify(data), this.options.encryptionKey || DEFAULT_ENCRYPTION_KEY)) + ";Path=/;";
41 |
42 | if (Object.prototype.toString.call(res.headers["Set-Cookie"]) === "[object String]") {
43 | var existing = res.headers["Set-Cookie"];
44 | res.headers["Set-Cookie"] = [];
45 | res.headers["Set-Cookie"].push(existing);
46 | }
47 | res.headers["Set-Cookie"].push(cookie);
48 |
49 | return res;
50 | };
51 |
52 |
53 | CookieDataProvider.prototype.clear = function(req, res, flashId) {
54 | res = res || {};
55 | res.headers = res.headers || {};
56 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
57 |
58 | var cookies = util.parseCookies(req);
59 | if (cookies && cookies[COOKIE_NAME]) {
60 | var yesterday = new Date();
61 | yesterday.setDate(yesterday.getDate() - 1);
62 | var clearCookie = COOKIE_NAME + "=expiring; Expires=" + yesterday.toUTCString() + ";Path=/;";
63 |
64 | if (Object.prototype.toString.call(res.headers["Set-Cookie"]) === "[object String]") {
65 | var existing = res.headers["Set-Cookie"];
66 | res.headers["Set-Cookie"] = [];
67 | res.headers["Set-Cookie"].push(existing);
68 | }
69 |
70 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"].filter(function(el) {
71 | return !(new RegExp("^" + COOKIE_NAME + "=").test(el));
72 | });
73 | res.headers["Set-Cookie"].push(clearCookie);
74 | }
75 |
76 | return res;
77 | };
--------------------------------------------------------------------------------
/lib/flash/flashIdCookieProvider.js:
--------------------------------------------------------------------------------
1 | var uuid = require("node-uuid"),
2 | security = require("../security"),
3 | util = require("../util"),
4 | flashCookieName = "jsgi_flash_session";
5 |
6 | module.exports = CookieIdProvider;
7 |
8 | function CookieIdProvider(options) {
9 | this.options = options || {};
10 | }
11 |
12 | CookieIdProvider.prototype.getFlashId = function(req) {
13 | var cookies = util.parseCookies(req);
14 | if (cookies && cookies[flashCookieName]) {
15 | return cookies[flashCookieName];
16 | } else return null;
17 | };
18 |
19 | CookieIdProvider.prototype.newId = function(req) {
20 | return uuid();
21 | };
22 |
23 | CookieIdProvider.prototype.finalize = function(req, res, flashId) {
24 | res = res || {};
25 | res.headers = res.headers || {};
26 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
27 | var cookie = flashCookieName + "=" + flashId + ";Path=/;";
28 |
29 | if (Object.prototype.toString.call(res.headers["Set-Cookie"]) === "[object String]") {
30 | var existing = res.headers["Set-Cookie"];
31 | res.headers["Set-Cookie"] = [];
32 | res.headers["Set-Cookie"].push(existing);
33 | }
34 |
35 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"].filter(function(el) {
36 | return !(new RegExp("^" + flashCookieName + "=").test(el));
37 | });
38 | res.headers["Set-Cookie"].push(cookie);
39 |
40 | return res;
41 | };
42 |
43 | CookieIdProvider.prototype.clear = function(req, res, flashId) {
44 | res = res || {};
45 | res.headers = res.headers || {};
46 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
47 |
48 | var cookies = util.parseCookies(req);
49 | if (cookies && cookies[flashCookieName]) {
50 | var yesterday = new Date();
51 | yesterday.setDate(yesterday.getDate() - 1);
52 | var clearCookie = flashCookieName + "=expiring; Expires=" + yesterday.toUTCString() + ";Path=/;";
53 |
54 | if (Object.prototype.toString.call(res.headers["Set-Cookie"]) === "[object String]") {
55 | var existing = res.headers["Set-Cookie"];
56 | res.headers["Set-Cookie"] = [];
57 | res.header["Set-Cookie"].push(existing);
58 | }
59 |
60 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"].filter(function(el) {
61 | return !(new RegExp("^" + flashCookieName + "=").test(el));
62 | }) || [];
63 | res.headers["Set-Cookie"].push(clearCookie);
64 | }
65 |
66 | return res;
67 | };
68 |
--------------------------------------------------------------------------------
/lib/forEachStream.js:
--------------------------------------------------------------------------------
1 | var util = require('util')
2 | , Readable = require('stream').Readable
3 | , Q = require('./q');
4 |
5 | var enqueue = typeof setImmediate === 'function' ? setImmediate : process.nextTick;
6 |
7 | function ForEachStream(forEachable) {
8 | if (!(this instanceof ForEachStream)) return new ForEachStream(forEachable);
9 |
10 | Readable.call(this);
11 |
12 | var self = this;
13 |
14 | this._waitingDeferred = null;
15 | this._readingDeferred = null;
16 |
17 | this._read = function () {
18 | if (this._waitingDeferred !== null) {
19 | this._waitingDeferred.resolve();
20 | this._waitingDeferred = null;
21 | return;
22 | }
23 |
24 | if (this._readingDeferred !== null) {
25 | return;
26 | }
27 |
28 | this._readingDeferred = Q.when(forEachable.forEach(function (data) {
29 | if (self.push(data) === false) {
30 | self._waitingDeferred = Q.defer();
31 | return self._waitingDeferred.promise;
32 | }
33 | }), function () {
34 | self.push(null);
35 | }, function (err) {
36 | self.emit('error', err);
37 | });
38 | };
39 | };
40 |
41 | require('util').inherits(ForEachStream, Readable);
42 |
43 | module.exports = ForEachStream;
44 |
--------------------------------------------------------------------------------
/lib/forEachable.js:
--------------------------------------------------------------------------------
1 | var q = require('./q');
2 |
3 | exports.join = function (forEachable) {
4 | var body = '';
5 |
6 | function appendChunk(chunk) {
7 | body += chunk;
8 | };
9 |
10 | return q.when(forEachable.forEach(appendChunk), function () {
11 | return body;
12 | });
13 | };
--------------------------------------------------------------------------------
/lib/fsp.js:
--------------------------------------------------------------------------------
1 | // Promise-based file-system functions
2 |
3 | var fs = require('fs')
4 | , q = require('./q');
5 |
6 | exports.readFile = function (filePath) {
7 | var deferred = q.defer();
8 |
9 | fs.readFile(filePath, function(err, data) {
10 | if (err) {
11 | deferred.reject(err);
12 | } else {
13 | deferred.resolve(data);
14 | }
15 | });
16 |
17 | return deferred.promise;
18 | }
19 |
20 | exports.stat = function (fileOrFolderPath) {
21 | var deferred = q.defer();
22 |
23 | fs.stat(fileOrFolderPath, function(err, status) {
24 | if (err) {
25 | deferred.reject(err);
26 | } else {
27 | deferred.resolve(status);
28 | }
29 | });
30 |
31 | return deferred.promise;
32 | }
--------------------------------------------------------------------------------
/lib/middleware.js:
--------------------------------------------------------------------------------
1 | var Q = require('./q'),
2 | when = Q.when,
3 | fs = require('fs'),
4 | path = require('path'),
5 | ForEachStream = require('./forEachStream'),
6 | util = require('./util'),
7 | merge = util.merge,
8 | EventEmitter = require('events').EventEmitter,
9 | _ = require('underscore'),
10 | Router = require('./router').Router;
11 |
12 | var middleware = function (handler) {
13 | return function factory(next) {
14 | if (next === undefined) {
15 | return function (next) {
16 | return factory(next);
17 | };
18 | }
19 |
20 | return function (req) {
21 | return handler(req, next);
22 | }
23 | }
24 | };
25 |
26 | module.exports = middleware;
27 |
28 | function deprecate(oldName, newName, fn) {
29 | return function () {
30 | console.log(oldName + ' is deprecated, please use ' + newName + ' instead');
31 | return fn.apply(middleware, Array.prototype.slice.call(arguments));
32 | }
33 | }
34 |
35 | middleware.freshEnv = middleware(function (req, next) {
36 | req.env = _.extend({}, req.env);
37 |
38 | return next(req);
39 | });
40 |
41 | middleware.parseJson = require('./middleware/parseJson');
42 | middleware.ParseJson = deprecate('ParseJson', 'parseJson', middleware.parseJson);
43 |
44 | middleware.parseForm = require('./middleware/parseForm');
45 | middleware.ParseForm = deprecate('ParseForm', 'parseForm', middleware.parseForm);
46 |
47 | middleware.gzip = require('./middleware/gzip');
48 | middleware.Gzip = deprecate('Gzip', 'gzip', middleware.gzip);
49 |
50 | /**
51 | * Provides Rails-style HTTP method overriding via the _method parameter or X-HTTP-METHOD-OVERRIDE header
52 | * http://code.google.com/apis/gdata/docs/2.0/basics.html#UpdatingEntry
53 | */
54 | middleware.methodOverride = require('./middleware/methodOverride');
55 | middleware.MethodOverride = deprecate('MethodOverride', 'methodOverride', middleware.methodOverride);
56 |
57 | middleware.parted = require('./middleware/parted');
58 | middleware.Parted = deprecate('Parted', 'parted', middleware.parted);
59 |
60 | /**
61 | * Translates rejected promises to a JSGI error response.
62 | *
63 | * @param errorResponse {Function} Optional. A function that returns a JSGI response when passed an error.
64 | * @returns {Function} JSGI response.
65 | */
66 | middleware.error = require('./middleware/error');
67 | middleware.Error = deprecate('Error', 'error', middleware.error);
68 |
69 | /**
70 | * Validates that the response from nextApp is a valid JSGI response.
71 | * Rejects the promise if the response is invalid.
72 | *
73 | * @param {Function} nextApp The next application in the JSGI chain.
74 | * @returns {Function} JSGI response.
75 | */
76 | middleware.validateResponse = require('./middleware/validateResponse');
77 | middleware.ValidateResponse = deprecate('ValidateResponse', 'validateResponse', middleware.validateResponse);
78 |
79 | middleware.directory = require('./middleware/directory');
80 | middleware.Directory = deprecate('Directory', 'directory', middleware.directory);
81 |
82 | middleware.flash = require('./middleware/flash');
83 | middleware.Flash = deprecate('Flash', 'flash', middleware.flash);
84 |
85 | /**
86 | * Creates a OAuth2 section to auth routes requiring oauth2
87 | *
88 | */
89 | middleware.oauth = require('./middleware/oauth');
90 |
91 | middleware.oauth2 = require('./middleware/oauth2');
92 |
93 | middleware.facebook = require('./middleware/facebook');
94 |
95 | middleware.google = require('./middleware/google');
96 |
97 | middleware.twitter = require('./middleware/twitter');
98 |
99 | middleware.session = middleware.Session = require("./middleware/session/session").Session;
100 |
101 | /**
102 | * Adapts Node.JS Buffer and Stream types used as response bodies into
103 | * a CommonJS ForEachable.
104 | *
105 | * @param {Function} nextApp The next application in the JSGI chain.
106 | * @returns {Function} JSGI Application.
107 | */
108 | middleware.bodyAdapter = require('./middleware/bodyAdapter');
109 |
110 | /**
111 | * Translates strings into JSGI response objects.
112 | *
113 | * The default response has a status of 200 and a "Content-Type" header of "text/html"
114 | *
115 | * @param {Object} responseDefaults Optional parameter. If stringReturnAdapter is called with one argument, that argument is assumed to be nextApp, not responseDefaults.
116 | * @param {Function} nextApp Next application in the JSGI chain.
117 | *
118 | * @returns {Function} JSGI Application.
119 | */
120 | middleware.stringReturnAdapter = require('./middleware/stringReturnAdapter');
121 |
122 | /**
123 | * JSGI Middleware that tries a series of JSGI Applications in order until one succeeds the `accept` test.
124 | * The first parameter is expected to be a function that will be called with the values returned by the applications.
125 | * The `accept` function must return a boolean indicating whether this response is the one that should be returned or
126 | * the next application in the series should be called.
127 | *
128 | * var cascade = bogart.middleware.cascade(function(res) { return res.status !== 404 },
129 | * function(req) {
130 | * return {
131 | * status: 404,
132 | * headers: {}
133 | * body: []
134 | * };
135 | * }, function(req) {
136 | * return {
137 | * status: 200,
138 | * headers: { 'Content-Type': 'text/html' },
139 | * body: [ 'Hello World' ]
140 | * };
141 | * });
142 | * cascade(req); // The 2nd middleware function's return would be accepted and would become the return from cascade.
143 | *
144 | * @params {Function} accept A function `function(res) {}` that takes a response and returns a boolean.
145 | * @params {Function} apps The rest of the arguments are used as applications to cascade through.
146 | *
147 | * @returns {Promise} JSGI Response
148 | */
149 | middleware.cascade = require('./middleware/cascade');
150 |
151 | /*
152 | * Configuration factory for `bogart.batteries`.
153 | *
154 | * @param {Object} overrides Optional prameter providing overrides
155 | * for default configuration options.
156 | *
157 | * @api private
158 | */
159 | function batteriesConfig(overrides) {
160 | overrides = overrides || {};
161 |
162 | return _.extend({}, batteriesConfig.default, overrides);
163 | };
164 |
165 | batteriesConfig.default = {
166 | directory: 'public',
167 | error: {},
168 | session: {}
169 | };
170 |
171 | /**
172 | * All the goodies in one convenient middleware.
173 | *
174 | * Includes the following JSGI Chain:
175 | *
176 | * error -> validateResponse -> gzip -> directory -> parted -> methodOverride
177 | * -> session -> flash -> bodyAdapter -> stringReturnAdapter -> nextApp
178 | *
179 | * @param {Object} config Optional configuration, if arity is two first parameter is config.
180 | * @param {Function} nextApp The next application (Middleware, Bogart Router, etc...) in the JSGI chain.
181 | * @returns {Function} A good default middleware stack in one function call.
182 | */
183 | middleware.batteries = function(config, nextApp) {
184 | if (nextApp === undefined) {
185 | if (typeof config === 'function') {
186 | nextApp = config;
187 |
188 | config = null;
189 | } else {
190 | return function (nextApp) {
191 | return middleware.batteries(config, nextApp);
192 | };
193 | }
194 | }
195 |
196 | config = batteriesConfig(config);
197 | if (config.secret) {
198 | config.session.secret = config.secret;
199 | }
200 |
201 | if (!nextApp) {
202 | throw 'Bogart batteries requires at least one parameter, a nextApp to execute to fulfill the request.'
203 | }
204 |
205 | var stack = middleware.freshEnv(
206 | middleware.error(config.error,
207 | middleware.gzip(
208 | middleware.directory(config.directory,
209 | middleware.parted(config.parted || undefined,
210 | middleware.methodOverride(
211 | middleware.session(config.session,
212 | middleware.flash(config.flash || undefined,
213 | middleware.bodyAdapter(
214 | middleware.stringReturnAdapter(nextApp))))))))));
215 |
216 | return stack;
217 | };
218 |
--------------------------------------------------------------------------------
/lib/middleware/bodyAdapter.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , middleware = require('../middleware');
3 |
4 | module.exports = middleware(function (req, nextApp) {
5 | return q.when(nextApp(req), function (resp) {
6 | if (resp.body && resp.body.forEach) {
7 | return resp;
8 | }
9 |
10 | if (Buffer.isBuffer(resp)) {
11 | return {
12 | status: 200,
13 | headers: { 'content-type': 'text/html' },
14 | body: [ resp.toString('utf-8') ]
15 | };
16 | }
17 |
18 | if (Buffer.isBuffer(resp.body)) {
19 | resp.body = [ resp.body.toString('utf-8') ];
20 | return resp;
21 | }
22 |
23 | var searchFriendlyErrorCode = 'BODY_ADAPTER_BAD_RESPONSE_BODY';
24 | var reason = 'Middleware error: The response object returned by your route handler for "'+req.pathInfo+'" is not a proper bogart JSGI response. It does not have a forEach method, is not a Buffer, and is not a readable Stream';
25 |
26 | throw new Error(reason + ' ' + searchFriendlyErrorCode);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/lib/middleware/cascade.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | throw new Error('Cascade middleware has been removed. \
3 | If you had a use-case for it that can no be accomplished via bogart.app, \
4 | please share and will consider readding it.');
5 | };
--------------------------------------------------------------------------------
/lib/middleware/directory.js:
--------------------------------------------------------------------------------
1 | var fsp = require('../fsp')
2 | , q = require('../q')
3 | , qfs = require('q-io/fs')
4 | , mimeType = require('../mimetypes').mimeType
5 | , path = require('path')
6 | , _ = require('underscore')
7 | , notFoundMessage = "404 Not Found"
8 | , nfLength = Buffer.byteLength(notFoundMessage)
9 | , response404 = {
10 | status: 404,
11 | body: [ notFoundMessage ],
12 | headers: {
13 | "content-length": nfLength
14 | }
15 | }
16 | ;
17 |
18 | function respondWithFile(req, root, filePath, contentType, headers) {
19 | if (!qfs.contains(root, filePath)) {
20 | return response404;
21 | }
22 | return q.when(fsp.stat(filePath), function (stat) {
23 | var etag, indexPath;
24 |
25 | if (!stat.isFile()) {
26 | if (stat.isDirectory()) {
27 | indexPath = path.join(filePath, "index.html");
28 | return respondWithFile(req, root, indexPath, contentType, headers);
29 | } else {
30 | return response404;
31 | }
32 | }
33 |
34 | headers = headers || {};
35 |
36 | if (typeof contentType === 'object') {
37 | headers = contentType;
38 | contentType = undefined;
39 | }
40 |
41 | contentType = mimeType(path.extname(filePath), contentType);
42 |
43 | etag = [stat.ino, stat.size, Date.parse(stat.mtime)].join("-");
44 |
45 | if (req.headers && req.headers["if-none-match"] === etag) {
46 | return {
47 | status: 304,
48 | body: [],
49 | headers: {}
50 | };
51 | }
52 |
53 | return q.when(fsp.readFile(filePath), function onSuccess(contents) {
54 | return {
55 | status: 200,
56 | headers: _.extend({
57 | etag: etag,
58 | "content-type": contentType
59 | }, headers),
60 | body: [contents]
61 | }
62 | });
63 | }, function(err) {
64 | if (err && err.code === 'ENOENT') {
65 | return response404;
66 | } else {
67 | throw err;
68 | }
69 | });
70 | };
71 |
72 | module.exports = function directory(root, nextApp) {
73 | var opts;
74 |
75 | if (nextApp === undefined) {
76 | if (typeof root === 'function') {
77 | nextApp = root;
78 | root = undefined;
79 | } else {
80 | return function (nextApp) {
81 | return directory(root, nextApp);
82 | };
83 | }
84 | }
85 |
86 | if (typeof root === 'string') {
87 | opts = { root: root };
88 | } else if (typeof root === 'object') {
89 | opts = root;
90 | } else {
91 | opts = {};
92 | }
93 |
94 | opts.headers = opts.headers || {};
95 |
96 | return function (req) {
97 | var reqPath = path.join(opts.root, req.pathInfo.substring(1));
98 |
99 | return q.when(respondWithFile(req, root, reqPath, opts.headers), function(res) {
100 | if (res.status === 404 && nextApp) {
101 | return nextApp(req);
102 | } else {
103 | return res;
104 | }
105 | });
106 | };
107 | };
108 |
--------------------------------------------------------------------------------
/lib/middleware/error.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , _ = require('underscore');
3 |
4 | function errorResponse(config, req, err) {
5 | var message = 'Message: ';
6 |
7 | if (config.showError) {
8 | if (typeof err === 'string') {
9 | message += err;
10 | } else if (err.message) {
11 | message += err.message;
12 | if (err.stack) {
13 | message += 'Stack Trace: ';
14 | message += '';
15 | if(err.stack.join) {
16 | message += err.stack.join(' ');
17 | } else {
18 | message += err.stack.replace(/\r?\n/g, ' ');
19 | }
20 | message += '
';
21 | }
22 | }
23 | } else {
24 | message = '';
25 | }
26 |
27 | if (config.logError) {
28 | console.error('Error processing request', req.pathInfo, err);
29 | }
30 |
31 | return {
32 | status: 500,
33 | body: ['Error An error occurred. ', message, ''],
34 | headers: {
35 | 'content-type': 'text/html'
36 | }
37 | };
38 | };
39 |
40 | var defaultConfig = {
41 | showError: true,
42 | logError: true
43 | };
44 |
45 | /**
46 | * Translates rejected promises to a JSGI error response.
47 | *
48 | * @param errorResponse {Function} Optional. A function that returns a JSGI response when passed an error.
49 | * @returns {Function} JSGI response.
50 | */
51 | module.exports = function errorMiddleware(config, nextApp) {
52 | if (nextApp === undefined) {
53 | if (typeof config === 'function') {
54 | nextApp = config;
55 | config = {};
56 | } else {
57 | return function (nextApp) {
58 | return errorMiddleware(config, nextApp);
59 | };
60 | }
61 | }
62 |
63 | config = _.extend({}, defaultConfig, config);
64 |
65 | return function (req) {
66 | return q.whenCall(function() { return nextApp(req); }, function(val) {
67 | return val;
68 | }, function (err) {
69 | return errorResponse(config, req, err);
70 | });
71 | };
72 | };
73 |
--------------------------------------------------------------------------------
/lib/middleware/facebook.js:
--------------------------------------------------------------------------------
1 | var middleware = require('../middleware')
2 | , _ = require('underscore');
3 |
4 | function parseFacebookProfile(body) {
5 | if (body){
6 | var o = JSON.parse(body);
7 | var profile = {
8 | provider: 'facebook'
9 | };
10 | profile.id = o.id;
11 | profile.username = o.username;
12 | profile.displayName = o.name;
13 | profile.name = {
14 | familyName: o.last_name,
15 | givenName: o.first_name,
16 | middleName: o.middle_name
17 | };
18 | profile.gender = o.gender;
19 | profile.profileUrl = o.link;
20 | profile.emails = [ o.email ];
21 | return profile;
22 | }
23 | return undefined;
24 | }
25 |
26 | module.exports = facebookMiddleware;
27 |
28 | function facebookMiddleware(config, nextApp) {
29 | if (nextApp === undefined) {
30 | return function (nextApp) {
31 | return facebookMiddleware(config, nextApp);
32 | };
33 | }
34 |
35 | var facebook_strategy = {
36 | authorizationURL: 'https://www.facebook.com/dialog/oauth',
37 | tokenURL: 'https://graph.facebook.com/oauth/access_token',
38 | resourceURL: 'https://graph.facebook.com/me',
39 | parse: facebookMiddleware.parseFacebookProfile
40 | };
41 |
42 | var oauth2Options = _.extend(facebook_strategy, config);
43 |
44 | return middleware.oauth2(oauth2Options, nextApp);
45 | };
46 |
47 | facebookMiddleware.parseFacebookProfile = parseFacebookProfile;
48 |
49 |
--------------------------------------------------------------------------------
/lib/middleware/flash.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , when = q.when
3 | , flashIdCookieProvider = require("../flash/flashIdCookieProvider")
4 | , flashCookieDataProvider = require("../flash/flashCookieDataProvider");
5 |
6 | module.exports = function flash(config, nextApp) {
7 | if (nextApp === undefined) {
8 | if (typeof config === 'function') {
9 | nextApp = config;
10 | config = {};
11 | } else {
12 | return function (nextApp) {
13 | return flash(config, nextApp);
14 | };
15 | }
16 | }
17 |
18 | if (typeof nextApp !== 'function') {
19 | throw {
20 | message: 'flash middleware expects a nextApp as the last argument.',
21 | code: 'BOGART_FLASH_BAD_NEXTAPP'
22 | };
23 | }
24 |
25 | config = config || {};
26 | config.options = config.options || {};
27 |
28 | config.flashIdProvider = config.flashIdProvider || new flashIdCookieProvider(config.options.idProvider || {});
29 | config.flashDataProvider = config.flashDataProvider || new flashCookieDataProvider(config.options.dataProvider || {});
30 |
31 | return function (req) {
32 | if (req.pathInfo === '/favicon.ico') {
33 | return nextApp(req);
34 | }
35 | var oldFlashId = config.flashIdProvider.getFlashId(req);
36 |
37 | // get a new id for the current request
38 | var newFlashId = config.flashIdProvider.newId(req);
39 |
40 | // make flash data from previous request available
41 | req.env = req.env || {};
42 | var prevData = oldFlashId ? config.flashDataProvider.previousFlash(req, oldFlashId) : {};
43 |
44 | return when(prevData, function (data) {
45 | req.env.flash = data || {};
46 | // create the setter for new flash data
47 | var setter = config.flashDataProvider.setter(req, newFlashId);
48 | req.flash = function (key, val) {
49 | if (key && val) {
50 | var obj = {};
51 | obj[key] = val;
52 | setter(obj);
53 | } else return req.env.flash[key];
54 | }
55 |
56 | return when(nextApp(req), function (resp) {
57 | if (oldFlashId) {
58 | // clear old flash data
59 | resp = config.flashDataProvider.clear(req, resp, oldFlashId);
60 | // clear old flash id
61 | resp = config.flashIdProvider.clear(req, resp, oldFlashId);
62 | }
63 |
64 | // finalize the flashId provider for the current request
65 | resp = config.flashIdProvider.finalize(req, resp, newFlashId);
66 | // finalize the flash data provider for the current request
67 | resp = config.flashDataProvider.finalize(req, resp, newFlashId);
68 |
69 | return resp;
70 | });
71 | });
72 | };
73 | };
--------------------------------------------------------------------------------
/lib/middleware/google.js:
--------------------------------------------------------------------------------
1 | var middleware = require('../middleware')
2 | , _ = require('underscore');
3 |
4 | function parseGoogleProfile(body) {
5 | if (body) {
6 | o = JSON.parse(body);
7 | var profile = {
8 | provider: 'google'
9 | };
10 | profile.id = o.id;
11 | profile.displayName = o.displayName;
12 | profile.name = o.name;
13 | profile.gender = o.gender;
14 | profile.profileUrl = o.url;
15 | if (o.emails) {
16 | profile.emails = o.emails;
17 | }
18 | return profile;
19 | }
20 | }
21 |
22 | module.exports = googleMiddleware;
23 |
24 | function googleMiddleware(config, nextApp) {
25 | if (nextApp === undefined) {
26 | return function (nextApp) {
27 | return googleMiddleware(config, nextApp);
28 | };
29 | }
30 |
31 | var googleStrategy = {
32 | authorizationURL: 'https://accounts.google.com/o/oauth2/auth',
33 | tokenURL: 'https://accounts.google.com/o/oauth2/token',
34 | resourceURL: 'https://www.googleapis.com/plus/v1/people/me',
35 | authorizationParams: {
36 | scope: 'https://www.googleapis.com/auth/plus.profile.emails.read'
37 | },
38 | parse: googleMiddleware.parseGoogleProfile
39 | };
40 |
41 | var oauth2Options = _.extend(googleStrategy, config);
42 |
43 | return middleware.oauth2(oauth2Options, nextApp);
44 | };
45 |
46 | googleMiddleware.parseGoogleProfile = parseGoogleProfile;
47 |
--------------------------------------------------------------------------------
/lib/middleware/gzip.js:
--------------------------------------------------------------------------------
1 | var zlib = require('zlib')
2 | , bogart = require('../bogart')
3 | , middleware = require('../middleware')
4 | , q = require('../q');
5 |
6 | function acceptsGzip(req) {
7 | var encodings;
8 |
9 | if (!req.headers || !req.headers['accept-encoding']) {
10 | return false;
11 | }
12 |
13 | encodings = req.headers['accept-encoding'].split(',');
14 | return encodings.filter(function(x) {
15 | return x === 'gzip';
16 | }).length > 0;
17 | }
18 |
19 | function toForEachable(stream) {
20 | var forEachable = {}
21 | , deferred = q.defer()
22 | , buffer = []
23 | , cb = null;
24 |
25 | forEachable.forEach = function(callback) {
26 | cb = callback;
27 |
28 | while (x = buffer.pop()) {
29 | cb(x);
30 | }
31 |
32 | return deferred.promise;
33 | };
34 |
35 | stream.on('data', function(data) {
36 | if (cb === null) { buffer.push(data); }
37 | else { cb(data); }
38 | });
39 |
40 | stream.on('end', function() {
41 | deferred.resolve();
42 | });
43 |
44 | return forEachable;
45 | }
46 |
47 | module.exports = middleware(function (req, nextApp) {
48 | var resp = nextApp(req);
49 |
50 | if (acceptsGzip(req)) {
51 | return q.when(resp, function(resp) {
52 | var gzip = zlib.createGzip();
53 | resp.headers['content-encoding'] = 'gzip';
54 | if (resp.headers['content-length']) {
55 | delete resp.headers['content-length'];
56 | }
57 | if (resp.headers['Content-Length']) {
58 | delete resp.headers['Content-Length'];
59 | }
60 |
61 | bogart.pump(resp.body, gzip);
62 |
63 | if(resp.status != 304) {
64 | resp.body = toForEachable(gzip);
65 | }
66 | return resp;
67 | });
68 | } else {
69 | return resp;
70 | }
71 | });
72 |
--------------------------------------------------------------------------------
/lib/middleware/methodOverride.js:
--------------------------------------------------------------------------------
1 | var middleware = require('../middleware');
2 |
3 | var HTTP_METHODS = {
4 | "GET": true,
5 | "HEAD": true,
6 | "PUT": true,
7 | "POST": true,
8 | "DELETE": true,
9 | "OPTIONS": true
10 | };
11 |
12 | var METHOD_OVERRIDE_PARAM_KEY = "_method";
13 | var HTTP_METHOD_OVERRIDE_HEADER = 'x-http-method-override';
14 |
15 | /**
16 | * Provides Rails-style HTTP method overriding via the _method parameter or X-HTTP-METHOD-OVERRIDE header
17 | * http://code.google.com/apis/gdata/docs/2.0/basics.html#UpdatingEntry
18 | */
19 | var methodOverride = middleware(function (req, nextApp) {
20 |
21 | if (req.body && typeof req.body == 'object') {
22 |
23 | if ((req.method.toUpperCase() == 'POST') && (req.headers['content-type'] && !req.headers['content-type'].match(/^multipart\/form-data/))) {
24 | var method = req.headers[HTTP_METHOD_OVERRIDE_HEADER] || req.body[METHOD_OVERRIDE_PARAM_KEY];
25 |
26 | if (method && HTTP_METHODS[method.toUpperCase()] === true) {
27 | req.env.original_method = req.method;
28 | req.method = method.toUpperCase();
29 | }
30 | }
31 | }
32 | return nextApp(req);
33 | });
34 |
35 | methodOverride.METHOD_OVERRIDE_PARAM_KEY = METHOD_OVERRIDE_PARAM_KEY;
36 | methodOverride.HTTP_METHOD_OVERRIDE_HEADER = HTTP_METHOD_OVERRIDE_HEADER;
37 |
38 | module.exports = methodOverride;
39 |
--------------------------------------------------------------------------------
/lib/middleware/oauth.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , oauth = require('oauth')
3 | , bogart = require('../bogart')
4 | , _ = require('underscore')
5 | , url = require('url');
6 |
7 | module.exports = function oauthMiddleware(config, nextApp) {
8 | if (nextApp === undefined) {
9 | return function (nextApp) {
10 | return oauthMiddleware(config, nextApp);
11 | };
12 | }
13 |
14 | var options = {
15 | loginRoute: '/auth/login',
16 | logoutRoute: '/auth/logout',
17 | callbackRoute: '/auth/callback',
18 | errorUrl: '/'
19 | };
20 | options = _.extend(options, config);
21 |
22 | if (!options.host) {
23 | throw new Error('host is a required option')
24 | }
25 |
26 | if (!options.host.match(/^http/)) {
27 | throw new Error('host must include the protocal.');
28 | }
29 |
30 | options.host = options.host.replace(/\/$/, ''); //remove trailing slash
31 |
32 | var OAuth = new oauth.OAuth(options.requestTokenURL, options.accessTokenURL, options.consumerKey, options.consumerSecret, '1.0A', null, 'HMAC-SHA1');
33 |
34 | var authorized = function(req) {
35 | if (!req.session('profile')) {
36 | return bogart.redirect(options.loginRoute + '?returnUrl=' + req.pathInfo );
37 | }
38 | req.auth = req.auth || {};
39 | req.auth.profile = req.session('profile');
40 | req.auth.access_token = req.session('access_token');
41 | return nextApp(req);
42 | };
43 |
44 | var router = bogart.router(authorized);
45 |
46 | router.get(options.loginRoute, function (req) {
47 | var deferred = q.defer();
48 | var callbackRoute = options.host + options.callbackRoute;
49 | if (req.params.returnUrl) {
50 | callbackRoute += '?returnUrl=' + encodeURIComponent(req.params.returnUrl);
51 | }
52 | var params = {
53 | oauth_callback: callbackRoute
54 | };
55 | OAuth.getOAuthRequestToken(params, function (error, token, tokenSecret, params) {
56 | if (error) {
57 | deferred.reject(error);
58 | }
59 | req.session('oauth_token_secret', tokenSecret);
60 | var parsed = url.parse(options.authorizationURL, true);
61 | parsed.query.oauth_token = token;
62 | var location = url.format(parsed);
63 | deferred.resolve(bogart.redirect(location));
64 | });
65 | return deferred.promise;
66 | });
67 |
68 | router.get(options.logoutRoute, function (req) {
69 | req.session('profile', undefined);
70 |
71 | return bogart.redirect('/');
72 | });
73 |
74 | router.get(options.callbackRoute, function (req) {
75 | var deferred = q.defer();
76 | var oAuthToken = req.params.oauth_token,
77 | oAuthVerifier = req.params.oauth_verifier,
78 | oAuthTokenSecret = req.session('oauth_token_secret');
79 | OAuth.getOAuthAccessToken(oAuthToken, oAuthTokenSecret, oAuthVerifier, function (error, accessToken, tokenSecret, params) {
80 | if (error) {
81 | deferred.reject(error);
82 | }
83 | var parsed = url.parse(options.resourceURL, true);
84 | options.resourceURLParams.forEach(function (resourceUrlParam) {
85 | if (params[resourceUrlParam]) {
86 | parsed.query[resourceUrlParam] = params[resourceUrlParam];
87 | }
88 | });
89 | var location = url.format(parsed);
90 | OAuth.getProtectedResource(location, 'get', accessToken, tokenSecret, function (error, body, res) {
91 | if (error) {
92 | deferred.reject(error);
93 | }
94 | var profile = q(options.parse(body, req)).then(function (profile) {
95 | req.session('profile', profile);
96 | req.session('access_token', accessToken);
97 | return bogart.redirect(options.successUrl || req.params.returnUrl || '/');
98 | }).fail(function (error) {
99 | var parsedUrl = url.parse(options.errorUrl, true);
100 | parsedUrl.query.message = error.message;
101 | var redirectUrl = url.format(parsedUrl);
102 | return bogart.redirect(redirectUrl);
103 | });
104 | deferred.resolve(profile);
105 | });
106 | });
107 | return deferred.promise;
108 | });
109 |
110 | return router(nextApp);
111 | };
112 |
--------------------------------------------------------------------------------
/lib/middleware/oauth2.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , oauth = require('oauth')
3 | , bogart = require('../bogart')
4 | , _ = require('underscore')
5 | , url = require('url');
6 |
7 | module.exports = function oauth2Middleware(config, nextApp) {
8 | if (nextApp === undefined) {
9 | return function (nextApp) {
10 | return oauth2Middleware(config, nextApp);
11 | };
12 | }
13 |
14 | var options = {
15 | loginRoute: '/auth/login',
16 | logoutRoute: '/auth/logout',
17 | callbackRoute: '/auth/callback',
18 | modalAuthRoute: '/auth/modalauth',
19 | errorUrl: '/'
20 | };
21 | options = _.extend(options, config);
22 |
23 | if (!options.host) {
24 | throw new Error('host is a required option')
25 | }
26 |
27 | if (!options.host.match(/^http/)) {
28 | throw new Error('host must include the protocal.');
29 | }
30 |
31 | options.host = options.host.replace(/\/$/, ''); //remove trailing slash
32 |
33 | var OAuth2 = new oauth.OAuth2(options.clientId, options.clientSecret, '', options.authorizationURL, options.tokenURL);
34 |
35 | var authd = function(req) {
36 | if (!req.session('profile')) {
37 | return bogart.redirect(options.loginRoute + '?returnUrl=' + req.pathInfo );
38 | }
39 | req.auth = req.auth || {};
40 | req.auth.profile = req.session('profile');
41 | req.auth.access_token = req.session('access_token');
42 | return nextApp(req);
43 | };
44 | var router = bogart.router(authd);
45 | var callbackRoute = ''
46 | router.get(options.loginRoute, function (req) {
47 |
48 | callbackRoute = options.host + options.callbackRoute;
49 | if (req.params.returnUrl) {
50 | callbackRoute += '?returnUrl=' + encodeURIComponent(req.params.returnUrl);
51 | }
52 |
53 | var url = OAuth2.getAuthorizeUrl(_.extend({
54 | response_type: 'code',
55 | redirect_uri: callbackRoute
56 | }, config.authorizationParams));
57 |
58 | return bogart.redirect(url);
59 | });
60 |
61 | router.get(options.logoutRoute, function (req) {
62 | req.session('profile', undefined);
63 |
64 | return bogart.redirect('/');
65 | });
66 |
67 | router.get(options.callbackRoute, function (req) {
68 | var deferred = q.defer();
69 |
70 | if (req.params.code) {
71 | var code = req.params.code;
72 | // NOTE: The module oauth (0.9.5), which is a dependency, automatically adds
73 | // a 'type=web_server' parameter to the percent-encoded data sent in
74 | // the body of the access token request. This appears to be an
75 | // artifact from an earlier draft of OAuth 2.0 (draft 22, as of the
76 | // time of this writing). This parameter is not necessary, but its
77 | // presence does not appear to cause any issues.
78 | OAuth2.getOAuthAccessToken(code, {
79 | grant_type: 'authorization_code',
80 | redirect_uri: callbackRoute
81 | }, function (err, accessToken, refreshToken) {
82 | OAuth2.getProtectedResource(options.resourceURL, accessToken, function(err, body, res) {
83 | try {
84 | if (err) {
85 | console.log(err)
86 | deferred.reject(err);
87 | }
88 |
89 | var profile = q(options.parse(body, req)).then(function (profile) {
90 | req.session('profile', profile);
91 | req.session('access_token', accessToken);
92 |
93 | return bogart.redirect(options.successUrl || req.params.returnUrl || '/');
94 | }).fail(function (error) {
95 | var parsedUrl = url.parse(options.errorUrl, true);
96 | parsedUrl.query.message = error.message;
97 | var redirectUrl = url.format(parsedUrl);
98 | return bogart.redirect(redirectUrl);
99 | });
100 | deferred.resolve(profile);
101 | } catch (e) {
102 | deferred.reject(e);
103 | }
104 | });
105 | });
106 | }
107 |
108 | return deferred.promise;
109 | });
110 |
111 | router.post(options.modalAuthRoute, function(req) {
112 | var deffered = q.defer()
113 | , accessToken = req.params.accessToken;
114 |
115 | OAuth2.getProtectedResource(options.resourceURL, accessToken, function (err, body, res) {
116 | try {
117 | var profile = options.parse(body)
118 | if (err) {
119 | deffered.reject(err);
120 | }
121 |
122 | req.session('profile', profile);
123 | req.session('access_token', accessToken);
124 |
125 | deffered.resolve(bogart.redirect(req.params.returnUrl || '/'));
126 | } catch (e) {
127 | deffered.reject(e);
128 | }
129 | });
130 |
131 | return deffered.promise;
132 | });
133 |
134 | return router(nextApp);
135 | };
136 |
--------------------------------------------------------------------------------
/lib/middleware/parseForm.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , middleware = require('../middleware')
3 | , forEachable = require('../forEachable')
4 | , querystring = require('querystring');
5 |
6 | module.exports = middleware(function (req, nextApp) {
7 | var contentType = req.headers['content-type'];
8 |
9 | if (contentType) {
10 | if (contentType.indexOf(';')) {
11 | contentType = contentType.split(';')[0];
12 | }
13 |
14 | if (contentType === 'application/x-www-form-urlencoded' && req.body) {
15 |
16 | return q.when(forEachable.join(req.body), function (body) {
17 | req.body = querystring.parse(body);
18 |
19 | return nextApp(req);
20 | });
21 | }
22 | }
23 |
24 | return nextApp(req);
25 | });
26 |
--------------------------------------------------------------------------------
/lib/middleware/parseJson.js:
--------------------------------------------------------------------------------
1 | var middleware = require('../middleware')
2 | , forEachable = require('../forEachable')
3 | , q = require('../q');
4 |
5 | module.exports = middleware(function (req, nextApp) {
6 | var contentType = req.headers['content-type'];
7 |
8 | if (contentType && req.body) {
9 | if (contentType === 'application/json') {
10 |
11 | return q.when(forEachable.join(req.body), function success(body) {
12 | req.body = JSON.parse(body);
13 |
14 | return nextApp(req);
15 | });
16 | }
17 | }
18 |
19 | return nextApp(req);
20 | });
--------------------------------------------------------------------------------
/lib/middleware/parted.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , ForEachStream = require('../forEachStream')
3 | , parted = require('parted');
4 |
5 | module.exports = function partedMiddleware(opts, nextApp) {
6 | if (nextApp === undefined) {
7 | if (typeof opts === 'function') {
8 | nextApp = opts;
9 | opts = undefined;
10 | } else {
11 | return function (nextApp) {
12 | return partedMiddleware(opts, nextApp);
13 | };
14 | }
15 | }
16 |
17 | var fn = parted(opts);
18 |
19 | return function (req) {
20 | var deferred = q.defer()
21 | , oldBody = req.body
22 | , emitter;
23 |
24 | if (oldBody === undefined) {
25 | return nextApp(req);
26 | }
27 |
28 | emitter = new ForEachStream(oldBody);
29 | req.on = emitter.on.bind(emitter);
30 | req.pipe = emitter.pipe.bind(emitter);
31 |
32 | delete req.body;
33 |
34 | fn(req, null, function () {
35 | q.when(nextApp(req), function (resp) {
36 | deferred.resolve(resp);
37 | }, deferred.reject);
38 | });
39 |
40 | return deferred.promise;
41 | };
42 | };
--------------------------------------------------------------------------------
/lib/middleware/renderView.js:
--------------------------------------------------------------------------------
1 | var middleware = require('./middleware');
2 |
3 | /*
4 | * Renders `template` using `viewEngine`.
5 | * ViewEngine#respond is called with the results of
6 | * the `next` apps response.
7 | *
8 | * Example:
9 | *
10 | * var router = bogart.router();
11 | * router.get('/', bogart.middleware.renderView('index.html'), function (req) {
12 | * return { locals: { title: 'Bogart' }, layout: true };
13 | * });
14 | *
15 | * @param {ViewEngine} viewEngine
16 | * @param {String} template The template to render.
17 | */
18 | function RenderView(viewEngine, template) {
19 | return middleware(req, next) {
20 | return next(req).then(function (renderOpts) {
21 | return viewEngine.respond(template, renderOpts);
22 | });
23 | };
24 | }
25 |
26 | module.exports = RenderView;
27 |
--------------------------------------------------------------------------------
/lib/middleware/session/cookieDataProvider.js:
--------------------------------------------------------------------------------
1 | var uuid = require("node-uuid"),
2 | security = require("../../security"),
3 | util = require("../../util"),
4 | SESSION_DATA_COOKIE = "bogart_session_data",
5 | DEFAULT_SESSION_LIFETIME = 1800 /* 30 mins */;
6 |
7 | function CookieDataProvider(config) {
8 | config = config || {};
9 | config.lifetime = config.lifetime || DEFAULT_SESSION_LIFETIME;
10 |
11 | if (!config.secret) {
12 | throw new Error('CookieDataProvider `secret` is required: new CookieDataProvider({ secret: "my-super-secret" })');
13 | }
14 |
15 | this.encrypt = config.encrypt || security.encrypt;
16 | this.decrypt = config.decrypt || security.decrypt;
17 |
18 | this.config = config;
19 | this.cookieKey = encodeURIComponent(this.encrypt(SESSION_DATA_COOKIE, this.config.secret));
20 | }
21 |
22 | /**
23 | * Called once at the beginning of the request
24 | */
25 | CookieDataProvider.prototype.loadSession = function(req, sessionId) {
26 | var cookie = util.parseCookies(req)[this.cookieKey];
27 |
28 | var decryptedCookie, session;
29 | try {
30 | decryptedCookie = this.decrypt(decodeURIComponent(cookie), this.config.secret);
31 | session = cookie ? JSON.parse(decryptedCookie) : {};
32 | } catch (err) {
33 | session = {};
34 | }
35 |
36 | return session;
37 | };
38 |
39 | /**
40 | * Called once at the end of the request
41 | */
42 | CookieDataProvider.prototype.save = function(req, res, sessionId) {
43 | return this.setCookie(req, res, sessionId, this.config.lifetime);
44 | };
45 |
46 | CookieDataProvider.prototype.destroy = function (req, res, sessionId) {
47 | return this.setCookie(req, res, sessionId, -1);
48 | };
49 |
50 | CookieDataProvider.prototype.setCookie = function (req, res, sessionId, expiresInSeconds) {
51 | var cookieKey = this.cookieKey;
52 | var secret = this.config.secret;
53 |
54 | res = res || {};
55 | res.headers = res.headers || {};
56 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
57 |
58 | var expires = new Date();
59 | expires.setSeconds(expires.getSeconds() + expiresInSeconds);
60 |
61 | var sData = expiresInSeconds > 0 ? JSON.stringify(req.env.session) : '{}';
62 |
63 | var cookie = cookieKey + "=" + encodeURIComponent(this.encrypt(sData, secret)) + "; Path=/; Expires=" + expires.toUTCString() + ";";
64 |
65 | res = util.ensureSetCookieArray(res);
66 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"].filter(function(el) {
67 | return !(new RegExp("^" + cookieKey + "=").test(el));
68 | });
69 | res.headers["Set-Cookie"].push(cookie);
70 |
71 | return res;
72 | }
73 |
74 | module.exports = CookieDataProvider;
75 |
--------------------------------------------------------------------------------
/lib/middleware/session/idProvider.js:
--------------------------------------------------------------------------------
1 | var uuid = require("node-uuid"),
2 | security = require("../../security"),
3 | util = require("../../util"),
4 | format = require('util').format,
5 | SESSION_ID_COOKIE = "bogart_session_id",
6 | DEFAULT_SESSION_LIFETIME = 1800;
7 |
8 | exports.IdProvider = IdProvider;
9 |
10 | function IdProvider(config) {
11 | config = config || {};
12 | config.lifetime = config.lifetime || DEFAULT_SESSION_LIFETIME;
13 |
14 | this.config = config;
15 | this.cookieKey = encodeURIComponent(security.encrypt(SESSION_ID_COOKIE, this.config.secret));
16 | }
17 |
18 | /**
19 | * Called once at the beginning of each request.
20 | */
21 | IdProvider.prototype.getSessionId = function(req) {
22 | var cookie = util.parseCookies(req)[this.cookieKey];
23 | var id;
24 | if (!cookie) {
25 | id = uuid();
26 | } else {
27 | id = security.decrypt(decodeURIComponent(cookie), this.config.secret);
28 | }
29 | return id;
30 | };
31 |
32 |
33 | /**
34 | * Modifies the response to include the SessionId
35 | *
36 | * @param {Object} req JSGI Request
37 | * @param {Object} res JSGI Response
38 | * @param {String} sessionId Unique identifier of the Session.
39 | *
40 | * @returns {Object} JSGI Response
41 | */
42 | IdProvider.prototype.save = function(req, res, sessionId) {
43 | return setCookie(req, res, sessionId, this.config.lifetime, this.config.secret, this.cookieKey);
44 | };
45 |
46 | IdProvider.prototype.destroy = function (req, res, sessionId) {
47 | return setCookie(req, res, '', -1, this.config.secret, this.cookieKey);
48 | };
49 |
50 | function setCookie(req, res, sessionId, expiresInSeconds, secret, cookieKey) {
51 | res = res || {};
52 | res.headers = res.headers || {};
53 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"] || [];
54 | var expires = new Date();
55 | expires.setSeconds(expires.getSeconds() + expiresInSeconds);
56 |
57 | var cookie = sessionCookie(sessionId, secret, expires, cookieKey);
58 |
59 | res = util.ensureSetCookieArray(res);
60 | res.headers["Set-Cookie"] = res.headers["Set-Cookie"].filter(isNotSessionIdCookie(cookieKey));
61 | res.headers["Set-Cookie"].push(cookie);
62 |
63 | return res;
64 | }
65 |
66 | function isNotSessionIdCookie(cookieKey) {
67 | return function (el) {
68 | return !(new RegExp("^" + cookieKey + "=").test(el));
69 | }
70 | }
71 |
72 | function sessionCookie(sessionId, secret, expires, cookieKey) {
73 | return format("%s=%s; Path=/; Expires=%s;",
74 | cookieKey,
75 | encodeSessionId(sessionId, secret),
76 | expires.toUTCString());
77 | }
78 |
79 | function encodeSessionId(sessionId, secret) {
80 | return encodeURIComponent(security.encrypt(sessionId, secret));
81 | }
82 |
--------------------------------------------------------------------------------
/lib/middleware/session/session.js:
--------------------------------------------------------------------------------
1 | var Q = require('../../q'),
2 | when = Q.when,
3 | DefaultIdProvider = require('./idProvider').IdProvider,
4 | CookieDataProvider = require('./cookieDataProvider');
5 |
6 | exports.Session = function (config, nextApp) {
7 | if (nextApp === undefined) {
8 |
9 | if (typeof config === 'function') {
10 | nextApp = config;
11 | config = {};
12 | } else {
13 | return function (nextApp) {
14 | return exports.Session(config, nextApp);
15 | };
16 | }
17 | }
18 |
19 | config = config || {};
20 | config.options = config.options || {};
21 | config.options.idProvider = config.options.idProvider || {};
22 | config.options.store = config.options.store || {};
23 | config.options.idProvider.lifetime = config.lifetime || undefined;
24 | config.options.store.lifetime = config.lifetime || undefined;
25 |
26 | if (config.encryptionKey) {
27 | warnConfigChange('encryptionKey', 'secret');
28 | config.secret = config.encryptionKey;
29 | }
30 |
31 | if (config.dataProvider) {
32 | warnConfigChange('dataProvider', 'store');
33 | config.store = config.dataProvider;
34 | }
35 |
36 | if (config.options.dataProvider) {
37 | warnConfigChange('options.dataProvider', 'options.store');
38 | config.options.store = config.options.dataProvider;
39 | }
40 |
41 | if (config.secret) {
42 | config.options.idProvider.secret = config.secret;
43 | config.options.store.secret = config.secret;
44 | }
45 |
46 | var idProvider = config.idProvider || new DefaultIdProvider(config.options.idProvider);
47 | var dataProvider = config.store || new CookieDataProvider(config.options.store);
48 |
49 | return function (req) {
50 | var sessionId = idProvider.getSessionId(req);
51 |
52 | return when(dataProvider.loadSession(req, sessionId), function (session) {
53 | req.env = req.env || {};
54 | req.env.session = session;
55 |
56 | // set up the session function
57 | req.session = function () {
58 | var args = Array.prototype.slice.call(arguments);
59 | if (args.length === 2) {
60 | session[args[0]] = args[1];
61 | } else {
62 | return session[args[0]];
63 | }
64 | };
65 |
66 | req.session.keys = function () {
67 | return Object.keys(session);
68 | };
69 |
70 | req.session.hasKey = function (key) {
71 | return this.keys().filter(function (k) { return k === key; }).length > 0;
72 | };
73 |
74 | req.session.remove = function (key) {
75 | delete session[key];
76 | };
77 |
78 | req.session.destroy = function () {
79 | req.session._destroy = true;
80 | };
81 |
82 | return when(nextApp(req), function (res) {
83 | if (req.session._destroy === true) {
84 | res = idProvider.destroy(req, res, sessionId) || res;
85 | return when(dataProvider.destroy(req, res, sessionId), function (dpResp) {
86 | return dpResp || res;
87 | });
88 | } else {
89 | res = idProvider.save(req, res, sessionId) || res;
90 |
91 | return when(dataProvider.save(req, res, sessionId), function (dpResp) {
92 | return dpResp || res;
93 | });
94 | }
95 | });
96 | });
97 | };
98 | };
99 |
100 | function warnConfigChange(oldProperty, newProperty) {
101 | console.warn('The bogart.session configuration `'+oldProperty+'` has been changed to `'+newProperty+'`. \
102 | Please update your configuration.')
103 | }
104 |
105 | exports.Session.DefaultIdProvider = DefaultIdProvider;
106 | exports.Session.DefaultDataProvider = CookieDataProvider;
--------------------------------------------------------------------------------
/lib/middleware/stringReturnAdapter.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , merge = require('../util').merge;
3 |
4 | module.exports = function stringReturnAdapter(responseDefaults, nextApp) {
5 | if (nextApp === undefined) {
6 | if (typeof responseDefaults === 'function') {
7 | nextApp = responseDefaults;
8 | responseDefaults = {
9 | status: 200,
10 | headers: { 'Content-Type': 'text/html' }
11 | };
12 | } else {
13 | return function (nextApp) {
14 | return stringReturnAdapter(responseDefaults, nextApp);
15 | };
16 | }
17 | }
18 |
19 | return function (req) {
20 | return q.when(nextApp(req), function (resp) {
21 | if (typeof resp === 'string') {
22 | return merge({}, responseDefaults, { body: [ resp ] });
23 | }
24 |
25 | return resp;
26 | });
27 | };
28 | };
--------------------------------------------------------------------------------
/lib/middleware/twitter.js:
--------------------------------------------------------------------------------
1 | var middleware = require('../middleware')
2 | , _ = require('underscore');
3 |
4 | function parseTwitterProfile(body) {
5 | if (body) {
6 | var o = JSON.parse(body),
7 | profile = {
8 | provider: 'twitter',
9 | id: o.id,
10 | userName: o.screen_name,
11 | email: o.screen_name,
12 | imageLocation: o.profile_image_url
13 | };
14 | if (o.name) {
15 | var nameSplit = o.name.split(' ');
16 | profile.givenName = nameSplit[0];
17 | profile.surName = nameSplit[1];
18 | }
19 | if (o.location) {
20 | var locationSplit = o.location.split(',');
21 | profile.city = locationSplit[0];
22 | profile.state = locationSplit[1].trim();
23 | }
24 | return profile;
25 | }
26 | }
27 |
28 | module.exports = twitterMiddleware;
29 |
30 | function twitterMiddleware(config, nextApp) {
31 | if (nextApp === undefined) {
32 | return function (nextApp) {
33 | return twitterMiddleware(config, nextApp);
34 | };
35 | }
36 |
37 | var twitter_strategy = {
38 | authorizationURL: 'https://api.twitter.com/oauth/authorize',
39 | requestTokenURL: 'https://api.twitter.com/oauth/request_token',
40 | accessTokenURL: 'https://api.twitter.com/oauth/access_token',
41 | resourceURL: 'https://api.twitter.com/1.1/users/show.json',
42 | parse: twitterMiddleware.parseTwitterProfile,
43 | resourceURLParams: [ 'user_id' ]
44 | };
45 |
46 | var oauthOptions = _.extend(twitter_strategy, config);
47 |
48 | return middleware.oauth(oauthOptions, nextApp);
49 | };
50 |
51 | twitterMiddleware.parseTwitterProfile = parseTwitterProfile;
52 |
--------------------------------------------------------------------------------
/lib/middleware/validateResponse.js:
--------------------------------------------------------------------------------
1 | var q = require('../q')
2 | , middleware = require('../middleware');
3 |
4 | /**
5 | * Validates that the response from nextApp is a valid JSGI response.
6 | * Rejects the promise if the response is invalid.
7 | *
8 | * @param {Function} nextApp The next application in the JSGI chain.
9 | * @returns {Function} JSGI response.
10 | */
11 | module.exports = middleware(function (req, nextApp) {
12 | return q.whenCall(function() { return nextApp(req); }, function(resp) {
13 | if (!resp) {
14 | return q.reject('Response must be an object.');
15 | }
16 |
17 | if (!resp.body) {
18 | return q.reject('Response must have a body property.');
19 | }
20 |
21 | if (!resp.body.forEach) {
22 | return q.reject('Response body must have a forEach method.');
23 | }
24 |
25 | if (typeof resp.body.forEach !== 'function') {
26 | return q.reject('Response body has a forEach method but the forEach method is not a function.');
27 | }
28 |
29 | if (!resp.status) {
30 | return q.reject('Response must have a status property.');
31 | }
32 |
33 | if (typeof resp.status.constructor !== '[Function: Number]') {
34 | return q.reject('Response has a status property but the status property must be a number.');
35 | }
36 |
37 | return resp;
38 | }, function(err) {
39 | // Workaround for fact that whenCall calls rejectCallback even if one is not provided.
40 | throw err;
41 | });
42 | });
--------------------------------------------------------------------------------
/lib/mimetypes.js:
--------------------------------------------------------------------------------
1 |
2 | // -- tlrobinson
3 |
4 | /**
5 | * @module
6 | */
7 |
8 | /*whatsupdoc*/
9 |
10 | // returns MIME type for extension, or fallback, or octet-steam
11 | /**
12 | * @param {String} extension a file extension, including the prefix
13 | * `"."`.
14 | * @param {String} fallback a MIME type to use if no MIME type
15 | * corresponds to the prefix. Defaults to
16 | * `"application/octet-stream"`.
17 | * @returns {String} a MIME type
18 | */
19 | exports.mimeType = function (extension, fallback) {
20 | return exports.mimeTypes[extension.toLowerCase()] ||
21 | fallback ||
22 | "application/octet-stream";
23 | };
24 |
25 | // List of most common mime-types, stolen from Rack.
26 | /**
27 | * {Object * String} an object mapping file extensions to MIME types.
28 | */
29 | exports.mimeTypes = {
30 | ".3gp" : "video/3gpp",
31 | ".a" : "application/octet-stream",
32 | ".ai" : "application/postscript",
33 | ".aif" : "audio/x-aiff",
34 | ".aiff" : "audio/x-aiff",
35 | ".asc" : "application/pgp-signature",
36 | ".asf" : "video/x-ms-asf",
37 | ".asm" : "text/x-asm",
38 | ".asx" : "video/x-ms-asf",
39 | ".atom" : "application/atom+xml",
40 | ".au" : "audio/basic",
41 | ".avi" : "video/x-msvideo",
42 | ".bat" : "application/x-msdownload",
43 | ".bin" : "application/octet-stream",
44 | ".bmp" : "image/bmp",
45 | ".bz2" : "application/x-bzip2",
46 | ".c" : "text/x-c",
47 | ".cab" : "application/vnd.ms-cab-compressed",
48 | ".cc" : "text/x-c",
49 | ".chm" : "application/vnd.ms-htmlhelp",
50 | ".class" : "application/octet-stream",
51 | ".com" : "application/x-msdownload",
52 | ".conf" : "text/plain",
53 | ".cpp" : "text/x-c",
54 | ".crt" : "application/x-x509-ca-cert",
55 | ".css" : "text/css",
56 | ".csv" : "text/csv",
57 | ".cxx" : "text/x-c",
58 | ".deb" : "application/x-debian-package",
59 | ".der" : "application/x-x509-ca-cert",
60 | ".diff" : "text/x-diff",
61 | ".djv" : "image/vnd.djvu",
62 | ".djvu" : "image/vnd.djvu",
63 | ".dll" : "application/x-msdownload",
64 | ".dmg" : "application/octet-stream",
65 | ".doc" : "application/msword",
66 | ".dot" : "application/msword",
67 | ".dtd" : "application/xml-dtd",
68 | ".dvi" : "application/x-dvi",
69 | ".ear" : "application/java-archive",
70 | ".eml" : "message/rfc822",
71 | ".eps" : "application/postscript",
72 | ".exe" : "application/x-msdownload",
73 | ".f" : "text/x-fortran",
74 | ".f77" : "text/x-fortran",
75 | ".f90" : "text/x-fortran",
76 | ".flv" : "video/x-flv",
77 | ".for" : "text/x-fortran",
78 | ".gem" : "application/octet-stream",
79 | ".gemspec" : "text/x-script.ruby",
80 | ".gif" : "image/gif",
81 | ".gz" : "application/x-gzip",
82 | ".h" : "text/x-c",
83 | ".hh" : "text/x-c",
84 | ".htm" : "text/html",
85 | ".html" : "text/html",
86 | ".ico" : "image/vnd.microsoft.icon",
87 | ".ics" : "text/calendar",
88 | ".ifb" : "text/calendar",
89 | ".iso" : "application/octet-stream",
90 | ".jar" : "application/java-archive",
91 | ".java" : "text/x-java-source",
92 | ".jnlp" : "application/x-java-jnlp-file",
93 | ".jpeg" : "image/jpeg",
94 | ".jpg" : "image/jpeg",
95 | ".js" : "application/javascript",
96 | ".json" : "application/json",
97 | ".log" : "text/plain",
98 | ".m3u" : "audio/x-mpegurl",
99 | ".m4v" : "video/mp4",
100 | ".man" : "text/troff",
101 | ".manifest": "text/cache-manifest",
102 | ".mathml" : "application/mathml+xml",
103 | ".mbox" : "application/mbox",
104 | ".mdoc" : "text/troff",
105 | ".me" : "text/troff",
106 | ".mid" : "audio/midi",
107 | ".midi" : "audio/midi",
108 | ".mime" : "message/rfc822",
109 | ".mml" : "application/mathml+xml",
110 | ".mng" : "video/x-mng",
111 | ".mov" : "video/quicktime",
112 | ".mp3" : "audio/mpeg",
113 | ".mp4" : "video/mp4",
114 | ".mp4v" : "video/mp4",
115 | ".mpeg" : "video/mpeg",
116 | ".mpg" : "video/mpeg",
117 | ".ms" : "text/troff",
118 | ".msi" : "application/x-msdownload",
119 | ".odp" : "application/vnd.oasis.opendocument.presentation",
120 | ".ods" : "application/vnd.oasis.opendocument.spreadsheet",
121 | ".odt" : "application/vnd.oasis.opendocument.text",
122 | ".ogg" : "application/ogg",
123 | ".p" : "text/x-pascal",
124 | ".pas" : "text/x-pascal",
125 | ".pbm" : "image/x-portable-bitmap",
126 | ".pdf" : "application/pdf",
127 | ".pem" : "application/x-x509-ca-cert",
128 | ".pgm" : "image/x-portable-graymap",
129 | ".pgp" : "application/pgp-encrypted",
130 | ".pkg" : "application/octet-stream",
131 | ".pl" : "text/x-script.perl",
132 | ".pm" : "text/x-script.perl-module",
133 | ".png" : "image/png",
134 | ".pnm" : "image/x-portable-anymap",
135 | ".ppm" : "image/x-portable-pixmap",
136 | ".pps" : "application/vnd.ms-powerpoint",
137 | ".ppt" : "application/vnd.ms-powerpoint",
138 | ".ps" : "application/postscript",
139 | ".psd" : "image/vnd.adobe.photoshop",
140 | ".py" : "text/x-script.python",
141 | ".qt" : "video/quicktime",
142 | ".ra" : "audio/x-pn-realaudio",
143 | ".rake" : "text/x-script.ruby",
144 | ".ram" : "audio/x-pn-realaudio",
145 | ".rar" : "application/x-rar-compressed",
146 | ".rb" : "text/x-script.ruby",
147 | ".rdf" : "application/rdf+xml",
148 | ".roff" : "text/troff",
149 | ".rpm" : "application/x-redhat-package-manager",
150 | ".rss" : "application/rss+xml",
151 | ".rtf" : "application/rtf",
152 | ".ru" : "text/x-script.ruby",
153 | ".s" : "text/x-asm",
154 | ".sgm" : "text/sgml",
155 | ".sgml" : "text/sgml",
156 | ".sh" : "application/x-sh",
157 | ".sig" : "application/pgp-signature",
158 | ".snd" : "audio/basic",
159 | ".so" : "application/octet-stream",
160 | ".svg" : "image/svg+xml",
161 | ".svgz" : "image/svg+xml",
162 | ".swf" : "application/x-shockwave-flash",
163 | ".t" : "text/troff",
164 | ".tar" : "application/x-tar",
165 | ".tbz" : "application/x-bzip-compressed-tar",
166 | ".tcl" : "application/x-tcl",
167 | ".tex" : "application/x-tex",
168 | ".texi" : "application/x-texinfo",
169 | ".texinfo" : "application/x-texinfo",
170 | ".text" : "text/plain",
171 | ".tif" : "image/tiff",
172 | ".tiff" : "image/tiff",
173 | ".torrent" : "application/x-bittorrent",
174 | ".tr" : "text/troff",
175 | ".txt" : "text/plain",
176 | ".vcf" : "text/x-vcard",
177 | ".vcs" : "text/x-vcalendar",
178 | ".vrml" : "model/vrml",
179 | ".war" : "application/java-archive",
180 | ".wav" : "audio/x-wav",
181 | ".wma" : "audio/x-ms-wma",
182 | ".wmv" : "video/x-ms-wmv",
183 | ".wmx" : "video/x-ms-wmx",
184 | ".wrl" : "model/vrml",
185 | ".wsdl" : "application/wsdl+xml",
186 | ".xbm" : "image/x-xbitmap",
187 | ".xhtml" : "application/xhtml+xml",
188 | ".xls" : "application/vnd.ms-excel",
189 | ".xml" : "application/xml",
190 | ".xpm" : "image/x-xpixmap",
191 | ".xsl" : "application/xml",
192 | ".xslt" : "application/xslt+xml",
193 | ".yaml" : "text/yaml",
194 | ".yml" : "text/yaml",
195 | ".zip" : "application/zip"
196 | };
197 |
198 |
--------------------------------------------------------------------------------
/lib/q.js:
--------------------------------------------------------------------------------
1 | var q = require('q')
2 | , slice = Array.prototype.slice;
3 |
4 | /**
5 | * Wraps a Node.JS style asynchronous function `function(err, result) {}`
6 | * to return a `Promise`.
7 | *
8 | * @param {Function} nodeAsyncFn A node style async function expecting a callback as its last parameter.
9 | * @param {Object} context Optional, if provided nodeAsyncFn is run with `this` being `context`.
10 | *
11 | * @returns {Function} A function that returns a promise.
12 | */
13 | q.promisify = function(nodeAsyncFn, context) {
14 | return function() {
15 | var defer = q.defer()
16 | , args = slice.call(arguments);
17 |
18 | args.push(function(err, val) {
19 | if (err !== null) {
20 | return defer.reject(err);
21 | }
22 |
23 | return defer.resolve(val);
24 | });
25 |
26 | nodeAsyncFn.apply(context || {}, args);
27 |
28 | return defer.promise;
29 | };
30 | };
31 |
32 | if (!q.execute) {
33 | /**
34 | * Runs a Node.JS style asynchronous function `function(err, result) {}`, but returns a Promise instead.
35 | * @param {Function} Node.JS compatible async function which takes a callback as its last argument
36 | * @returns {Promise} A promise for the return value from the callback from the function
37 | */
38 | q.execute = function(asyncFunction){
39 | var defer = q.defer()
40 | , args = slice.call(arguments, 1);
41 |
42 | args.push(function(err, result){
43 | if(err !== null) {
44 | defer.reject(err);
45 | }
46 | else {
47 | if(arguments.length > 2){
48 | // Return an array if multiple success values.
49 | Array.prototype.shift.call(arguments, 1);
50 | defer.resolve(arguments);
51 | }
52 | else{
53 | defer.resolve(result);
54 | }
55 | }
56 | });
57 | asyncFunction.apply(this, args);
58 | return defer.promise;
59 | };
60 | }
61 |
62 | if (!q.resolve) {
63 | q.resolve = function(val) {
64 | var deferred = q.defer();
65 |
66 | deferred.resolve(val);
67 |
68 | return deferred.promise;
69 | };
70 | }
71 |
72 | if (!q.reject) {
73 | q.reject = function(reason) {
74 | var deferred = q.defer();
75 |
76 | deferred.reject(reason);
77 |
78 | return deferred.promise;
79 | }
80 | }
81 |
82 | if (!q.whenCall) {
83 | q.whenCall = function(callback, resolvedCallback, errorCallback, progressCallback) {
84 | try {
85 | return q.when(callback(), resolvedCallback, errorCallback, progressCallback);
86 | } catch (err) {
87 | return errorCallback(err);
88 | }
89 | }
90 | }
91 |
92 | module.exports = q;
93 |
--------------------------------------------------------------------------------
/lib/request.js:
--------------------------------------------------------------------------------
1 |
2 | var util = require('./util');
3 |
4 | module.exports = function(jsgiReq) {
5 | return Object.create(jsgiReq, {
6 | search: getter(function() {
7 | return util.extractSearch(jsgiReq);
8 | }),
9 | isXMLHttpRequest: getter(function() {
10 | return !util.no(this.headers['x-requested-with']);
11 | }),
12 | routeParams: {
13 | value: {},
14 | enumerable: true
15 | },
16 | params: getter(function() {
17 | return util.merge({}, this.routeParams, this.search, this.body);
18 | })
19 | });
20 | };
21 |
22 | /**
23 | * Helper function to create property descriptor that is enumerable
24 | * and has a getter.
25 | *
26 | * @param fn {Function} The getter function.
27 | * @api private
28 | */
29 | function getter(fn) {
30 | return {
31 | get: fn,
32 | enumerable: true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/router.js:
--------------------------------------------------------------------------------
1 | var
2 | util = require('./util'),
3 | EventEmitter = require('events').EventEmitter,
4 | Q = require('./q'),
5 | when = Q.when,
6 | inherits = require('util').inherits,
7 | slice = Array.prototype.slice,
8 | _ = require('underscore');
9 |
10 | var
11 | httpMethod = {
12 | GET: "get",
13 | POST: "post",
14 | PUT: "put",
15 | DELETE: "delete"
16 | },
17 | restMethod = {
18 | SHOW: httpMethod.GET,
19 | CREATE: httpMethod.POST,
20 | UPDATE: httpMethod.PUT,
21 | DESTROY: httpMethod.DELETE
22 | },
23 | PATH_PARAMETER_REPLACEMENT = "([^\/\?]+)",
24 | PATH_PARAMETERS = /:([\w\d]+)/g;
25 |
26 | exports.bogartEvent = {
27 | BEFORE_ADD_ROUTE: "beforeAddRoute",
28 | AFTER_ADD_ROUTE: "afterAddRoute"
29 | };
30 |
31 | function defaultNotFoundApp(req) {
32 | var body = 'Not Found';
33 | if (req.pathInfo) {
34 | body += ': ' + req.pathInfo;
35 | }
36 |
37 | return {
38 | status: 404,
39 | body: [body],
40 | headers: { 'Content-Length': Buffer.byteLength(body, 'utf-8'), 'Content-Type': 'text/html' }
41 | };
42 | }
43 |
44 | exports.Router = Router;
45 | function Router() {
46 | if (!this.respond) {
47 | return new Router();
48 | }
49 |
50 | var settings = {}
51 |
52 | var app = function (notFoundApp) {
53 | if (notFoundApp) {
54 | if (typeof notFoundApp !== 'function') {
55 | throw new Error('Invalid argument: router expects a function.')
56 | }
57 |
58 | app.nextApp = notFoundApp;
59 | } else {
60 | app.nextApp = app.nextApp || defaultNotFoundApp;
61 | }
62 |
63 | return function (req) {
64 | return when(req, function(req) {
65 | try {
66 | return app.respond(require('./request')(req));
67 | } catch (err) {
68 | return Q.reject(err);
69 | }
70 | });
71 | };
72 | };
73 |
74 | app.routes = {};
75 | app.beforeCallbacks = [];
76 | app.afterCallbacks = [];
77 |
78 | app.setting = function(name, val) {
79 | if (val === undefined) {
80 | return settings[name];
81 | }
82 |
83 | settings[name] = val;
84 | return this;
85 | };
86 |
87 | EventEmitter.call(app);
88 |
89 | _.extend(app, this.__proto__);
90 | _.extend(app, this.__proto__.__proto__);
91 |
92 | return app;
93 | }
94 |
95 | inherits(Router, EventEmitter);
96 |
97 | /**
98 | * Register a callback to happen before bogart handlers are invoked to
99 | * handle a request. Multiple 'before' callbacks may be registered.
100 | *
101 | * @param {Function} cb Callback to happen before route handler is invoked.
102 | */
103 | Router.prototype.before = function(cb) {
104 | this.beforeCallbacks.push(cb);
105 | };
106 |
107 | /**
108 | * Register a callback to happen after bogart handlers are invoked to
109 | * handle a request. Multiple 'after' callbacks may be registered.
110 | *
111 | * @param {Function} cb Callback to happen after route handler is invoked.
112 | */
113 | Router.prototype.after = function(cb) {
114 | this.afterCallbacks.push(cb);
115 | };
116 |
117 | /**
118 | * Register a route
119 | * @param {String} method Http Verb e.g. 'GET', 'POST', 'PUT', 'DELETE'
120 | * @param {String} path Path for the route
121 | * @param {Function} handler Function to execute when the route is accessed
122 | */
123 | Router.prototype.route = function(method, path /*, handlers... */) {
124 | var paramNames, route, originalPath = path
125 | , args = slice.call(arguments);
126 |
127 | method = args.shift();
128 | path = args.shift();
129 | apps = args;
130 |
131 | if (path.constructor === String) {
132 | paramNames = path.match(PATH_PARAMETERS) || [];
133 | paramNames = paramNames.map(function(x) { return x.substring(1); });
134 |
135 | path = new RegExp("^"+path.replace(/\./, '\\.').replace(/\*/g, '(.+)').replace(PATH_PARAMETERS, PATH_PARAMETER_REPLACEMENT)+'$');
136 | }
137 |
138 | route = makeRoute({ path: path, paramNames: paramNames, apps: apps, originalPath: originalPath });
139 |
140 | this.emit(exports.bogartEvent.BEFORE_ADD_ROUTE, this, route);
141 |
142 | this.routes[method] = this.routes[method] || [];
143 | this.routes[method].push(route);
144 |
145 | this.emit(exports.bogartEvent.AFTER_ADD_ROUTE, this, route);
146 |
147 | return this;
148 | };
149 |
150 | Router.prototype.handler = function(verb, path) {
151 | verb = verb.toLowerCase();
152 | var route;
153 |
154 | if (this.routes[verb]) {
155 | for (var i=0;i indx) {
298 | req.routeParams[route.paramNames[indx]] = val;
299 | } else if (val !== undefined) {
300 | req.routeParams.splat = req.routeParams.splat || [];
301 | req.routeParams.splat.push(val);
302 | }
303 | });
304 | }
305 | }
306 | }
307 | });
308 | }
--------------------------------------------------------------------------------
/lib/security.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto'),
2 | secretCipherPass = "SDFOIJSDFJLKJ";
3 |
4 | exports.hash = function(value) {
5 | var shasum = crypto.createHash('sha1');
6 | shasum.update(value);
7 | return shasum.digest('hex');
8 | }
9 |
10 | exports.encrypt = function(valueToEncrypt) {
11 | var key = arguments[1] || secretCipherPass;
12 | var cipher = crypto.createCipher('aes-256-cbc', key);
13 |
14 | var str = cipher.update(valueToEncrypt, 'utf8', 'hex');
15 | str += cipher.final('hex') || '';
16 | return str;
17 | }
18 |
19 | exports.decrypt = function(valueToDecrypt) {
20 | var key = arguments[1] || secretCipherPass;
21 | var cipher = crypto.createDecipher('aes-256-cbc', key);
22 |
23 |
24 | var str = cipher.update(valueToDecrypt, 'hex', 'utf8');
25 | str += cipher.final('utf8') || '';
26 | return str;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/stream.js:
--------------------------------------------------------------------------------
1 | var Q = require('./q')
2 | , Stream = require('stream').Stream
3 | , util = require('util')
4 | , ForEachStream = require('./forEachStream');
5 |
6 |
7 | function pumpForEachStream(src, dest) {
8 | var deferred = Q.defer()
9 | , forEachStream = new ForEachStream(src)
10 | , isRejected = false;
11 |
12 | forEachStream.pipe(dest);
13 |
14 | dest.on('finish', function() {
15 | if (!isRejected) {
16 | deferred.resolve();
17 | }
18 | });
19 |
20 | forEachStream.on('error', function(err) {
21 | deferred.reject(err);
22 | });
23 |
24 | return deferred.promise;
25 | }
26 |
27 | function pumpStream(src, dest) {
28 | var deferred = Q.defer()
29 | , isRejected = false;
30 |
31 | src.pipe(dest);
32 |
33 | dest.on('finish', function() {
34 | if (!isRejected) {
35 | deferred.resolve();
36 | }
37 | });
38 |
39 | src.on('error', function(err) {
40 | deferred.reject(err);
41 | });
42 |
43 | return deferred.promise;
44 | }
45 |
46 | /**
47 | * Pipes data from source to dest.
48 | *
49 | * @param {ForEachable | ReadableStream} src Source of data
50 | * @param {WriteableStream} dest Write data from src to dest
51 | *
52 | * @returns {Promise} A promise that will be resolved when the pumping is completed
53 | */
54 | exports.pump = function(src, dest) {
55 | if (src.forEach) {
56 | return pumpForEachStream(src, dest);
57 | } else {
58 | return pumpStream(src, dest);
59 | }
60 | };
61 |
62 | exports.ForEachStream = require('./forEachStream');
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 |
3 | exports.no = function(value) {
4 | return value === undefined || value === null;
5 | };
6 |
7 | exports.extractBody = function(jsgiReq) {
8 | return when(jsgiReq.input, function(input) {
9 | return require('querystring').parse(input);
10 | });
11 | };
12 |
13 | exports.extractSearch = function(jsgiReq) {
14 | return require('querystring').parse(jsgiReq.queryString);
15 | };
16 |
17 | exports.merge = _.extend;
18 |
19 | /**
20 | * Taken under terms of the MIT license from: https://github.com/senchalabs/connect
21 | * Parse the given cookie string into an object.
22 | *
23 | * @param {String} str
24 | * @return {Object}
25 | * @api public
26 | */
27 | exports.parseCookies = function(arg){
28 | var str = arg.headers && arg.headers.cookie ? arg.headers.cookie : arg;
29 | if(Object.prototype.toString.call(str) !== "[object String]") {
30 | return {};
31 | }
32 | var obj = {}
33 | , pairs = str.split(/[;,] */);
34 | for (var i = 0, len = pairs.length; i < len; ++i) {
35 | var pair = pairs[i]
36 | , eqlIndex = pair.indexOf('=')
37 | , key = pair.substr(0, eqlIndex).trim()
38 | , val = pair.substr(++eqlIndex, pair.length).trim();
39 |
40 | // quoted values
41 | if ('"' == val[0]) val = val.slice(1, -1);
42 |
43 | // only assign once
44 | if (undefined == obj[key]) {
45 | val = val.replace(/\+/g, ' ');
46 | try {
47 | obj[key] = decodeURIComponent(val);
48 | } catch (err) {
49 | if (err instanceof URIError) {
50 | obj[key] = val;
51 | } else {
52 | throw err;
53 | }
54 | }
55 | }
56 | }
57 | return obj;
58 | };
59 |
60 | /**
61 | * Ensures that the Set-Cookie header in the response is an array
62 | */
63 | exports.ensureSetCookieArray = function(response) {
64 | response.headers = response.headers || {};
65 |
66 | response.headers["Set-Cookie"] = response.headers["Set-Cookie"] || [];
67 | if (Object.prototype.toString.call(response.headers["Set-Cookie"]) === "[object String]") {
68 | var existing = response.headers["Set-Cookie"];
69 | response.headers["Set-Cookie"] = [];
70 | response.headers["Set-Cookie"].push(existing);
71 | }
72 |
73 | return response;
74 | };
75 |
76 | function isOfMethod(method) {
77 | return function (req) {
78 | return req.method.toLowerCase() === method;
79 | }
80 | }
81 |
82 | exports.isOfMethod = isOfMethod;
83 |
84 | /**
85 | * Returns whether a request is a get.
86 | *
87 | * @param {Request} req
88 | * @returns {Boolean}
89 | */
90 | exports.isGet = isOfMethod('get');
91 |
92 | /**
93 | * Returns whether a request is a post.
94 | *
95 | * @param {Request} req
96 | * @returns {Boolean}
97 | */
98 | exports.isPost = isOfMethod('post');
99 |
100 | /**
101 | * Returns whether a request is a put.
102 | *
103 | * @param {Request} req
104 | * @returns {Boolean}
105 | */
106 | exports.isPut = isOfMethod('put');
107 |
108 | /**
109 | * Returns whether a request is a delete.
110 | *
111 | * @param {Request} req
112 | * @returns {Boolean}
113 | */
114 | exports.isDel = exports.isDelete = isOfMethod('delete');
115 |
--------------------------------------------------------------------------------
/lib/view.js:
--------------------------------------------------------------------------------
1 | var
2 | path = require('path'),
3 | http = require('http'),
4 | fs = require('fs'),
5 | extname = path.extname,
6 | engines = {},
7 | settings = {},
8 | _ = require('underscore'),
9 | events = require('events'),
10 | async = require('async'),
11 | q = require('./q'),
12 | when = q.when,
13 | util = require('util'),
14 | readFile = q.promisify(fs.readFile, fs);
15 |
16 | // TODO: Figure out how to make this work x-platform using the nodules 'engines' overlay
17 | //watchFile = require('fileWatcher').watchFile;
18 |
19 | var ViewEngine = exports.ViewEngine = function() {
20 | events.EventEmitter.call(this);
21 | };
22 |
23 | util.inherits(ViewEngine, events.EventEmitter);
24 |
25 | /**
26 | * @param {String} engineName name of the engine 'mustache', 'haml'
27 | * @param {String} views path to the views
28 | * @returns {ViewEngine}
29 | */
30 | exports.viewEngine = function(engineName, views, opts) {
31 | return Object.create(new ViewEngine(), {
32 | engineName: { value: engineName, enumerable: true, readonly: true },
33 | views: { value: views || path.join(setting('template directory')), enumerable: true, readonly: true },
34 | viewCache: { value: {} },
35 | opts: { value: opts || { cache: false } }
36 | });
37 | };
38 |
39 | ViewEngine.prototype.clearCache = function() {
40 | this.viewCache = {};
41 | };
42 |
43 | /**
44 | * @api private
45 | */
46 | ViewEngine.prototype.cacheView = function(view) {
47 |
48 | return when(this.read(view), function(str) {
49 | if (this.opts.cache) {
50 | this.cache(view, str);
51 | }
52 | return str;
53 | }.bind(this));
54 | };
55 |
56 | ViewEngine.prototype.read = function(view) {
57 | return readFile(isAbsolute(view) ? view : this.viewPath(view), 'utf8');
58 | };
59 |
60 | ViewEngine.prototype.cache = function(view, str) {
61 | if (str === undefined) {
62 | return this.viewCache[view];
63 | }
64 |
65 | this.viewCache[view] = str;
66 | };
67 |
68 | ViewEngine.prototype.viewPath = function(view) {
69 | return path.join(this.views, view);
70 | }
71 |
72 | /**
73 | * @api private
74 | */
75 | ViewEngine.prototype.getCacher = function(view) {
76 | return this.cache.bind(this, view);
77 | }
78 |
79 | ViewEngine.prototype.respond = function(view, opts) {
80 | var viewEngine = this;
81 |
82 | opts = opts || {};
83 |
84 | return q.when(viewEngine.render(view, opts), function(str) {
85 | var headers = _.extend({}, {
86 | 'content-type': 'text/html',
87 | 'content-length': Buffer.byteLength(str, 'utf-8')
88 | }, opts.headers);
89 | return {
90 | status: opts.status || 200,
91 | headers: headers,
92 | body: [str]
93 | };
94 | });
95 | };
96 |
97 | ViewEngine.prototype.render = function(view, opts) {
98 | opts = opts || {};
99 |
100 | opts.locals = opts.locals || {};
101 |
102 | var
103 | self = this,
104 | ext = extname(view),
105 | engine = this.engineName,
106 | renderer = engine ? exports.viewEngine.engine(engine) : exports.viewEngine.engine(ext.substring(1)),
107 | layout = opts.layout === undefined ? true : opts.layout;
108 |
109 | layout = layout === true ? 'layout' + ext : layout;
110 |
111 | return when(this.cache(view) || this.cacheView(view),
112 | function success(template) {
113 | var renderedView
114 | , cacher
115 | , jsName = setting('shared js name') || 'javascript'
116 | , namespace = setting('shared js namespace') || 'bogart';
117 |
118 | if (renderer === undefined || renderer === null) {
119 | throw new Error('Rendering engine not found for '+engine+'. Try `npm install bogart-'+engine+'`.');
120 | }
121 |
122 | if (opts._renderedShared !== true) {
123 | if (opts.locals[jsName]) {
124 | throw 'The locals property of `render` method options contains a property named "'+jsName+'" already. ' +
125 | 'The view engine settings specify this name for the shared JavaScript. Please either change the "shared js name" settings ' +
126 | 'by executing `var view = require("view"); view.setting("shared js name", "myNewName") or remove the code that is setting ' +
127 | 'this property on the opts.locals.';
128 | }
129 |
130 | opts.locals[jsName] = '';
136 |
137 | opts._renderedShared = true;
138 | }
139 |
140 | self.emit('beforeRender', self, opts, template, cacher);
141 |
142 | cacher = self.getCacher(view);
143 | renderedView = renderer(template, opts, cacher, self);
144 |
145 | self.emit('afterRender', self, opts, template, cacher);
146 |
147 | return when(renderedView, function(renderedView) {
148 | if (layout) {
149 | opts.locals.body = renderedView;
150 | opts.layout = false;
151 |
152 | return self.render(layout, opts);
153 | }
154 |
155 | return renderedView;
156 | });
157 | });
158 | };
159 |
160 | ViewEngine.prototype.partial = function(view, opts) {
161 | opts = opts || {};
162 |
163 | opts.locals = opts.locals || {};
164 |
165 | opts.partial = true;
166 | opts.layout = false;
167 |
168 | return this.render(view, opts);
169 | };
170 |
171 | /**
172 | * Share JavaScript with the client side.
173 | */
174 | ViewEngine.prototype.share = function(obj, name) {
175 | if (obj === undefined || obj === null) {
176 | throw 'Have to share something! First parameter to `ViewEngine.prototype.share` cannot be empty.';
177 | }
178 |
179 | this._shared = this._shared || {};
180 | this._shared[name] = this._shared[name] || [];
181 |
182 | if (typeof obj === 'string') {
183 | this._shared[name].push(obj);
184 | } else {
185 | this.share(stringify(obj), name);
186 | }
187 |
188 | return this;
189 | };
190 |
191 | /**
192 | * Inspect JavaScript that is shared with the client side.
193 | *
194 | * @params {String} name The key of the JavaScript to look up.
195 | * @returns {String} JavaScript shared with the client for the `name` specified.
196 | */
197 | ViewEngine.prototype.shared = function(name) {
198 | this._shared = this._shared || {};
199 | return this._shared[name].join('\n');
200 | };
201 |
202 | /**
203 | * Add a template engine
204 | *
205 | * @param {String} engineName The name of the template engine
206 | * @param {Function} render Render a template given the templaet as a string and and options object
207 | *
208 | * @returns undefined
209 | */
210 | exports.viewEngine.addEngine = function(engineName, render) {
211 | engines[engineName] = render;
212 | };
213 |
214 | /**
215 | * Remove a template engine
216 | *
217 | * @param {String} engineName The name of the template engine
218 | * @returns undefined
219 | */
220 | exports.viewEngine.removeEngine = function(engineName) {
221 | delete engines[engineName];
222 | };
223 |
224 | /**
225 | * Retrieves the render function for a registered template engine
226 | *
227 | * @param {String} engineName The name of the template engine
228 | * @returns {Function} Function registered to render templates of the type specified by engineName
229 | */
230 | exports.viewEngine.engine = function(engineName) {
231 | return engines[engineName];
232 | };
233 |
234 | exports.viewEngine.addEngine('mustache', function(str, opts, cache, viewEngine) {
235 | var partials = {};
236 |
237 | opts = _.extend({}, opts);
238 |
239 | if (opts.partials) {
240 | var defer = q.defer();
241 |
242 | async.forEach(Object.keys(opts.partials), function(k, callback) {
243 |
244 | when(viewEngine.cache(opts.partials[k]) || viewEngine.cacheView(opts.partials[k]), function(str) {
245 | partials[k] = str;
246 | callback();
247 | });
248 | }, function(err) {
249 | delete opts.partials;
250 |
251 | if (err) {
252 | defer.reject(err);
253 | } else {
254 | defer.resolve(require('mustache').to_html(str, opts.locals, partials));
255 | }
256 | });
257 |
258 | return defer.promise;
259 | }
260 |
261 | return require('mustache').to_html(str, opts.locals);
262 | });
263 |
264 | var setting = exports.setting = function(key, val) {
265 | if (val !== undefined) {
266 | settings[key] = val;
267 | return this;
268 | }
269 |
270 | return settings[key];
271 | };
272 |
273 | /**
274 | * Returns whether a path is an absolute path.
275 | */
276 | function isAbsolute(path) {
277 | var windowsDriveRe = /^[a-zA-Z]:/;
278 |
279 | return path[0] === '/' || windowsDriveRe.test(path);
280 | }
281 |
282 | /**
283 | * Returns a string representation of an object.
284 | * This method is used for sharing objects with the client.
285 | *
286 | * @param {Object} obj An object.
287 | * @returns {String} a string representation of the object.
288 | * @api private
289 | */
290 | function stringify(obj) {
291 | if (Array.isArray(obj)) {
292 | return '['+obj.map(stringify).join(',')+']';
293 | } else if (obj instanceof Date) {
294 | return 'new Date("'+obj.toString()+'")';
295 | } else if (typeof obj === 'function') {
296 | return obj.toString();
297 | } else {
298 | return JSON.stringify(obj);
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bogart",
3 | "description": "Fast JSGI web framework taking inspiration from Sinatra",
4 | "version": "0.5.27",
5 | "keywords": ["bogart", "framework", "sinatra", "REST"],
6 | "author": "Nathan Stott ",
7 | "email": "nathan.stott@whiteboard-it.com",
8 | "homepage": "https://github.com/nrstott/bogart",
9 | "main": "./lib/bogart",
10 | "directories": { "lib": "./lib" },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/nrstott/bogart"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/nrstott/bogart/issues",
17 | "email": "nrstott@gmail.com"
18 | },
19 | "dependencies": {
20 | "q": "0.9.6",
21 | "q-io": "~1.13.1",
22 | "jsgi": ">=v0.2.2",
23 | "mustache": "0.7.2",
24 | "underscore": ">=1.4.4",
25 | "node-uuid": ">=1.2.0",
26 | "parted": "git://github.com/soitgoes/parted.git#v0.1.4",
27 | "oauth": "=0.9.5",
28 | "request": "=2.2.9",
29 | "commander": "*",
30 | "mkdirp": "*",
31 | "async": "0.1.22",
32 | "sinon": "1.5.2"
33 | },
34 | "devDependencies": {
35 | "haml": ">=0.4.2",
36 | "jade": ">=0.15.4",
37 | "jasmine-node": ">=1.4.0"
38 | },
39 | "scripts": {
40 | "test": "jasmine-node --coffee spec/"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/spec/appSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 |
3 | describe 'bogart app', ->
4 | app = null
5 |
6 | beforeEach ->
7 | app = bogart.app()
8 |
9 | describe 'given a router with no parameters', ->
10 |
11 | it 'should not be started', ->
12 | expect(app.started).toBe(false)
13 |
14 | it 'should have `use` method', ->
15 | expect(typeof app.use).toBe('function')
16 |
17 | describe 'afterAddMiddleware event', ->
18 | appParam = null
19 | middlewareParam = null
20 | router = null
21 |
22 | beforeEach ->
23 | router = bogart.router()
24 |
25 | app.on 'afterAddMiddleware', (app, middleware) ->
26 | appParam = app
27 | middlewareParam = middleware
28 |
29 | app.use router
30 |
31 | it 'should raise afterAddMiddleware with correct app parameters', ->
32 | expect(appParam).toBe(app)
33 |
34 | it 'should raise afterAddMiddleware with correct middleware parameter', ->
35 | expect(middlewareParam).toBe(router)
36 |
37 | describe 'starting the app', ->
38 | server = {}
39 | result = null
40 |
41 | beforeEach ->
42 | spyOn(bogart, 'start').andReturn server
43 |
44 | app.use(->
45 | return (req) ->
46 | bogart.html 'Hello World'
47 | )
48 |
49 | result = app.start()
50 |
51 | it 'should be started', ->
52 | expect(app.started).toBe(true)
53 |
54 | it 'should have been called with correct jsgi parameters', ->
55 | expect(bogart.start).toHaveBeenCalledWith(jasmine.any(Function), { port: 8080, host: '127.0.0.1' })
56 |
57 | it 'should return server', ->
58 | expect(result).toBe(server)
59 |
60 | describe 'given JSGI options', ->
61 | router = null
62 | server = {}
63 | jsgiOpts = null
64 |
65 | beforeEach ->
66 | spyOn(bogart, 'start').andReturn server
67 |
68 | router = bogart.router()
69 | app.use router
70 |
71 | jsgiOpts = { port: 1337, host: 'whiteboard-it.com', somethingElse: 'anotherOption' }
72 | app.start jsgiOpts
73 |
74 | it 'should be started', ->
75 | expect(app.started).toBe(true)
76 |
77 | it 'should have been started with correct JSGI Options', ->
78 | expect(bogart.start).toHaveBeenCalledWith jasmine.any(Function), jsgiOpts
79 |
80 |
--------------------------------------------------------------------------------
/spec/fixtures/chrome.part:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nrstott/bogart/a6e3b01b2e6f1247dde697038101fd6fa94e76b0/spec/fixtures/chrome.part
--------------------------------------------------------------------------------
/spec/fixtures/greeting.mustache:
--------------------------------------------------------------------------------
1 | With Partial
--------------------------------------------------------------------------------
/spec/fixtures/index.mustache:
--------------------------------------------------------------------------------
1 | Hello World from Mustache
--------------------------------------------------------------------------------
/spec/fixtures/partial-test.mustache:
--------------------------------------------------------------------------------
1 | Hello World from Mustache {{>greeting}}
--------------------------------------------------------------------------------
/spec/fixtures/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nrstott/bogart/a6e3b01b2e6f1247dde697038101fd6fa94e76b0/spec/fixtures/test.jpg
--------------------------------------------------------------------------------
/spec/fixtures/text.txt:
--------------------------------------------------------------------------------
1 | Hello World
--------------------------------------------------------------------------------
/spec/helpers/JsgiRequestHelper.coffee:
--------------------------------------------------------------------------------
1 | _ = require 'underscore'
2 |
3 | class MockJsgiRequest
4 | this.root = () ->
5 | new RootRequest()
6 |
7 | constructor: (@pathInfo, @method = 'get', @headers = {}) ->
8 |
9 | class RootRequest extends MockJsgiRequest
10 | constructor: (method, headers) ->
11 | super('/', method, headers)
12 |
13 | module.exports = MockJsgiRequest
--------------------------------------------------------------------------------
/spec/middleware/facebookSpec.coffee:
--------------------------------------------------------------------------------
1 | facebook = require '../../lib/middleware/facebook'
2 |
3 | describe 'parse facebook profile', ->
4 | rawProfile = null
5 | parsedProfile = null
6 |
7 | shouldMap = (propertyName, rawValue, parsedValue) ->
8 | it "should have correct #{propertyName}", ->
9 | expect(parsedValue()).toBe(rawValue())
10 |
11 | beforeEach ->
12 | rawProfile =
13 | id: 'abc123',
14 | username: 'myusername',
15 | name: 'myname',
16 | last_name: 'last_name',
17 | first_name: 'first_name',
18 | middle_name: 'middle_name',
19 | gender: 'M',
20 | link: 'http://me.facebook.com',
21 | email: 'hello@test.com'
22 |
23 | parsedProfile = facebook.parseFacebookProfile JSON.stringify(rawProfile)
24 |
25 | shouldMap 'id', (() -> rawProfile.id), (() -> parsedProfile.id)
26 |
27 | shouldMap 'username', (() -> rawProfile.username), (() -> parsedProfile.username)
28 |
29 | shouldMap 'name', (() -> rawProfile.name), (() -> parsedProfile.displayName)
30 |
31 | shouldMap 'last_name', (() -> rawProfile.last_name), (() -> parsedProfile.name.familyName)
32 |
33 | shouldMap 'first_name', (() -> rawProfile.first_name), (() -> parsedProfile.name.givenName)
34 |
35 | shouldMap 'middle_name', (() -> rawProfile.middle_name), (() -> parsedProfile.name.middleName)
36 |
37 | shouldMap 'gender', (() -> rawProfile.gender), (() -> parsedProfile.gender)
38 |
39 | shouldMap 'profileUrl', (() -> rawProfile.link), (() -> parsedProfile.profileUrl)
40 |
41 | shouldMap 'email', (() -> rawProfile.email), (() -> parsedProfile.emails[0])
42 |
43 |
--------------------------------------------------------------------------------
/spec/middleware/session/cookieDataProviderSpec.coffee:
--------------------------------------------------------------------------------
1 | JsgiRequest = require '../../helpers/JsgiRequestHelper'
2 | CookieDataProvider = require '../../../lib/middleware/session/cookieDataProvider'
3 | uuid = require 'node-uuid'
4 | q = require '../../../lib/q'
5 |
6 | describe 'Cookie Data Provider', ->
7 |
8 | describe 'load session', ->
9 | cookieDataProvider = null
10 | sessionId = null
11 | encryptionKey = null
12 | decryptedSessionData = null
13 | session = null
14 |
15 | beforeEach ->
16 | encryptionKey = uuid()
17 | sessionId = 1293
18 |
19 | req = new JsgiRequest('/', 'get', { cookie: 'bogart_session_data={}' })
20 |
21 | encrypt = jasmine.createSpy 'encrypt'
22 | decrypt = jasmine.createSpy 'decrypt'
23 |
24 | encrypt.andReturn 'bogart_session_data'
25 |
26 | decryptedSessionData = '{}'
27 | decrypt.andReturn decryptedSessionData
28 |
29 | cookieDataProvider = new CookieDataProvider({
30 | secret: encryptionKey,
31 | encrypt: encrypt,
32 | decrypt: decrypt
33 | });
34 |
35 | session = cookieDataProvider.loadSession req, sessionId
36 |
37 | it 'should call encrypt', (done) ->
38 | q.when session, (session) ->
39 | expect(cookieDataProvider.encrypt).toHaveBeenCalled()
40 | done()
41 |
42 | it 'should call decrypt', (done) ->
43 | q.when session, (session) ->
44 | expect(cookieDataProvider.decrypt).toHaveBeenCalled()
45 | done()
46 |
47 | it 'should have correct session data', (done) ->
48 | q.when session, (session) ->
49 | expect(session).toEqual JSON.parse(decryptedSessionData)
50 | done()
51 |
52 | describe 'destroy session', ->
53 | res = null
54 | sessionId = null
55 | encrypt = null
56 |
57 | beforeEach ->
58 | sessionId = '--some-session-id--'
59 |
60 | encrypt = jasmine.createSpy 'encrypt'
61 | encrypt.andCallFake (sessionId, secret) ->
62 | sessionId
63 |
64 | cookieDataProvider = new CookieDataProvider
65 | secret: 'VERY_SECRET',
66 | encrypt: encrypt
67 |
68 | req = new JsgiRequest('/', 'get', { cookie: '' })
69 | req.env =
70 | session:
71 | foo: 'bar'
72 | bar: 'baz'
73 |
74 | res = { headers: {} }
75 | res = cookieDataProvider.destroy(req, res, sessionId)
76 |
77 | it 'should call encrypt', (done) ->
78 | q(res)
79 | .then ->
80 | expect(encrypt).toHaveBeenCalled()
81 | .fail (err) =>
82 | @fail err
83 | .fin done
84 |
85 | it 'should have empty session cookie', (done) ->
86 | q(res)
87 | .then (res) ->
88 | cookie = res.headers['Set-Cookie'][0]
89 | parts = cookie.split(';')
90 | val = parts[0].split('=')[1]
91 | expect(val).toEqual(encodeURIComponent(JSON.stringify({})))
92 | .fail (err) =>
93 | @fail err
94 | .fin done
95 |
--------------------------------------------------------------------------------
/spec/middlewareSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | q = bogart.q
3 | mockRequest = require './helpers/JsgiRequestHelper'
4 |
5 | describe 'middleware helper', ->
6 | testMiddleware = null
7 | req = null
8 | next = null
9 | handler = null
10 |
11 | beforeEach ->
12 | handler = jasmine.createSpy()
13 |
14 | testMiddleware = bogart.middleware handler
15 | req = mockRequest.root()
16 | next = jasmine.createSpy()
17 |
18 | testMiddleware(next)(req)
19 |
20 | it 'should be a function', ->
21 | expect(typeof testMiddleware).toBe 'function'
22 |
23 | it 'should call handler with correct request', ->
24 | expect(handler).toHaveBeenCalledWith req, jasmine.any(Function)
25 |
26 | it 'should call handler with correct nextApp', ->
27 | expect(handler).toHaveBeenCalledWith jasmine.any(Object), next
28 |
29 |
30 | describe 'parse json', ->
31 | parseJsonMiddleware = null
32 | body = null
33 | jsgiRequest = null
34 | reqParam = null
35 | res = null
36 |
37 | beforeEach ->
38 | forEachDeferred = q.defer()
39 | headers = { 'content-type': 'application/json' }
40 |
41 | body = {
42 | forEach: (callback) ->
43 | callback(JSON.stringify { a: '1' })
44 | forEachDeferred.promise
45 | }
46 |
47 | request = { headers: headers, body: body }
48 |
49 | parseJsonMiddleware = bogart.middleware.parseJson((req) ->
50 | reqParam = req
51 | )
52 |
53 | res = parseJsonMiddleware(request)
54 |
55 | process.nextTick(() ->
56 | forEachDeferred.resolve()
57 | )
58 |
59 | it 'should have passed request to nextApp', (done) ->
60 | q.when(res, () ->
61 | expect(reqParam).toBeDefined()
62 | done()
63 | , (err) =>
64 | this.fail err
65 | done()
66 | )
67 |
68 | it 'should have correct body', (done) ->
69 | q.when(res, () ->
70 | expect(reqParam.body.a).toBe '1'
71 | done()
72 | , (err) =>
73 | this.fail err
74 | done()
75 | )
76 |
77 | describe 'test parse form', ->
78 | forEachDeferred = null
79 | parseFormMiddleware = null
80 | echoApp = null
81 | res = null
82 | reqParamInNextApp = null
83 |
84 | beforeEach ->
85 | echoApp = () ->
86 | (req) ->
87 | reqParamInNextApp = req
88 | { body: [], headers: {}, status: 200 }
89 |
90 | forEachDeferred = q.defer()
91 |
92 | parseFormMiddleware = bogart.middleware.parseForm echoApp()
93 |
94 | jsgiRequest = {
95 | headers: { 'content-type': 'application/x-www-form-urlencoded' },
96 | body: {
97 | forEach: (callback) ->
98 | callback('a=1')
99 | }
100 | }
101 |
102 | res = parseFormMiddleware jsgiRequest
103 |
104 | process.nextTick(() ->
105 | forEachDeferred.resolve()
106 | )
107 |
108 | it 'should pass correct req to nextApp', (done) ->
109 | q.when(res, () ->
110 | expect(reqParamInNextApp.body.a).toBe '1'
111 | done()
112 | , (err) =>
113 | this.fail(err)
114 | done()
115 | )
116 |
117 | describe 'method override', ->
118 | methodOverrideMiddleware = null
119 | jsgiRequest = null
120 | res = null
121 |
122 | beforeEach ->
123 | methodOverrideMiddleware = bogart.middleware.methodOverride((req) ->
124 | {}
125 | )
126 |
127 | headers = { 'content-type': 'text/html' }
128 | jsgiRequest = { method: 'post', env: {}, body: { _method: 'put' }, headers: headers }
129 |
130 | res = methodOverrideMiddleware jsgiRequest
131 |
132 | it 'should change request method to `PUT`', (done) ->
133 | q.when(res, () ->
134 | expect(jsgiRequest.method).toBe 'PUT'
135 | done()
136 | , (err) =>
137 | this.fail(err)
138 | done()
139 | )
140 |
141 | describe 'gzip', ->
142 | jsgiRequest = null
143 | res = null
144 |
145 | beforeEach ->
146 | headers = { 'content-type': 'text/html', 'accept-encoding': 'gzip' }
147 | jsgiRequest = { method: 'post', env: {}, headers: headers, body: [] }
148 |
149 | gzipMiddleware = bogart.middleware.gzip (req) ->
150 | bogart.html 'Hello World'
151 |
152 | res = gzipMiddleware jsgiRequest
153 |
154 | it 'should have response body', (done) ->
155 | q.when(res, (res) ->
156 | expect(typeof res.body).toBe 'object'
157 | done()
158 | , (err) =>
159 | this.fail(err)
160 | done()
161 | )
162 |
163 | it 'should have correct content-type', (done) ->
164 | q.when(res, (res) ->
165 | expect(res.headers['content-type']).toBe 'text/html'
166 | done()
167 | , (err) =>
168 | this.fail(err);
169 | done();
170 | )
171 |
172 | it 'should have correct status', (done) ->
173 | q.when(res, (res) ->
174 | expect(res.status).toBe 200
175 | done()
176 | , (err) =>
177 | this.fail(err)
178 | done()
179 | )
180 |
181 | describe 'error middleware given exception', ->
182 | res = null
183 | errorMessage = '__Intentional Error Test__'
184 |
185 | beforeEach ->
186 | errorMiddleware = bogart.middleware.error { logError: false }, (req) ->
187 | throw new Error(errorMessage)
188 |
189 | res = errorMiddleware { method: 'get', env: {}, headers: {}, body: [] }
190 |
191 | it 'should have correct status', (done) ->
192 | q.when res, (res) ->
193 | expect(res.status).toBe 500
194 | done()
195 |
196 | it 'should have correct content-type', (done) ->
197 | q.when res, (res) ->
198 | expect(res.headers['content-type']).toBe 'text/html'
199 | done()
200 |
201 | it 'should contain error message', (done) ->
202 | q.when res, (res) ->
203 | expect(res.body.join('')).toContain(errorMessage)
204 | done()
205 |
206 | describe 'error middleware rejected promise given string', ->
207 | rejectionMessage = '__INTENTIONAL REJECTION__'
208 | res = null
209 |
210 | beforeEach ->
211 | errorMiddleware = bogart.middleware.error { logError: false }, (req) ->
212 | q.reject rejectionMessage
213 |
214 | res = errorMiddleware { body: [], headers: {}, env: {} }
215 |
216 | it 'should have correct status', (done) ->
217 | q.when res, (res) ->
218 | expect(res.status).toBe 500
219 | done()
220 |
221 | it 'should have correct content-type', (done) ->
222 | q.when res, (res) ->
223 | expect(res.headers['content-type']).toBe 'text/html'
224 | done()
225 |
226 | it 'should contain rejection message', (done) ->
227 | q.when res, (res) ->
228 | expect(res.body.join '').toContain rejectionMessage
229 | done()
230 |
231 | describe 'error middleware rejected promise given Error', ->
232 | errorMessage = '__INTENTIONAL ERROR__'
233 | rejectionError = new Error errorMessage
234 | res = null
235 |
236 | beforeEach ->
237 | errorMiddleware = bogart.middleware.error { logError: false }, (req) ->
238 | q.reject rejectionError
239 |
240 | res = errorMiddleware { body: [], headers: {}, env: {} }
241 |
242 | it 'should have correct status', (done) ->
243 | q.when res, (res) ->
244 | expect(res.status).toBe 500
245 | done()
246 |
247 | it 'should have correct content-type', (done) ->
248 | q.when res, (res) ->
249 | expect(res.headers['content-type']).toBe 'text/html'
250 | done()
251 |
252 | it 'should contain error message', (done) ->
253 | q.when res, (res) ->
254 | expect(res.body.join '').toContain errorMessage
255 | done()
256 |
257 | describe 'flash middleware', ->
258 | res = null
259 | foo = null
260 | jsgiRequest = null
261 | flashMiddleware = null
262 |
263 | beforeEach ->
264 | flashMiddleware = bogart.middleware.flash {}, (req) ->
265 | req.flash 'foo', 'bar'
266 |
267 | foo = req.flash 'foo'
268 |
269 | { status: 200, body: [], headers: { 'content-type': 'text/html' } }
270 |
271 | jsgiRequest = { headers: { 'content-type': 'text/plain' }, body: [] }
272 |
273 | res = flashMiddleware(jsgiRequest).then (res) ->
274 | cookieStr = res.headers['Set-Cookie'].join('').replace(/;$/, '');
275 | jsgiRequest.headers.cookie = cookieStr
276 | res
277 |
278 | describe 'first request', ->
279 |
280 | it 'should have `foo` value of undefined', (done) ->
281 | q.when res, (res) ->
282 | expect(foo).toEqual undefined
283 | done()
284 |
285 | describe 'second request', ->
286 |
287 | beforeEach ->
288 | res = q.when res, () ->
289 | flashMiddleware jsgiRequest
290 |
291 | it 'should have correct `foo` value', (done) ->
292 | q.when res, (res) ->
293 | expect(foo).toEqual 'bar'
294 | done()
295 |
296 | describe 'parted', ->
297 | partedMiddleware = null
298 | res = null
299 |
300 | beforeEach ->
301 | partedMiddleware = bogart.middleware.parted (req) ->
302 | req
303 |
304 | describe 'json', ->
305 |
306 | beforeEach ->
307 | res = partedMiddleware {
308 | headers: { 'content-type': 'application/json' },
309 | body: [ '{ "hello": "world" }' ],
310 | env: {},
311 | method: 'POST'
312 | }
313 |
314 | it 'should have correct body', (done) ->
315 | q.when res, (req) ->
316 | expect(req.body.hello).toBe 'world'
317 | done()
318 |
319 | describe 'form url-encoded', ->
320 |
321 | beforeEach ->
322 | body = {
323 | forEach: (callback) ->
324 | callback('hello=world')
325 | }
326 |
327 | res = partedMiddleware {
328 | method: 'POST',
329 | env: {},
330 | headers: { 'content-type': 'application/x-www-form-urlencoded' },
331 | body: body
332 | }
333 |
334 | it 'should have correct body', (done) ->
335 | q.when res, (res) ->
336 | expect(res.body.hello).toBe 'world'
337 | done()
338 |
339 | describe 'multipart form-encoded', ->
340 |
341 | beforeEach ->
342 | res = partedMiddleware multipartRequest(100, 'chrome')
343 |
344 | it 'should have content', (done) ->
345 | q.when res, (res) ->
346 | expect(res.body.content).not.toBe undefined
347 | done()
348 |
349 | describe 'session', ->
350 | sessionApp = null
351 | firstRequest = true
352 | res = null
353 | values = []
354 |
355 | beforeEach ->
356 | sessionApp = bogart.middleware.session { secret: 'my-super-secret' }, (req) ->
357 | req.session('foo', 'bar') if firstRequest
358 | firstRequest = false
359 |
360 | values.push req.session('foo')
361 |
362 | return {
363 | status: 200,
364 | body: []
365 | }
366 |
367 | headers = { 'content-type': 'text/plain' }
368 | jsgiRequest = { headers: headers, body: [] }
369 |
370 | res = q.when sessionApp(jsgiRequest), (res) ->
371 | cookieStr = res.headers['Set-Cookie'].join('').replace(/;$/, '');
372 |
373 | jsgiRequest.headers.cookie = cookieStr
374 |
375 | q.when sessionApp(jsgiRequest)
376 |
377 | it 'should have correct value for first request', (done) ->
378 | q.when res, (res) ->
379 | expect(values[0]).toBe 'bar'
380 | done()
381 | , (err) =>
382 | this.fail err
383 |
384 | it 'should have correct value for second request', (done) ->
385 | q.when res, (res) ->
386 | expect(values[1]).toBe 'bar'
387 | done()
388 | , (err) =>
389 | this.fail err
390 |
391 | describe 'validate response middleware', ->
392 | validateResApp = null
393 |
394 | describe 'null response', ->
395 |
396 | beforeEach ->
397 | validateResApp = bogart.middleware.validateResponse (req) ->
398 | null
399 |
400 | it 'should have correct error', (done) ->
401 | validateResApp().fail (err) ->
402 | expect(err).toBe 'Response must be an object.'
403 | done()
404 | , (err) =>
405 | this.fail error
406 | done()
407 |
408 | describe 'response without a body', ->
409 |
410 | beforeEach ->
411 | validateResApp = bogart.middleware.validateResponse (req) ->
412 | { status: 200, headers: {} }
413 |
414 | it 'should have correct error', (done) ->
415 | validateResApp().fail (err) ->
416 | expect(err).toBe 'Response must have a body property.'
417 | done()
418 |
419 | describe 'response that has a body that is not a forEachable', ->
420 |
421 | beforeEach ->
422 | validateResApp = bogart.middleware.validateResponse (req) ->
423 | { status: 200, body: {}, headers: {} }
424 |
425 | it 'should have correct error', (done) ->
426 | validateResApp().fail (err) ->
427 | expect(err).toBe 'Response body must have a forEach method.'
428 | done()
429 |
430 | describe 'given response with body that has a forEach property that is not a function', ->
431 |
432 | beforeEach ->
433 | validateResApp = bogart.middleware.validateResponse (req) ->
434 | { status: 200, body: { forEach: {} }, headers: {} }
435 |
436 | it 'should have correct error', (done) ->
437 | validateResApp().fail (err) ->
438 | expect(err).toBe 'Response body has a forEach method but the forEach method is not a function.'
439 | done()
440 |
441 | describe 'given a response without status', ->
442 |
443 | beforeEach ->
444 | validateResApp = bogart.middleware.validateResponse (req) ->
445 | { body: [], headers: {} }
446 |
447 | it 'should have correct error', (done) ->
448 | validateResApp().fail (err) ->
449 | expect(err).toBe 'Response must have a status property.'
450 | done()
451 |
452 | describe 'given a response with a non-number status', ->
453 |
454 | beforeEach ->
455 | validateResApp = bogart.middleware.validateResponse (req) ->
456 | { body: [], headers: {}, status: '200' }
457 |
458 | it 'should have correct error', (done) ->
459 | validateResApp().fail (err) ->
460 | expect(err).toBe 'Response has a status property but the status property must be a number.'
461 | done()
462 |
463 | describe 'string return adapter', ->
464 | str = null
465 | res = null
466 |
467 | beforeEach ->
468 | str = 'This is a message'
469 | req = mockRequest.root()
470 |
471 | stringReturnAdapterApp = bogart.middleware.stringReturnAdapter ->
472 | str
473 |
474 | res = stringReturnAdapterApp req
475 |
476 | it 'should have correct body', (done) ->
477 | q.when res, (res) ->
478 | expect(res.body).toEqual([ str ])
479 | done()
480 |
481 | describe 'batteries', ->
482 |
483 | describe 'given directory configuration', ->
484 |
485 | beforeEach ->
486 | spyOn bogart.middleware, 'directory'
487 | bogart.middleware.batteries({ directory: 'public', secret: 'my-super-secret' })((req) -> {})
488 |
489 | it 'should pass correct configuration to directory middleware', ->
490 | expect(bogart.middleware.directory).toHaveBeenCalledWith 'public', jasmine.any(Function)
491 |
492 | describe 'given empty configuration', ->
493 |
494 | beforeEach ->
495 | spyOn bogart.middleware, 'directory'
496 | bogart.middleware.batteries({})((req) -> {})
497 |
498 | it 'should pass default configuration for `directory`', ->
499 | expect(bogart.middleware.directory).toHaveBeenCalledWith 'public', jasmine.any(Function)
500 |
501 | ###
502 | Create a mock request
503 | Modified from the mock request method in Parted in compliance with the license.
504 | ###
505 | multipartRequest = (size, file) ->
506 | fs = require 'fs'
507 | path = require 'path'
508 |
509 | file = path.join __dirname, 'fixtures', file+'.part'
510 |
511 | stream = fs.createReadStream file, {
512 | bufferSize: size
513 | }
514 |
515 | boundary = fs
516 | .readFileSync(file)
517 | .toString('utf8')
518 | .match(/--[^\r\n]+/)[0]
519 | .slice(2)
520 |
521 | res =
522 | headers: { 'content-type': 'multipart/form-data; boundary="' + boundary + '"' }
523 | method: 'POST'
524 | env: {}
525 | pipe: (dest) ->
526 | stream.pipe dest
527 | emit: (ev, err) ->
528 | this.errback && this.errback(err) if ev == 'error'
529 | this
530 | on: (ev, func) ->
531 | this.errback = func if ev == 'error'
532 | this
533 | destroy: () ->
534 | stream.destroy()
535 | this
536 | body:
537 | forEach: (fn) ->
538 | deferred = q.defer()
539 |
540 | stream.on 'data', (data) ->
541 | fn data
542 |
543 | stream.on 'end', ->
544 | deferred.resolve()
545 |
546 | deferred.promise
547 |
--------------------------------------------------------------------------------
/spec/requestSpec.coffee:
--------------------------------------------------------------------------------
1 | request = require '../lib/request'
2 |
3 | describe 'construction request given empty object parameter', ->
4 | req = null
5 | keys = null
6 |
7 | beforeEach ->
8 | req = request({})
9 | keys = Object.keys req
10 |
11 | it 'should have `params`', ->
12 | expect(keys).toContain 'params'
13 |
14 | it 'should have `search`', ->
15 | expect(keys).toContain 'search'
16 |
17 | it 'should have `isXMLHttpRequest`', ->
18 | expect(keys).toContain 'isXMLHttpRequest'
19 |
20 | describe 'query string parameters', ->
21 | jsgiRequest = null
22 | req = null
23 |
24 | beforeEach ->
25 | jsgiRequest = { queryString: 'a=1&b=abc' }
26 |
27 | req = request jsgiRequest
28 |
29 | it 'should have correct `queryString`', ->
30 | expect(req.queryString).toBe jsgiRequest.queryString
31 |
32 | it 'should have correct `search`', ->
33 | expect(req.search).toEqual { a: '1', b: 'abc' }
34 |
35 | describe 'route parameters', ->
36 | req = null
37 |
38 | beforeEach ->
39 | req = request {}
40 |
41 | req.routeParams.name = 'Bob'
42 |
43 | it 'should have correct name parameter', ->
44 | expect(req.params.name).toBe 'Bob'
45 |
46 | it 'should have correct name search parameter', ->
47 | expect(req.search.name).not.toBe 'Bob'
48 |
49 |
50 | describe 'XHR request', ->
51 | req = null
52 |
53 | beforeEach ->
54 | req = request { headers: { 'x-requested-with': '' } }
55 |
56 | it 'should have correct `isXMLHttpRequest`', ->
57 | expect(req.isXMLHttpRequest).toBe true
58 |
59 | describe 'non-XHR request', ->
60 | req = null
61 |
62 | beforeEach ->
63 | req = request { headers: {} }
64 |
65 | it 'should have correct `isXMLHttpRequest`', ->
66 | expect(req.isXMLHttpRequest).toBe false
67 |
--------------------------------------------------------------------------------
/spec/responseBuilderSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | q = bogart.q
3 | fs = require 'fs'
4 | path = require 'path'
5 |
6 | describe 'resolving ResponseBuilder', ->
7 | responseBuilder = null
8 | resolveVal = { 'hello': 'world' }
9 |
10 | beforeEach ->
11 | responseBuilder = new bogart.ResponseBuilder()
12 |
13 | setTimeout ->
14 | responseBuilder.resolve resolveVal
15 |
16 | it 'should resolve', (done) ->
17 | q.when responseBuilder, (res) ->
18 | expect(res).toBe resolveVal
19 | done()
20 |
21 | describe 'rejecting ResponseBuilder', ->
22 | responseBuilder = null
23 | rejectVal = null
24 |
25 | beforeEach ->
26 | rejectVal = new Error('something bad happened')
27 |
28 | responseBuilder = new bogart.ResponseBuilder()
29 |
30 | setTimeout ->
31 | responseBuilder.reject(rejectVal)
32 |
33 | it 'should reject', (done) ->
34 | q.when(responseBuilder).fail (err) ->
35 | expect(err).toBe rejectVal
36 | done()
37 |
38 | describe 'sending a string', ->
39 | responseBuilder = null
40 | msg = 'Hello World'
41 |
42 | beforeEach ->
43 | responseBuilder = bogart.ResponseBuilder()
44 |
45 | responseBuilder.send msg
46 |
47 | responseBuilder.end()
48 |
49 | it 'should have correct body', (done) ->
50 | q.when responseBuilder, (res) ->
51 | written = ''
52 |
53 | q.when res.body.forEach((data) ->
54 | written += data
55 | ), ->
56 | expect(written).toBe msg
57 | done()
58 |
59 | describe 'sending a buffer', ->
60 | responseBuilder = null
61 | msg = null
62 | buffer = null
63 |
64 | beforeEach ->
65 | msg = 'Hello World'
66 | buffer = new Buffer msg.length
67 | buffer.write msg
68 |
69 | responseBuilder = new bogart.ResponseBuilder()
70 | responseBuilder.send buffer
71 | responseBuilder.end()
72 |
73 | it 'should have correct body', (done) ->
74 | q.when responseBuilder, (res) ->
75 | written = ''
76 |
77 | forEachFinished = res.body.forEach (data) ->
78 | written += data
79 |
80 | q.when forEachFinished, ->
81 | expect(written).toBe msg
82 | done()
83 |
84 | describe 'sending binary', ->
85 | responseBuilder = null
86 | filePath = path.join __dirname, 'fixtures', 'test.jpg'
87 | stat = fs.statSync filePath
88 | fileContent = null
89 |
90 | beforeEach ->
91 | responseBuilder = new bogart.ResponseBuilder()
92 |
93 | fs.readFile filePath, 'binary', (err, content) =>
94 | this.fail err if err
95 |
96 | fileContent = content
97 | responseBuilder.send content
98 | responseBuilder.end()
99 |
100 | it 'should have correct body', (done) ->
101 | q.when responseBuilder, (res) ->
102 | written = new Buffer stat.size
103 |
104 | forEachFinished = res.body.forEach (data) ->
105 | written.write data, 'binary'
106 |
107 | q.when forEachFinished, ->
108 | expect(written.toString 'binary').toBe fileContent
109 | done()
110 |
111 |
--------------------------------------------------------------------------------
/spec/responseHelperSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | q = bogart.q
3 | path = require 'path'
4 | fs = require 'fs'
5 |
6 | describe 'json', ->
7 | it 'should have default status of 200', ->
8 | expect(bogart.json({}).status).toBe 200
9 |
10 | it 'given status should override status', ->
11 | expect(bogart.json({}, { status: 403 }).status).toBe 403
12 |
13 | describe 'given object', ->
14 | res = null
15 | obj = null
16 | etag = "298fjsdkf"
17 |
18 | beforeEach ->
19 | obj = { hello: 'world' }
20 | opts = { headers: {ETag:etag} }
21 |
22 | res = bogart.json obj, opts
23 |
24 | it 'should have correct body', ->
25 | expect(res.body.join()).toBe JSON.stringify(obj)
26 |
27 | it 'should add any additional headers', ->
28 | expect(res.headers["ETag"]).toBe etag
29 |
30 |
31 | describe 'cors', ->
32 | it 'should have default status of 200', ->
33 | expect(bogart.cors({}).status).toBe 200
34 |
35 | it 'given status should override status', ->
36 | expect(bogart.cors({}, { status: 403 }).status).toBe 403
37 |
38 | describe 'given object', ->
39 | res = null
40 | body = null
41 |
42 | beforeEach ->
43 | body = { hello: 'world' }
44 |
45 | res = bogart.cors body
46 |
47 | it 'should have correct body', ->
48 | expect(res.body.join()).toBe JSON.stringify(body)
49 | it 'should have correct headers', ->
50 | expect(res.headers['Access-Control-Allow-Origin']).toBe '*'
51 | expect(res.headers['Access-Control-Allow-Methods']).toBe 'GET,PUT,POST,DELETE'
52 | expect(res.headers['Access-Control-Allow-Headers']).toBe 'x-requested-with,*'
53 | it 'given headers should override the default headers', ->
54 | headers = {'Access-Control-Allow-Origin': 'http://whiteboard-it.com', 'Content-Type': 'text/plain'}
55 | expect(bogart.cors({a:'b'},{headers: headers}).headers['Access-Control-Allow-Origin']).toBe 'http://whiteboard-it.com'
56 | expect(bogart.cors({a:'b'},{headers: headers}).headers['Content-Type']).toBe 'text/plain'
57 |
58 | describe 'error', ->
59 | it 'should have default status of 500', ->
60 | expect(bogart.error().status).toBe 500
61 |
62 | it 'should override status', ->
63 | expect(bogart.error('', { status: 403 }).status).toBe 403
64 |
65 |
66 | describe 'html', ->
67 | it 'should have default status of 200', ->
68 | expect(bogart.html('hello').status).toBe 200
69 |
70 | it 'should override status', ->
71 | expect(bogart.html('hello', { status: 404 }).status).toBe 404
72 |
73 | describe 'given html string', ->
74 | res = null
75 | str = null
76 |
77 | beforeEach ->
78 | str = 'Hello '
79 | res = bogart.html str
80 |
81 | it 'should have correct content-type', ->
82 | expect(res.headers['content-type']).toBe 'text/html'
83 |
84 | it 'should have correct content-length', ->
85 | expect(res.headers['content-length']).toBe Buffer.byteLength(str, 'utf-8')
86 |
87 | it 'should have correct body', ->
88 | expect(res.body.join()).toBe str
89 |
90 |
91 | describe 'pipe', ->
92 | res = null
93 |
94 | beforeEach ->
95 | readStream = fs.createReadStream path.join(__dirname, 'fixtures', 'text.txt')
96 | res = bogart.pipe readStream
97 |
98 | it 'should have correct body', (done) ->
99 | q.when(res, (res) ->
100 | written = ''
101 |
102 | forEachFinished = res.body.forEach (data) ->
103 | written += data
104 |
105 | q.when forEachFinished, ->
106 | expect(written).toBe 'Hello World'
107 | done()
108 | ).fail (err) =>
109 | this.fail err
110 | done()
111 |
112 | describe 'pipe given forEachable', ->
113 | forEachable = null
114 | deferred = null
115 | msg = null
116 | res = null
117 |
118 | beforeEach ->
119 | msg = 'Hello World'
120 |
121 | deferred = q.defer()
122 |
123 | forEachable = {
124 | forEach: (callback) ->
125 | callback msg
126 | deferred.promise
127 | }
128 |
129 | setTimeout ->
130 | deferred.resolve()
131 |
132 | res = bogart.pipe forEachable
133 |
134 | it 'should have correct body', (done) ->
135 | q.when(res, (res) ->
136 | written = ''
137 |
138 | forEachFinished = res.body.forEach (data) ->
139 | written += data
140 |
141 | q.when forEachFinished, ->
142 | expect(written).toBe msg
143 | done()
144 |
145 | ).fail (err) =>
146 | this.fail err
147 | done()
148 |
149 | describe 'redirect given options', ->
150 | res = null
151 | opts = null
152 |
153 | beforeEach ->
154 | opts = { hello: 'world' }
155 | res = bogart.redirect '/', opts
156 |
157 | it 'should have correctly merged `hello` value', ->
158 | expect(res.hello).toBe opts.hello
159 |
160 | describe 'redirect given headers', ->
161 | res = null
162 | headers = null
163 |
164 | beforeEach ->
165 | headers = { hello: 'world' }
166 |
167 | res = bogart.redirect '/', { headers: headers }
168 |
169 | it 'should have correct location header', ->
170 | expect(res.headers.location).toBe '/'
171 |
172 | it 'should have correct `hello` header', ->
173 | expect(res.headers.hello).toBe headers.hello
174 |
175 |
--------------------------------------------------------------------------------
/spec/routerSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | MockRequest = require './helpers/JsgiRequestHelper'
3 | Router = (require '../lib/router').Router
4 | q = require 'q'
5 |
6 | describe 'matches parameter', ->
7 | req = null
8 | router = null
9 | res = null
10 | name = null
11 |
12 | beforeEach ->
13 | req = MockRequest.root()
14 |
15 | router = bogart.router()
16 |
17 | router.get '/hello/:name', (req) ->
18 | name = req.params.name
19 | bogart.html 'hello'
20 |
21 | req.pathInfo = '/hello/nathan'
22 |
23 | res = router() req
24 |
25 | it 'should have correct status', (done) ->
26 | q.when res, (res) ->
27 | expect(res.status).toBe 200
28 | done()
29 |
30 | it 'should have correct name', (done) ->
31 | q.when res, (res) ->
32 | expect(name).toBe 'nathan'
33 | done()
34 |
35 | describe 'order of routes matching should be in order defined', ->
36 | router = null
37 | res = null
38 | firstCalled = false
39 | secondCalled = false
40 |
41 | beforeEach ->
42 | router = bogart.router()
43 |
44 | router.get '/hello/:name', (req) ->
45 | firstCalled = true
46 | bogart.html 'hello'
47 |
48 | router.get '/hello/:name/:something', (req) ->
49 | secondCalled = true
50 | bogart.html 'hello'
51 |
52 | res = router() new MockRequest('/hello/nathan')
53 |
54 | it 'should have called first route', (done) ->
55 | q.when res, (res) ->
56 | expect(firstCalled).toBe true
57 | done()
58 |
59 | it 'should not have called second route', (done) ->
60 | q.when res, ->
61 | expect(secondCalled).toBe false
62 | done()
63 |
64 | describe 'should call notFoundApp', ->
65 | notFoundApp = null
66 | router = null
67 | res = null
68 | called = false
69 | notFoundRes = null
70 |
71 | beforeEach ->
72 | notFoundRes = { status: 404, body: [ '' ], headers: {} }
73 |
74 | notFoundApp = (req) ->
75 | called = true
76 | notFoundRes
77 |
78 | router = bogart.router()
79 |
80 | res = router(notFoundApp) MockRequest.root()
81 |
82 | it 'should have correct response', (done) ->
83 | q.when res, (res) ->
84 | expect(res).toBe notFoundRes
85 | done()
86 |
87 | it 'should have called the not found app', (done) ->
88 | q.when res, (res) ->
89 | expect(called).toBe true
90 | done()
91 |
92 |
93 | describe 'default notFoundApp behavior of returning 404', ->
94 | res = null
95 |
96 | beforeEach ->
97 | router = bogart.router()
98 |
99 | res = router() MockRequest.root()
100 |
101 | it 'should have status of 404', (done) ->
102 | q.when res, (res) ->
103 | expect(res.status).toBe 404
104 | done()
105 |
106 | describe 'router.notFound', ->
107 | notFoundHandler = null
108 | req = null
109 | res = null
110 |
111 | beforeEach ->
112 | notFoundHandler = jasmine.createSpy 'not found handler'
113 |
114 | router = bogart.router()
115 | router.notFound(notFoundHandler)
116 |
117 | req = MockRequest.root()
118 | res = router() req
119 |
120 | it 'should have called handler specified by notFound', ->
121 | q.when res, ->
122 | expect(notFoundHandler).toHaveBeenCalled()
123 |
124 | describe 'partially matched route', ->
125 | res = null
126 |
127 | beforeEach ->
128 | router = bogart.router()
129 |
130 | router.get '/partial-match', (req) ->
131 | { status: 200, body: [ 'hello' ], headers: {} }
132 |
133 | res = router() new MockRequest('/partial-match/path')
134 |
135 | it 'should have status of 404', (done) ->
136 | q.when res, (res) ->
137 | expect(res.status).toBe 404
138 | done()
139 |
140 | describe 'partially matched route with parameter', ->
141 | res = null
142 |
143 | beforeEach ->
144 | router = bogart.router()
145 | router.get '/:foo', (req) ->
146 | return { status: 200, body: [ 'hello' ], headers: {} }
147 |
148 | res = router() new MockRequest('/hello/world')
149 |
150 | it 'should have status of 404', (done) ->
151 | q.when res, (res) ->
152 | expect(res.status).toBe 404
153 | done()
154 |
155 | describe 'route with querystring', ->
156 | res = null
157 |
158 | beforeEach ->
159 | router = bogart.router()
160 |
161 | router.get '/home', (req) ->
162 | { status: 200, body: [ 'home' ], headers: {} }
163 |
164 | req = new MockRequest('/home')
165 | req.queryString = 'hello=world'
166 |
167 | res = router() req
168 |
169 | it 'should have correct status', (done) ->
170 | q.when res, (res) ->
171 | expect(res.status).toBe 200
172 | done()
173 |
174 | describe 'regex route', ->
175 | res = null
176 | splat = null
177 | routeRes = null
178 |
179 | beforeEach ->
180 | router = bogart.router()
181 |
182 | routeRes = bogart.html 'hello'
183 |
184 | router.get /\/hello\/(.*)\/(.*)/, (req) ->
185 | splat = req.params.splat
186 | routeRes
187 |
188 | res = router() new MockRequest('/hello/cruel/world')
189 |
190 | it 'should have correct splat', (done) ->
191 | q.when res, (res) ->
192 | expect(splat).toEqual [ 'cruel', 'world' ]
193 | done()
194 |
195 | it 'should have correct response', (done) ->
196 | q.when res, (res) ->
197 | expect(res).toBe routeRes
198 | done()
199 |
200 | describe 'request path with encoded slashes', ->
201 | res = null
202 | routeRes = null
203 |
204 | beforeEach ->
205 | routeRes = bogart.html 'hello'
206 |
207 | router = bogart.router()
208 |
209 | router.get '/:foo', (req) ->
210 | routeRes
211 |
212 | res = router() new MockRequest('/foo%2fbar')
213 |
214 | it 'should have correct response', (done) ->
215 | q.when res, (res) ->
216 | expect(res).toBe routeRes
217 | done()
218 |
219 | describe 'request with a dot (".") as part of the named parameter', ->
220 | res = null
221 | params = null
222 | routeRes = null
223 | res = null
224 |
225 | beforeEach ->
226 | routeRes = bogart.html 'hello'
227 |
228 | router = bogart.router()
229 | router.get '/:foo/:bar', (req) ->
230 | params = req.params
231 | routeRes
232 |
233 | res = router() new MockRequest('/user@example.com/name')
234 |
235 |
236 | it 'should have correct :foo param', (done) ->
237 | q.when res, ->
238 | expect(params.foo).toBe 'user@example.com'
239 | done()
240 |
241 | it 'should have correct :bar param', (done) ->
242 | q.when res, ->
243 | expect(params.bar).toBe 'name'
244 | done()
245 |
246 | it 'should have correct response', (done) ->
247 | q.when res, (res) ->
248 | expect(res).toBe routeRes
249 | done()
250 |
251 | describe 'matches empty `pathInfo` to "/" if no route is defined for ""', ->
252 | res = null
253 | routeRes = null
254 |
255 | beforeEach ->
256 | routeRes = bogart.text 'success'
257 |
258 | router = bogart.router()
259 | router.get '/', ->
260 | routeRes
261 |
262 | res = router() new MockRequest('')
263 |
264 | it 'should have correct response', (done) ->
265 | q.when res, (res) ->
266 | expect(res).toBe routeRes
267 | done()
268 |
269 | describe 'matches empty `pathInfo` to "" if a route is defined for ""', ->
270 | res = null
271 | rightRouteRes = null
272 | wrongRouteRes = null
273 |
274 | beforeEach ->
275 | rightRouteRes = bogart.text 'right'
276 | wrongRouteRes = bogart.text 'wrong'
277 |
278 | router = bogart.router()
279 |
280 | router.get '', (req) ->
281 | rightRouteRes
282 |
283 | router.get '/', (req) ->
284 | wrongRouteRes
285 |
286 | res = router() new MockRequest('')
287 |
288 | it 'should have correct response', (done) ->
289 | q.when res, (res) ->
290 | expect(res).toBe rightRouteRes
291 | done()
292 |
293 | describe 'paths that include spaces', ->
294 | res = null
295 | routeRes = null
296 |
297 | beforeEach ->
298 | routeRes = bogart.text 'spaces are cool'
299 | router = bogart.router()
300 |
301 | router.get '/path with spaces', (req) ->
302 | routeRes
303 |
304 | res = router() new MockRequest('/path%20with%20spaces')
305 |
306 | it 'should have correct response', (done) ->
307 | q.when res, (res) ->
308 | expect(res).toBe routeRes
309 | done()
310 |
311 | describe 'literal (".") in path', ->
312 | res = null
313 | routeRes = null
314 |
315 | beforeEach ->
316 | routeRes = bogart.text 'hello'
317 |
318 | router = bogart.router()
319 |
320 | router.get '/foo.bar', ->
321 | routeRes
322 |
323 | res = router() new MockRequest('/foo.bar')
324 |
325 | it 'should have correct response', (done) ->
326 | q.when res, (res) ->
327 | expect(res).toBe routeRes
328 | done()
329 |
330 | describe '("-") in path', ->
331 | res = null
332 | routeRes = null
333 |
334 | beforeEach ->
335 | routeRes = bogart.text 'hello'
336 |
337 | router = bogart.router()
338 |
339 | router.get '/foo/:bar/dash-url', ->
340 | routeRes
341 |
342 | res = router() new MockRequest('/foo/baz/dash-url')
343 |
344 | it 'should have correct response', (done) ->
345 | q.when res, (res) ->
346 | expect(res).toBe routeRes
347 | done()
348 |
349 | describe 'path with splat ("*")', ->
350 | routeRes = null
351 | res = null
352 | params = null
353 |
354 | beforeEach ->
355 | routeRes = bogart.text 'splat'
356 |
357 | router = bogart.router()
358 |
359 | router.get '/foo/*', (req) ->
360 | params = req.params
361 | routeRes
362 |
363 | res = router() new MockRequest('/foo/hello/there')
364 |
365 | it 'should have correct splat', (done) ->
366 | q.when res, (res) ->
367 | expect(params.splat[0]).toBe 'hello/there'
368 | done()
369 |
370 | it 'should have correct response', (done) ->
371 | q.when res, (res) ->
372 | expect(res).toBe routeRes
373 | done()
374 |
375 | describe 'path with multiple splat parameters', ->
376 | routeRes = null
377 | res = null
378 | params = null
379 |
380 | beforeEach ->
381 | routeRes = bogart.text 'splat'
382 |
383 | router = bogart.router()
384 |
385 | router.get '/download/*/*', (req) ->
386 | params = req.params
387 | routeRes
388 |
389 | res = router() new MockRequest('/download/images/ninja-cat.jpg')
390 |
391 | it 'should have correct splat', (done) ->
392 | q.when res, (res) ->
393 | expect(params.splat).toEqual [ 'images', 'ninja-cat.jpg' ]
394 | done()
395 |
396 | it 'should have correct response', (done) ->
397 | q.when res, (res) ->
398 | expect(res).toBe routeRes
399 | done()
400 |
401 | describe 'mixing splat and named parameters', ->
402 | routeRes = null
403 | res = null
404 | params = null
405 |
406 | beforeEach ->
407 | routeRes = bogart.text 'mix'
408 |
409 | router = bogart.router()
410 |
411 | router.get '/:foo/*', (req) ->
412 | params = req.params
413 | routeRes
414 |
415 | res = router() new MockRequest('/foo/bar/baz')
416 |
417 | it 'should have correct response', (done) ->
418 | q.when res, (res) ->
419 | expect(res).toBe routeRes
420 | done()
421 |
422 | it 'should have correct named parameter', (done) ->
423 | q.when res, (res) ->
424 | expect(params.foo).toBe 'foo'
425 | done()
426 |
427 | it 'should have correct splat parameter', (done) ->
428 | q.when res, (res) ->
429 | expect(params.splat).toEqual [ 'bar/baz' ]
430 | done()
431 |
432 | describe 'chaining route handlers', ->
433 | routeRes = null
434 | res = null
435 | hello = null
436 |
437 | beforeEach ->
438 | routeRes = bogart.text 'hello'
439 |
440 | router = bogart.router()
441 |
442 | firstHandler = (req, next) ->
443 | req.hello = 'world'
444 | next req
445 |
446 | secondHandler = (req) ->
447 | hello = req.hello
448 | routeRes
449 |
450 | router.get '/', firstHandler, secondHandler
451 |
452 | res = router() MockRequest.root()
453 |
454 | it 'should have correct response', (done) ->
455 | q.when res, (res) ->
456 | expect(res).toBe routeRes
457 | done()
458 |
459 | it 'should have correct value set by first handler', (done) ->
460 | q.when res, ->
461 | expect(hello).toBe 'world'
462 | done()
463 |
464 |
--------------------------------------------------------------------------------
/spec/streamSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | q = bogart.q
3 | fs = require 'fs'
4 | Readable = require('stream').Readable
5 | ForEachStream = require '../lib/forEachStream'
6 | zlib = require 'zlib'
7 |
8 | describe 'ForEachStream pipe to file stream', ->
9 | stat = null
10 |
11 | beforeEach ->
12 | stat = q.defer()
13 |
14 | source = [ 'Hello', 'World' ]
15 | stream = new ForEachStream source
16 | fileName = 'hello-world.txt'
17 | writeStream = fs.createWriteStream fileName
18 |
19 | stream.pipe writeStream
20 |
21 | stream.on 'end', ->
22 | stat.resolve(fs.statSync fileName)
23 |
24 | it 'should have created file', (done) ->
25 | q.when stat.promise, (stat) ->
26 | expect(stat.isFile()).toBe true
27 | done()
28 |
29 | describe 'ForEachStream pipe to deflate stream', ->
30 | stat = null
31 | bufferInflated = null
32 |
33 | beforeEach ->
34 | statDeferred = q.defer()
35 | bufferInflatedDeferred = q.defer()
36 |
37 | stat = statDeferred.promise
38 | bufferInflated = bufferInflatedDeferred.promise
39 |
40 | source = [ 'Hello', ' ', 'World' ]
41 | srcStream = new ForEachStream source
42 | stream = zlib.createDeflateRaw()
43 | fileName = 'hello-world.dat'
44 |
45 | srcStream.pipe(stream).pipe(fs.createWriteStream(fileName))
46 |
47 | stream.on 'end', ->
48 | statDeferred.resolve fs.statSync(fileName)
49 |
50 | process.nextTick ->
51 | readStream = fs.createReadStream fileName
52 | inflateStream = zlib.createInflateRaw()
53 | buffer = new Buffer 0
54 |
55 | readStream.pipe inflateStream
56 |
57 | inflateStream.on 'data', (data) ->
58 | oldBuffer = buffer
59 |
60 | buffer = new Buffer(oldBuffer.length + data.length)
61 | oldBuffer.copy(buffer, 0, 0)
62 | data.copy(buffer, oldBuffer.length, 0)
63 |
64 | inflateStream.on 'error', (err) =>
65 | this.fail err
66 |
67 | inflateStream.on 'end', ->
68 | bufferInflatedDeferred.resolve buffer.toString('utf-8')
69 |
70 | it 'should have correct inflated buffer', (done) ->
71 | q.when bufferInflated, (str) ->
72 | expect(str).toBe 'Hello World'
73 | done()
74 |
75 | it 'should have created file', (done) ->
76 | q.when stat, (stat) ->
77 | expect(stat.isFile()).toBe true
78 | done()
79 |
80 | describe 'pump ForEachStream to file stream', ->
81 | ended = null
82 | fileName = null
83 |
84 | beforeEach ->
85 | seed = [ 'Hello', ' ', 'World' ].map (x) ->
86 | new Buffer x
87 |
88 | fileName = 'forEachableToFileStream.txt'
89 |
90 | src = new ForEachStream seed
91 |
92 | dest = fs.createWriteStream fileName
93 |
94 | ended = bogart.pump src, dest
95 |
96 | it 'should create file', (done) ->
97 | q.when ended, ->
98 | expect(fs.statSync(fileName).isFile()).toBe true
99 | done()
100 |
101 | it 'should have correct file content', (done) ->
102 | q.when ended, ->
103 | expect(fs.readFileSync(fileName, 'utf-8')).toBe 'Hello World'
104 | done()
105 |
--------------------------------------------------------------------------------
/spec/utilSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | path = require 'path'
3 | q = bogart.q
4 | security = require '../lib/security'
5 |
6 | describe 'security', ->
7 | dec = null
8 | plain = null
9 |
10 | beforeEach ->
11 | plain = "this is a very long string of text with random characters {}[[]\!@#$%^&*()_+!@#$%^&*()_+%3D%44%5Z,this is a very long string of text with random characters {}[[]\!@#$%^&*()_+!@#$%^&*()_+%3D%44%5Z,this is a very long string of text with random characters {}[[]\!@#$%^&*()_+!@#$%^&*()_+%3D%44%5Z,this is a very long string of text with random characters {}[[]\!@#$%^&*()_+!@#$%^&*()_+%3D%44%5Z";
12 |
13 | describe 'default key', ->
14 | beforeEach ->
15 | enc = security.encrypt plain
16 | dec = security.decrypt enc
17 |
18 | afterEach ->
19 | dec = null
20 |
21 | it 'should have decrypted original message', ->
22 | expect(dec).toBe plain
23 |
24 | describe 'provided key', ->
25 | beforeEach ->
26 | key = "THIS is a Secret -- key == C!@#$%^&*())_%3D%2F"
27 |
28 | enc = security.encrypt plain, key
29 | dec = security.decrypt enc, key
30 |
31 | afterEach ->
32 | dec = null
33 |
34 | it 'should have decrypted original message', ->
35 | expect(dec).toBe plain
36 |
37 | describe 'uuid key', ->
38 | beforeEach ->
39 | key = "0ce05d12-1a33-11e1-a436-0019e34411d1"
40 |
41 | enc = security.encrypt plain, key
42 | dec = security.decrypt enc, key
43 |
44 | afterEach ->
45 | dec = null
46 |
47 | it 'should have decrypted original message', ->
48 | expect(dec).toBe plain
49 |
50 | describe 'promisify', ->
51 |
52 | describe 'given success', ->
53 | expectedVal = 1
54 | res = null
55 |
56 | beforeEach ->
57 | asyncFn = (cb) ->
58 | process.nextTick ->
59 | cb null, expectedVal
60 |
61 | res = bogart.promisify(asyncFn)()
62 |
63 | it 'should have corrected resolution', (done) ->
64 | q.when res, (res) ->
65 | expect(res).toBe expectedVal
66 | done()
67 |
68 | describe 'given rejection', ->
69 | res = null
70 | expectedRejection = new Error('bad stuff happened')
71 |
72 | beforeEach ->
73 | asyncFn = (cb) ->
74 | process.nextTick ->
75 | cb expectedRejection
76 |
77 | res = bogart.promisify(asyncFn)()
78 |
79 | it 'should have correct rejection', (done) ->
80 | q.when(res).fail (err) ->
81 | expect(err).toBe expectedRejection
82 | done()
83 |
84 |
85 |
--------------------------------------------------------------------------------
/spec/viewSpec.coffee:
--------------------------------------------------------------------------------
1 | bogart = require '../lib/bogart'
2 | view = require '../lib/view'
3 | jsgi = require 'jsgi'
4 | q = bogart.q
5 | path = require 'path'
6 | fixturesPath = path.join __dirname, 'fixtures'
7 |
8 | describe 'render mustache', ->
9 | res = null
10 |
11 | beforeEach ->
12 | viewEngine = bogart.viewEngine 'mustache', fixturesPath
13 |
14 | res = viewEngine.render 'index.mustache', { layout: false }
15 |
16 | it 'should have correct html', (done) ->
17 | q.when res, (res) ->
18 | expect(res).toBe 'Hello World from Mustache '
19 | done()
20 |
21 | describe 'render mustache with partials', ->
22 | res = null
23 |
24 | beforeEach ->
25 | viewEngine = bogart.viewEngine 'mustache', fixturesPath
26 |
27 | res = viewEngine.render 'partial-test.mustache', {
28 | layout: false,
29 | locals: { greeting: {} },
30 | partials: { greeting: 'greeting.mustache' }
31 | }
32 |
33 | it 'should have correct html', (done) ->
34 | q.when res, (res) ->
35 | expect(res).toBe 'Hello World from Mustache With Partial
'
36 | done()
37 |
38 | describe 'given headers to merge', ->
39 | res = null
40 | opts = null
41 |
42 | beforeEach ->
43 | viewEngine = bogart.viewEngine 'mustache', fixturesPath
44 |
45 | opts = {
46 | layout: false,
47 | headers: { abc: 123 }
48 | }
49 |
50 | res = viewEngine.respond 'index.mustache', opts
51 |
52 | it 'should have correct content-type', (done) ->
53 | q.when res, (res) ->
54 | expect(res.headers['content-type']).toBe 'text/html'
55 | done()
56 |
57 | it 'should have correct merged value', (done) ->
58 | q.when res, (res) ->
59 | expect(res.headers.abc).toBe opts.headers.abc
60 | done()
61 |
--------------------------------------------------------------------------------