Skip to content

Server side aborted request with Transfer-Encoding: chunked never resolve/reject #1096

@darksabrefr

Description

@darksabrefr
  • Node.js version: 13.9.0 at least to 13.11.0
  • OS & version: Ubuntu 19.10

A server side aborted request with Transfer-Encoding: chunked header conduces got to never resolve nor reject. I think got doesn't listen aborted event from IncomingMessage. The stream version of got is affected too and stream never emit end, abort or error but stays in a stale state.

const http = require('http');
const got = require('got');

// http server that aborts a chunked request
const server = http.createServer((req, res) => {
	res
		.on('error', (e) => {
			console.log(`server error: ${e}`)
		})
		.on('close', () => {
			console.log('server close')
		})
		.on('end', () => {
			console.log('server end')
		})
		.on('finish', () => {
			console.log('server finish')
		});

	res.writeHead(200, { 'Content-Type': 'text/plain' });
	res.write('chunk 1');

	setTimeout(() => res.write('chunk 2'), 1000);
	setTimeout(() => res.destroy(), 2000);

	// this destroy version leads to the same result
	// setTimeout(() => res.socket.destroy(), 2000);

	// a non-aborting version would call res.end() as it
	// setTimeout(() => res.end(), 2000);
});
server.listen(8000);

// got request
(async () => {
	try {
		const gotOptions = {timeout: {socket: 2000, request: 3000}};
		const res = await got('http://localhost:8000', gotOptions);
		console.log(`client res: res=${JSON.stringify(res.body)}`);
	} catch (e) {
		console.log(`client error: error=${e}`);
	}
})();

This only outputs server close but no client response because the got promise never resolves nor rejects even with timeouts set. IncomingMessage doesn't emit end event in this case (at least in the latest node version, maybe this affirmation was not true in past versions, see: nodejs/node#25081, from where the examples here are derived) but an aborted event. To show it, you can do that:

const http = require('http');

// http server that aborts a chunked request
const server = http.createServer((req, res) => {
	res
		.on('error', (e) => {
			console.log(`server error: ${e}`)
		})
		.on('close', () => {
			console.log('server close')
		})
		.on('end', () => {
			console.log('server end')
		})
		.on('finish', () => {
			console.log('server finish')
		});

	res.writeHead(200, { 'Content-Type': 'text/plain' });
	res.write('chunk 1');

	setTimeout(() => res.write('chunk 2'), 1000);
	setTimeout(() => res.destroy(), 2000);

	// this destroy version leads to the same result
	// setTimeout(() => res.socket.destroy(), 2000);

	// a non-aborting version would call res.end() as it
	// setTimeout(() => res.end(), 2000);
});
server.listen(8000);

// standard http request
const req = http.get('http://localhost:8000', res => {
	console.log('Status code:', res.statusCode);
	console.log('Raw headers:', res.rawHeaders);

	res
		.on('data', (data) => {
			console.log(`client data: complete=${res.complete}, aborted=${res.aborted}, data=${data.toString()}`)
		})
		.on('error', (e) => {
			console.log(`client error: complete=${res.complete}, aborted=${res.aborted}, error=${e}`)
		})
		.on('aborted', () => {
			console.log(`client aborted: complete=${res.complete}, aborted=${res.aborted}`)
		})
		.on('close', () => {
			console.log(`client close: complete=${res.complete}, aborted=${res.aborted}`)
		})
		.on('end', () => {
			console.log(`client end: complete=${res.complete}, aborted=${res.aborted}`)
		});
});

req.on('error', (e) => {
	console.log(`client error: error=${e}`);
});

Here, you can see a more precise output:

Status code: 200
Raw headers: [
 ... some headers ...
  'Transfer-Encoding',
  'chunked'
]
client data: complete=false, aborted=false, data=chunk 1
client data: complete=false, aborted=false, data=chunk 2
client aborted: complete=false, aborted=true
client close: complete=false, aborted=true
server close

Note there's no client end event but an aborted one. I have also included two statuses properties from IncomingMessage: complete (true after an end) and aborted (true after an aborted event)

Checklist

  • I have read the documentation.
  • I have tried my code with the latest version of Node.js and Got.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions