Hi,There
I did some testing on my demo app, find some strange result.
My demo app has a healthcheck, it returns with a 'ack' in the response (suppose 'ack' should be in response's body). code fragment shows below
app.get('/demoapp/healthcheck', function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('ack');
});
Then my test code shows below
var should = require('should');
var assert = require('assert');
var request = require('supertest');
describe('Routing', function() {
var url = 'http://localhost:8080';
describe('demoservice', function() {
it('do health check', function(done) {
request(url)
.get('/demoapp/healthcheck')
.expect(200,function(){
console.log('expect status');
}) //Status code
.end(function(err, res) {
if (err) {
throw err;
}
console.log(res);
console.log(res.text);
console.log(res.body);
res.text.should.have.match(/^ack/);
done();
});
});
});
});
In the console, output is so weird,
res shows it's 'body' is {}, but it has 'text' 'ackack' (not typo,it's real two ack), is there anybody can tell me why? Thanks in advance.
whole res shows in the console:
{ req:
{ domain: null,
_events: { drain: [Function], error: [Function], response: [Object] },
_maxListeners: 10,
output: [],
outputEncodings: [],
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_headerSent: true,
_header: 'GET /demoapp/healthcheck HTTP/1.1\r\nHost: localhost:10060\r\nAccept-Encoding: gzip, deflate\r\nCookie: \r\nConnection: close\r\n\r\n',
_hasBody: true,
_trailer: '',
finished: true,
_hangupClose: false,
socket:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Circular],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Circular],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
agent: false,
socketPath: undefined,
method: 'GET',
path: '/demoapp/healthcheck',
_headers:
{ host: 'localhost:10060',
'accept-encoding': 'gzip, deflate',
cookie: '' },
_headerNames:
{ host: 'Host',
'accept-encoding': 'Accept-Encoding',
cookie: 'Cookie' },
parser: null,
res:
{ _readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
socket: [Object],
connection: [Object],
httpVersion: '1.1',
complete: true,
headers: [Object],
trailers: {},
_pendings: [],
_pendingIndex: 0,
url: '',
method: null,
statusCode: 200,
client: [Object],
_consuming: true,
_dumped: false,
httpVersionMajor: 1,
httpVersionMinor: 1,
upgrade: false,
req: [Circular],
text: 'ackack',
pipe: [Function],
addListener: [Function],
on: [Function],
pause: [Function],
resume: [Function],
read: [Function],
body: undefined } },
res:
{ _readableState:
{ highWaterMark: 16384,
buffer: [],
length: 0,
pipes: null,
pipesCount: 0,
flowing: false,
ended: true,
endEmitted: true,
reading: false,
calledRead: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
objectMode: false,
defaultEncoding: 'utf8',
ranOut: false,
awaitDrain: 0,
readingMore: false,
decoder: [Object],
encoding: 'utf8' },
readable: false,
domain: null,
_events:
{ end: [Object],
data: [Object],
readable: [Function],
close: [Function],
error: [Function] },
_maxListeners: 10,
socket:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
httpVersion: '1.1',
complete: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
trailers: {},
_pendings: [],
_pendingIndex: 0,
url: '',
method: null,
statusCode: 200,
client:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
_consuming: true,
_dumped: false,
httpVersionMajor: 1,
httpVersionMinor: 1,
upgrade: false,
req:
{ domain: null,
_events: [Object],
_maxListeners: 10,
output: [],
outputEncodings: [],
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_headerSent: true,
_header: 'GET /demoapp/healthcheck HTTP/1.1\r\nHost: localhost:10060\r\nAccept-Encoding: gzip, deflate\r\nCookie: \r\nConnection: close\r\n\r\n',
_hasBody: true,
_trailer: '',
finished: true,
_hangupClose: false,
socket: [Object],
connection: [Object],
agent: false,
socketPath: undefined,
method: 'GET',
path: '/demoapp/healthcheck',
_headers: [Object],
_headerNames: [Object],
parser: null,
res: [Circular] },
text: 'ackack',
pipe: [Function],
addListener: [Function],
on: [Function],
pause: [Function],
resume: [Function],
read: [Function],
body: undefined },
links: {},
text: 'ackack',
body: {},
files: {},
buffered: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
header:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
statusCode: 200,
status: 200,
statusType: 2,
info: false,
ok: true,
redirect: false,
clientError: false,
serverError: false,
error: false,
accepted: false,
noContent: false,
badRequest: false,
unauthorized: false,
notAcceptable: false,
forbidden: false,
notFound: false,
type: 'text/plain',
setEncoding: [Function],
redirects: [] }
I'm having the same issue, did you figure out why it's occuring? Perhaps it's a bug.
cant read any of the code in here, please format stuff otherwise it's just noise
same issue
Looks like a bug when you provide a callback to expect() and also .end(). It ends up calling .end() internally twice, which is what creates the "ackack" instead of just "ack".
I suggest don't provide a callback to .end() for now.
I'm experiencing a similar issue although I'm not sure exactly the same cause...
I'm using express compress() middleware, and for responses above a threshold, the response is gzipped and chunked (so no Content-Length header is provided.)
When this occurs, superagent doesn't seem to buffer the response even though I'm using .buffer():
describe('GET /stats', () => {
it('should return os stats', done => {
request(app)
.get('/stats')
//.set('Accept-Encoding', null) // otherwise we get chunked encoding
.set('Accept', 'application/json')
.buffer(true)
.expect('Content-Type', /json/)
.expect(res => {
expect(res.text.length).to.be.greaterThan(0);
expect(res.body.hostname).to.be.a('string');
})
.expect(200,done);
})
});
Note that if I uncomment the Accept-Encoding line above, it prevents the client from providing that header, the server doesn't gzip/chunk the response and the test passes just fine.
I find it odd that superagent can't deal with something as common as Transfer-Encoding: chunked - hopefully I'm doing something wrong - but I can't figure out what, or figure out how to force the response body to be buffered and parsed.
Response object (with some redundant data removed)
{
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
res:
IncomingMessage {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: [Object],
length: 0,
pipes: null,
pipesCount: 0,
flowing: false,
ended: true,
endEmitted: true,
reading: false,
sync: false,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
defaultEncoding: 'utf8',
ranOut: false,
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: false,
domain: null,
_events: { end: [Object], error: [Object], close: [Function: bound emit] },
_eventsCount: 3,
_maxListeners: undefined,
socket:
Socket { ... },
connection:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 9,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 158,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Object],
read: [Function],
_consuming: true,
write: [Function: writeAfterFIN],
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: false,
headers:
{ 'x-powered-by': 'Express',
'x-content-type-options': 'nosniff',
'x-xss-protection': '1; mode=block',
'x-frame-options': 'SAMEORIGIN',
'access-control-allow-origin': '*',
'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept',
'access-control-allow-credentials': 'true',
'content-type': 'application/json; charset=utf-8',
etag: 'W/"51c-Gen7IbIahZKE/zqzs2CBRw"',
'set-cookie': [Object],
vary: 'Accept-Encoding',
'content-encoding': 'gzip',
date: 'Wed, 19 Oct 2016 15:30:09 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
rawHeaders: [ ... ],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 200,
statusMessage: 'OK',
client:
Socket { ... },
_consuming: true,
_dumped: false,
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET /stats HTTP/1.1\r\nHost: 127.0.0.1:52307\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: node-superagent/2.3.0\r\nAccept: application/json\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
method: 'GET',
path: '/stats',
_ended: true,
parser: null,
res: [Circular] },
setEncoding: [Function],
on: [Function],
text: '',
read: [Function],
body: '' },
links: {},
text: '',
body: '',
files: undefined,
buffered: true,
headers: { ... },
header:
{ 'x-powered-by': 'Express',
'x-content-type-options': 'nosniff',
'x-xss-protection': '1; mode=block',
'x-frame-options': 'SAMEORIGIN',
'access-control-allow-origin': '*',
'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept',
'access-control-allow-credentials': 'true',
'content-type': 'application/json; charset=utf-8',
etag: 'W/"51c-Gen7IbIahZKE/zqzs2CBRw"',
'set-cookie': [ 'connect.sid=s%3AlMI1wtXBJFjBtVmi-_OcrepHKaCszRxS.QOHfNGUyD06bDfCxQPHHG%2FvMmvwoTX2%2Bq%2FmpWYOCb5c; Path=/; HttpOnly' ],
vary: 'Accept-Encoding',
'content-encoding': 'gzip',
date: 'Wed, 19 Oct 2016 15:30:09 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
statusCode: 200,
status: 200,
statusType: 2,
info: false,
ok: true,
redirect: false,
clientError: false,
serverError: false,
error: false,
accepted: false,
noContent: false,
badRequest: false,
unauthorized: false,
notAcceptable: false,
forbidden: false,
notFound: false,
charset: 'utf-8',
type: 'application/json',
setEncoding: [Function: bound ],
redirects: [] }
+1
see #348 for a similar issue
+1
+1
+1
+1
+1
In my case it turned out that I didn't set the Content-Type header in responses.
Previously I was calling res.send(object), so the header was set to application/json automatically, but after changing the code to use res.write(...) and res.end(...) I had to set it myself. After that, superagent would read the whole chunked response correctly.