Permalink
Browse files

add res.jsonp(). Closes #1307

this also removes the jsonp
  • Loading branch information...
1 parent cdb3e9d commit a6caa267bd1948f014885d6fe8463cd7d1b7af77 @tj tj committed Aug 30, 2012
Showing with 287 additions and 76 deletions.
  1. +47 −7 lib/response.js
  2. +8 −69 test/res.json.js
  3. +220 −0 test/res.jsonp.js
  4. +12 −0 test/res.send.js
View
@@ -183,19 +183,59 @@ res.json = function(obj){
}
// settings
- var app = this.app
- , jsonp = app.get('jsonp callback')
- , replacer = app.get('json replacer')
- , spaces = app.get('json spaces')
- , body = JSON.stringify(obj, replacer, spaces)
- , callback = this.req.query[app.get('jsonp callback name')];
+ var app = this.app;
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
+ var body = JSON.stringify(obj, replacer, spaces);
+
+ // content-type
+ this.charset = this.charset || 'utf-8';
+ this.set('Content-Type', 'application/json');
+
+ return this.send(body);
+};
+
+/**
+ * Send JSON response with JSONP callback support.
+ *
+ * Examples:
+ *
+ * res.jsonp(null);
+ * res.jsonp({ user: 'tj' });
+ * res.jsonp(500, 'oh noes!');
+ * res.jsonp(404, 'I dont have that');
+ *
+ * @param {Mixed} obj or status
+ * @param {Mixed} obj
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.jsonp = function(obj){
+ // allow status / body
+ if (2 == arguments.length) {
+ // res.json(body, status) backwards compat
+ if ('number' == typeof arguments[1]) {
+ this.statusCode = arguments[1];
+ } else {
+ this.statusCode = obj;
+ obj = arguments[1];
+ }
+ }
+
+ // settings
+ var app = this.app;
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
+ var body = JSON.stringify(obj, replacer, spaces);
+ var callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.charset = this.charset || 'utf-8';
this.set('Content-Type', 'application/json');
// jsonp
- if (callback && jsonp) {
+ if (callback) {
this.set('Content-Type', 'text/javascript');
body = callback.replace(/[^\[\]\w$.]/g, '') + '(' + body + ');';
}
View
@@ -5,77 +5,16 @@ var express = require('../')
describe('res', function(){
describe('.json(object)', function(){
- describe('when "jsonp callback" is enabled', function(){
- it('should respond with jsonp', function(done){
- var app = express();
-
- app.enable('jsonp callback');
-
- app.use(function(req, res){
- res.json({ count: 1 });
- });
-
- request(app)
- .get('/?callback=something')
- .end(function(err, res){
- res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
- res.text.should.equal('something({"count":1});');
- done();
- })
- })
-
- it('should allow renaming callback', function(done){
- var app = express();
-
- app.enable('jsonp callback');
- app.set('jsonp callback name', 'clb');
-
- app.use(function(req, res){
- res.json({ count: 1 });
- });
-
- request(app)
- .get('/?clb=something')
- .end(function(err, res){
- res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
- res.text.should.equal('something({"count":1});');
- done();
- })
- })
-
- it('should allow []', function(done){
- var app = express();
-
- app.enable('jsonp callback');
- app.use(function(req, res){
- res.json({ count: 1 });
- });
-
- request(app)
- .get('/?callback=callbacks[123]')
- .end(function(err, res){
- res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
- res.text.should.equal('callbacks[123]({"count":1});');
- done();
- })
- })
-
- it('should disallow arbitrary js', function(done){
- var app = express();
+ it('should not support jsonp callbacks', function(done){
+ var app = express();
- app.enable('jsonp callback');
- app.use(function(req, res){
- res.json({});
- });
+ app.use(function(req, res){
+ res.json({ foo: 'bar' });
+ });
- request(app)
- .get('/?callback=foo;bar()')
- .end(function(err, res){
- res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
- res.text.should.equal('foobar({});');
- done();
- })
- })
+ request(app)
+ .get('/?callback=foo')
+ .expect('{"foo":"bar"}', done);
})
describe('when given primitives', function(){
View
@@ -0,0 +1,220 @@
+
+var express = require('../')
+ , request = require('./support/http')
+ , assert = require('assert');
+
+describe('res', function(){
+ describe('.jsonp(object)', function(){
+ it('should respond with jsonp', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp({ count: 1 });
+ });
+
+ request(app)
+ .get('/?callback=something')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
+ res.text.should.equal('something({"count":1});');
+ done();
+ })
+ })
+
+ it('should allow renaming callback', function(done){
+ var app = express();
+
+ app.set('jsonp callback name', 'clb');
+
+ app.use(function(req, res){
+ res.jsonp({ count: 1 });
+ });
+
+ request(app)
+ .get('/?clb=something')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
+ res.text.should.equal('something({"count":1});');
+ done();
+ })
+ })
+
+ it('should allow []', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp({ count: 1 });
+ });
+
+ request(app)
+ .get('/?callback=callbacks[123]')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
+ res.text.should.equal('callbacks[123]({"count":1});');
+ done();
+ })
+ })
+
+ it('should disallow arbitrary js', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp({});
+ });
+
+ request(app)
+ .get('/?callback=foo;bar()')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
+ res.text.should.equal('foobar({});');
+ done();
+ })
+ })
+
+ describe('when given primitives', function(){
+ it('should respond with json', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp(null);
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.text.should.equal('null');
+ done();
+ })
+ })
+ })
+
+ describe('when given an array', function(){
+ it('should respond with json', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp(['foo', 'bar', 'baz']);
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.text.should.equal('["foo","bar","baz"]');
+ done();
+ })
+ })
+ })
+
+ describe('when given an object', function(){
+ it('should respond with json', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp({ name: 'tobi' });
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.text.should.equal('{"name":"tobi"}');
+ done();
+ })
+ })
+ })
+
+ describe('"json replacer" setting', function(){
+ it('should be passed to JSON.stringify()', function(done){
+ var app = express();
+
+ app.set('json replacer', function(key, val){
+ return '_' == key[0]
+ ? undefined
+ : val;
+ });
+
+ app.use(function(req, res){
+ res.jsonp({ name: 'tobi', _id: 12345 });
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.text.should.equal('{"name":"tobi"}');
+ done();
+ });
+ })
+ })
+
+ describe('"json spaces" setting', function(){
+ it('should default to 2 in development', function(){
+ process.env.NODE_ENV = 'development';
+ var app = express();
+ app.get('json spaces').should.equal(2);
+ process.env.NODE_ENV = 'test';
+ })
+
+ it('should be undefined otherwise', function(){
+ var app = express();
+ assert(undefined === app.get('json spaces'));
+ })
+
+ it('should be passed to JSON.stringify()', function(done){
+ var app = express();
+
+ app.set('json spaces', 2);
+
+ app.use(function(req, res){
+ res.jsonp({ name: 'tobi', age: 2 });
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.text.should.equal('{\n "name": "tobi",\n "age": 2\n}');
+ done();
+ });
+ })
+ })
+ })
+
+ describe('.json(status, object)', function(){
+ it('should respond with json and set the .statusCode', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp(201, { id: 1 });
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.statusCode.should.equal(201);
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.text.should.equal('{"id":1}');
+ done();
+ })
+ })
+ })
+
+ describe('.json(object, status)', function(){
+ it('should respond with json and set the .statusCode for backwards compat', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.jsonp({ id: 1 }, 201);
+ });
+
+ request(app)
+ .get('/')
+ .end(function(err, res){
+ res.statusCode.should.equal(201);
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.text.should.equal('{"id":1}');
+ done();
+ })
+ })
+ })
+})
View
@@ -322,4 +322,16 @@ describe('res', function(){
.expect('hey')
.expect(500, done);
})
+
+ it('should not support jsonp callbacks', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.send({ foo: 'bar' });
+ });
+
+ request(app)
+ .get('/?callback=foo')
+ .expect('{"foo":"bar"}', done);
+ })
})

0 comments on commit a6caa26

Please sign in to comment.