Skip to content

Commit

Permalink
Add support for flushing headers
Browse files Browse the repository at this point in the history
  • Loading branch information
PlasmaPower committed Mar 4, 2016
1 parent 291c7c3 commit 6a14772
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 6 deletions.
4 changes: 4 additions & 0 deletions docs/api/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,7 @@ this.response.etag = crypto.createHash('md5').update(this.body).digest('hex');
### response.vary(field)

Vary on `field`.

### response.flushHeaders()

Flush any set headers, and begin the body.
16 changes: 11 additions & 5 deletions lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function respond(ctx) {
if (false === ctx.respond) return;

const res = ctx.res;
if (res.headersSent || !ctx.writable) return;
if (!ctx.writable) return;

let body = ctx.body;
const code = ctx.status;
Expand All @@ -194,15 +194,19 @@ function respond(ctx) {
}

if ('HEAD' == ctx.method) {
if (isJSON(body)) ctx.length = Buffer.byteLength(JSON.stringify(body));
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}

// status body
if (null == body) {
ctx.type = 'text';
body = ctx.message || String(code);
ctx.length = Buffer.byteLength(body);
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}

Expand All @@ -213,6 +217,8 @@ function respond(ctx) {

// body: json
body = JSON.stringify(body);
ctx.length = Buffer.byteLength(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
1 change: 1 addition & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ delegate(proto, 'response')
.method('vary')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
Expand Down
10 changes: 10 additions & 0 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ module.exports = {
set status(code) {
assert('number' == typeof code, 'status code must be a number');
assert(statuses[code], `invalid status code: ${code}`);
assert(!this.res.headersSent, 'headers have already been sent');
this._explicitStatus = true;
this.res.statusCode = code;
this.res.statusMessage = statuses[code];
Expand Down Expand Up @@ -130,6 +131,8 @@ module.exports = {
const original = this._body;
this._body = val;

if (this.res.headersSent) return;

// no content
if (null == val) {
if (!statuses.empty[this.status]) this.status = 204;
Expand Down Expand Up @@ -521,5 +524,12 @@ module.exports = {
'message',
'header'
]);
},

/**
* Flush any set headers, and begin the body
*/
flushHeaders() {
this.res.writeHead(this.res.statusCode);
}
};
7 changes: 6 additions & 1 deletion test/application/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,12 @@ describe('app.respond', () => {
ctx.status = 200;
res.setHeader('Content-Type', 'text/html');
res.write('Hello');
setTimeout(() => res.end('Goodbye'), 0);
return new Promise(resolve => {
setTimeout(() => {
res.end('Goodbye');
resolve();
}, 0);
});
});

const server = app.listen();
Expand Down
98 changes: 98 additions & 0 deletions test/response/flushHeaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

'use strict';

const request = require('supertest');
const assert = require('assert');
const Koa = require('../..');

describe('ctx.flushHeaders()', () => {
it('should set headersSent', done => {
const app = new Koa();

app.use((ctx, next) => {
ctx.body = 'Body';
ctx.status = 200;
ctx.flushHeaders();
assert(ctx.res.headersSent);
});

const server = app.listen();

request(server)
.get('/')
.expect(200)
.expect('Body', done);
});

it('should allow a response afterwards', done => {
const app = new Koa();

app.use((ctx, next) => {
ctx.status = 200;
ctx.res.setHeader('Content-Type', 'text/plain');
ctx.flushHeaders();
ctx.body = 'Body';
});

const server = app.listen();
request(server)
.get('/')
.expect(200)
.expect('Content-Type', 'text/plain')
.expect('Body', done);
});

it('should send the correct status code', done => {
const app = new Koa();

app.use((ctx, next) => {
ctx.status = 401;
ctx.res.setHeader('Content-Type', 'text/plain');
ctx.flushHeaders();
ctx.body = 'Body';
});

const server = app.listen();
request(server)
.get('/')
.expect(401)
.expect('Content-Type', 'text/plain')
.expect('Body', done);
});

it('should fail to set the headers after flushHeaders', done => {
const app = new Koa();

app.use((ctx, next) => {
ctx.status = 401;
ctx.res.setHeader('Content-Type', 'text/plain');
ctx.flushHeaders();
ctx.body = '';
try {
ctx.set('X-Shouldnt-Work', 'Value');
} catch (err) {
ctx.body += 'ctx.set fail ';
}
try {
ctx.status = 200;
} catch (err) {
ctx.body += 'ctx.status fail ';
}
try {
ctx.length = 10;
} catch (err) {
ctx.body += 'ctx.length fail';
}
});

const server = app.listen();
request(server)
.get('/')
.expect(401)
.expect('Content-Type', 'text/plain')
.expect('ctx.set fail ctx.status fail ctx.length fail', (err, res) => {
assert(res.headers['x-shouldnt-work'] === undefined, 'header set after flushHeaders');
done(err);
});
});
});

0 comments on commit 6a14772

Please sign in to comment.