Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Began moving code in renderer.js to context.js along with tests. A ne…

…w RequestContext will be created even before dispatch in future.
  • Loading branch information...
commit fca26609d9cafd7a40201816ce2168fbff7e4fd7 1 parent adea164
@tuxychandru authored
View
161 grasshopper/lib/context.js
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010 Chandra Sekar S
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var url = require('url'),
+ fs = require('fs'),
+ mime = require('./mime'),
+ i18n = require('./i18n'),
+ base64 = require('./base64');
+
+var defaultViewExtn = 'html',
+ defaultEncoding = 'utf8',
+ defaultCharset = 'UTF-8',
+ viewsDir = '.';
+
+// Class: RequestContext
+function RequestContext(request, response) {
+ this.request = request;
+ this.response = response;
+ this.model = {};
+
+ this.status = 200;
+ this.extn = this._getExtn();
+ this.encoding = defaultEncoding;
+ this.headers = {
+ 'content-type': mime.mimes[this.extn]
+ ? mime.mimes[this.extn]
+ : 'application/octet-stream',
+ 'date': new Date().toUTCString(),
+ 'x-powered-by': 'Grasshopper/0.3.3'
+ };
+
+ var cookieLine = request.headers['cookie'];
+ this.requestCookies = {};
+ if(cookieLine) {
+ var cookies = cookieLine.split(';');
+ for(var i = 0; i < cookies.length; i++) {
+ var cookieParts = cookies[i].trim().split('=');
+ if(cookieParts[0] == 'GHSESSION') {
+ this.sessionId = decodeURIComponent(cookieParts[1]);
+ } else {
+ this.requestCookies[cookieParts[0]]
+ = decodeURIComponent(cookieParts[1]);
+ }
+ }
+ }
+
+ i18n.init(this);
+ this.charset = defaultCharset;
+}
+
+RequestContext.prototype._getExtn = function() {
+ var extn = defaultViewExtn;
+ var path = url.parse(this.request.url).pathname;
+ if(path.match(/\.[^\/]+$/)) {
+ extn = path.substring(path.lastIndexOf('.') + 1);
+ this.requestExtn = extn;
+ }
+
+ return extn;
+};
+
+RequestContext.prototype.challengeAuth = function(type, options) {
+ var challengeHeader = type + ' ';
+ Object.keys(options).forEach(function(key) {
+ challengeHeader += key + '="' + options[key] + '",';
+ });
+ challengeHeader = challengeHeader.substring(0, challengeHeader.length - 1);
+ this.headers['www-authenticate'] = challengeHeader;
+ this.renderError(401);
+};
+
+RequestContext.prototype.getAuth = function() {
+ var authHeader = this.request.headers['authorization'];
+ if(authHeader && authHeader.substring(0, 6) == "Basic ") {
+ var credentials = base64.decode(authHeader.substring(6), this.encoding);
+ var userPass = credentials.split(":", 2);
+ return {
+ username: userPass[0],
+ password: userPass[1]
+ };
+ } else if(authHeader && authHeader.substring(0, 7) == "Digest ") {
+ var credentials = authHeader.substring(7);
+ var auth = {};
+ credentials.split(',').forEach(function(part) {
+ var subParts = part.trim().split('=');
+ var value = subParts[1];
+ if(value.charAt(0) == '"'
+ && value.charAt(0) == value.charAt(value.length - 1)) {
+ value = value.substring(1, value.length - 1);
+ }
+ auth[subParts[0]] = value;
+ });
+ return auth;
+ }
+};
+
+RequestContext.prototype.renderText = function(text) {
+ this._writeHead();
+ if(this.request.method != 'HEAD') {
+ this.response.write(text, this.encoding);
+ }
+ this.response.end();
+};
+
+RequestContext.prototype.renderError = function(status, error, cb) {
+ this.status = status;
+ if(error === undefined) {
+ error = {};
+ }
+ if(typeof error == 'function') {
+ cb = error;
+ error = {};
+ }
+
+ var viewFile = viewsDir + '/' + this.status + '.' + this.extn;
+
+ var self = this;
+ fs.stat(viewFile, function(err, stats) {
+ if(!err && stats.isFile()) {
+ try {
+ self._writeHead();
+ ghp.fill(viewFile, self.response, {error: error}, self.encoding, viewsDir, self.extn, this.locale);
+ cb && cb();
+ } catch(e) {
+ self._handleError(e);
+ cb && cb();
+ }
+ } else {
+ self._writeHead();
+ self.response.end();
+ cb && cb();
+ }
+ });
+};
+
+RequestContext.prototype._writeHead = function() {
+ if(this.charset) {
+ this.headers['content-type'] += '; charset=' + this.charset
+ }
+ this.response.writeHead(this.status, this.headers);
+};
+
+RequestContext.prototype.disableCache = function() {
+ this.headers['expires'] = 'Thu, 11 Mar 2010 12:48:43 GMT';
+ this.headers['cache-control'] = 'no-store, no-cache, must-revalidate';
+ this.headers['pragma'] = 'no-cache';
+};
+
+exports.RequestContext = RequestContext;
View
34 test/common/mocks.js
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-var EventEmitter = require('events').EventEmitter;
+var EventEmitter = require('events').EventEmitter,
+ fs = require('fs');
function MockRequest(method, url, headers) {
EventEmitter.call(this);
@@ -36,8 +37,12 @@ require('util').inherits(MockResponse, EventEmitter);
MockResponse.prototype.writeHead = function(statusCode, reasonPhrase, headers) {
this.statusCode = statusCode;
- this.reasonPhrase = reasonPhrase;
- this.headers = headers;
+ if(typeof reasonPhrase == 'object') {
+ this.headers = reasonPhrase;
+ } else {
+ this.reasonPhrase = reasonPhrase;
+ this.headers = headers;
+ }
};
MockResponse.prototype.write = function(chunk, encoding) {
@@ -57,5 +62,28 @@ MockResponse.prototype.destroy = function() {
this.writable = false;
};
+// Mock modules (supports only non-native modules currently)
+
+var moduleOriginals = {};
+
+exports.mockModule = function(mod, exports) {
+ var moduleId = fs.realpathSync(mod + '.js');
+ var cacheValue = require(moduleId.substring(0, moduleId.length - 3));
+ moduleOriginals[moduleId] = {};
+ Object.keys(exports).forEach(function(key) {
+ moduleOriginals[moduleId][key] = cacheValue[key];
+ cacheValue[key] = exports[key];
+ });
+};
+
+exports.unmockModule = function(mod) {
+ var moduleId = fs.realpathSync(mod + '.js');
+ var cacheValue = require(moduleId.substring(0, moduleId.length - 3));
+ Object.keys(moduleOriginals[moduleId]).forEach(function(key) {
+ cacheValue[key] = moduleOriginals[moduleId][key];
+ });
+ delete moduleOriginals[moduleId];
+};
+
exports.MockRequest = MockRequest;
exports.MockResponse = MockResponse;
View
47 test/simple/auth-test.js
@@ -1,47 +0,0 @@
-var RequestContext = require('../../grasshopper/lib/renderer').RequestContext,
- assert = require('assert'),
- mocks = require('../common/mocks'),
- base64 = require('../../grasshopper/lib/base64');
-
-var suite = {name: 'Authentication Tests'};
-exports.suite = suite;
-
-suite.tests = {
- 'Basic getAuth().': function(next) {
- var req = new mocks.MockRequest('GET', '/', {
- authorization: 'Basic ' + base64.encode('Chandru:Pass')
- });
- var ctx = new RequestContext(req, new mocks.MockResponse(), {});
- assert.deepEqual(ctx.getAuth(), {
- username: 'Chandru',
- password: 'Pass'
- });
- next();
- },
-
- 'Digest getAuth().': function(next) {
- var req = new mocks.MockRequest('GET', '/', {
- authorization: 'Digest key1="value1", key2=value2'
- });
- var ctx = new RequestContext(req, new mocks.MockResponse(), {});
- assert.deepEqual(ctx.getAuth(), {
- key1: 'value1',
- key2: 'value2'
- });
- next();
- },
-
- 'Auth Challenge.': function(next) {
- var req = new mocks.MockRequest('GET', '/', {}),
- res = new mocks.MockResponse();
-
- var ctx = new RequestContext(req, res, {});
- ctx.challengeAuth('Digest', {key1: 'value1', key2: 'value2'});
- assert.deepEqual(ctx.headers['www-authenticate'],
- 'Digest key1="value1",key2="value2"');
- next();
- }
-};
-
-if(process.argv[1] == __filename)
- require('../common/ghunit').test(suite);
View
137 test/simple/context-test.js
@@ -0,0 +1,137 @@
+var assert = require('assert'),
+ mocks = require('../common/mocks'),
+ MockRequest = mocks.MockRequest,
+ MockResponse = mocks.MockResponse;
+
+var context = require('../../grasshopper/lib/context'),
+ base64 = require('../../grasshopper/lib/base64'),
+ RequestContext = context.RequestContext;
+
+var suite = {name: 'Request Context Tests'};
+exports.suite = suite;
+
+suite.setupOnce = function(next) {
+ mocks.mockModule('../../grasshopper/lib/i18n', {
+ init: function(ctx) {
+ ctx.locale = 'initialized-locale';
+ }
+ });
+ next();
+};
+
+suite.tearDownOnce = function(next) {
+ mocks.unmockModule('../../grasshopper/lib/i18n');
+ next();
+};
+
+suite.tests = {
+ 'Context Initialization.': function(next) {
+ var req = new MockRequest('GET', '/test.txt', {
+ cookie: 'name=Chandru; city=Bangalore'
+ });
+ var res = new MockResponse();
+
+ var ctx = new RequestContext(req, res);
+ assert.equal(ctx.request, req);
+ assert.equal(ctx.response, res);
+ assert.deepEqual(ctx.model, {});
+ assert.equal(ctx.status, 200);
+ assert.equal(ctx.extn, 'txt');
+ assert.equal(ctx.encoding, 'utf8');
+ assert.equal(ctx.headers['content-type'], 'text/plain');
+ assert.equal(ctx.headers['date'], new Date().toUTCString());
+ assert.deepEqual(ctx.requestCookies, {
+ name: 'Chandru',
+ city: 'Bangalore'
+ });
+ assert.equal(ctx.locale, 'initialized-locale');
+ assert.equal(ctx.charset, 'UTF-8');
+ next();
+ },
+
+ 'Render Text.': function(next) {
+ var req = new MockRequest('GET', '/test.txt', {
+ cookie: 'name=Chandru; city=Bangalore'
+ });
+ var res = new MockResponse();
+
+ var ctx = new RequestContext(req, res);
+ ctx.renderText('Hello');
+ assert.deepEqual(res.headers, {
+ 'content-type': 'text/plain; charset=UTF-8',
+ date: new Date().toUTCString(),
+ 'x-powered-by': 'Grasshopper/0.3.3'
+ });
+ assert.deepEqual(res.chunks, ['Hello']);
+ assert.deepEqual(res.encodings, ['utf8']);
+ assert.ok(!res.writable);
+ next();
+ },
+
+ 'Disable cache.': function(next) {
+ var req = new MockRequest('GET', '/test.txt', {
+ cookie: 'name=Chandru; city=Bangalore'
+ });
+ var res = new MockResponse();
+
+ var ctx = new RequestContext(req, res);
+ ctx.disableCache();
+ assert.equal(ctx.headers['expires'], 'Thu, 11 Mar 2010 12:48:43 GMT');
+ assert.equal(ctx.headers['cache-control'],
+ 'no-store, no-cache, must-revalidate');
+ assert.equal(ctx.headers['pragma'], 'no-cache');
+
+ next();
+ },
+
+ 'Basic getAuth().': function(next) {
+ var req = new mocks.MockRequest('GET', '/', {
+ authorization: 'Basic ' + base64.encode('Chandru:Pass')
+ });
+ var ctx = new RequestContext(req, new mocks.MockResponse());
+ assert.deepEqual(ctx.getAuth(), {
+ username: 'Chandru',
+ password: 'Pass'
+ });
+ next();
+ },
+
+ 'Digest getAuth().': function(next) {
+ var req = new mocks.MockRequest('GET', '/', {
+ authorization: 'Digest key1="value1", key2=value2'
+ });
+ var ctx = new RequestContext(req, new mocks.MockResponse());
+ assert.deepEqual(ctx.getAuth(), {
+ key1: 'value1',
+ key2: 'value2'
+ });
+ next();
+ },
+
+ 'Auth Challenge.': function(next) {
+ var req = new mocks.MockRequest('GET', '/', {}),
+ res = new mocks.MockResponse();
+
+ var ctx = new RequestContext(req, res);
+ ctx.challengeAuth('Digest', {key1: 'value1', key2: 'value2'});
+ assert.deepEqual(ctx.headers['www-authenticate'],
+ 'Digest key1="value1",key2="value2"');
+ next();
+ },
+
+ 'Render Error.': function(next) {
+ var req = new MockRequest('GET', '/test.txt', {
+ cookie: 'name=Chandru; city=Bangalore'
+ });
+ var res = new MockResponse();
+
+ var ctx = new RequestContext(req, res);
+ ctx.renderError(500, function() {
+ assert.equal(res.statusCode, 500);
+ next();
+ });
+ }
+};
+
+if(process.argv[1] == __filename)
+ require('../common/ghunit').test(suite);
View
4 test/simple/test.js
@@ -1,5 +1,4 @@
var suites = [
- require('./auth-test').suite,
require('./dispatcher-test').suite,
require('./ghp-test').suite,
require('./gzip-test').suite,
@@ -8,7 +7,8 @@ var suites = [
require('./model-test').suite,
require('./routes-test').suite,
require('./wrapper-test').suite,
- require('./params-test').suite
+ require('./params-test').suite,
+ require('./context-test').suite
];
require('../common/ghunit').test.apply(null, suites);
Please sign in to comment.
Something went wrong with that request. Please try again.