Skip to content

Commit

Permalink
Throw on canceled request with incomplete response (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Apr 5, 2019
1 parent 8cc6e03 commit 92b1005
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 19 deletions.
5 changes: 5 additions & 0 deletions source/as-promise.ts
Expand Up @@ -40,6 +40,11 @@ export default function asPromise(options: Options) {
return;
}

if (response.req && response.req.aborted) {
// Canceled while downloading - will throw a CancelError or TimeoutError
return;
}

const limitStatusCode = options.followRedirect ? 299 : 399;

response.body = data;
Expand Down
5 changes: 4 additions & 1 deletion source/as-stream.ts
Expand Up @@ -23,7 +23,10 @@ export default function asStream(options: MergedOptions) {
const emitter = requestAsEventEmitter(options, input);

// Cancels the request
proxy._destroy = emitter.abort;
proxy._destroy = (error, callback) => {
callback(error);
emitter.abort();
};

emitter.on('response', (response: Response) => {
const {statusCode} = response;
Expand Down
57 changes: 57 additions & 0 deletions test/cancel.ts
Expand Up @@ -2,9 +2,11 @@ import {EventEmitter} from 'events';
import {Readable as ReadableStream} from 'stream';
import test from 'ava';
import pEvent from 'p-event';
import getStream from 'get-stream';
// @ts-ignore
import got, {CancelError} from '../source';
import withServer from './helpers/with-server';
import slowDataStream from './helpers/slow-data-stream';

const prepareServer = server => {
const emitter = new EventEmitter();
Expand Down Expand Up @@ -34,6 +36,14 @@ const prepareServer = server => {
return {emitter, promise};
};

const downloadHandler = (_request, response) => {
response.writeHead(200, {
'transfer-encoding': 'chunked'
});
response.flushHeaders();
slowDataStream().pipe(response);
};

test('does not retry after cancelation', withServer, async (t, server, got) => {
const {emitter, promise} = prepareServer(server);

Expand Down Expand Up @@ -145,3 +155,50 @@ test('recover from cancellation using error instance', async t => {

await t.notThrowsAsync(recover);
});

test('throws on incomplete (canceled) response - promise', withServer, async (t, server, got) => {
server.get('/', downloadHandler);

await t.throwsAsync(got({
timeout: {request: 500}
}), got.TimeoutError);
});

test('throws on incomplete (canceled) response - promise #2', withServer, async (t, server, got) => {
server.get('/', downloadHandler);

const promise = got('').on('response', () => {
setTimeout(() => promise.cancel(), 500);
});

await t.throwsAsync(promise, got.CancelError);
});

test('throws on incomplete (canceled) response - stream', withServer, async (t, server, got) => {
server.get('/', downloadHandler);

const errorString = 'Foobar';

const stream = got.stream('').on('response', () => {
setTimeout(() => stream.destroy(new Error(errorString)), 500);
});

await t.throwsAsync(getStream(stream), errorString);
});

// Note: it will throw, but the response is loaded already.
test('throws when canceling cached request', withServer, async (t, server, got) => {
server.get('/', (_request, response) => {
response.setHeader('Cache-Control', 'public, max-age=60');
response.end(Date.now().toString());
});

const cache = new Map();
await got({cache});

const promise = got({cache}).on('response', () => {
promise.cancel();
});

await t.throwsAsync(promise, got.CancelError);
});
18 changes: 18 additions & 0 deletions test/helpers/slow-data-stream.ts
@@ -0,0 +1,18 @@
import {PassThrough} from 'stream';

export default (): PassThrough => {
const slowStream = new PassThrough();
let count = 0;

const interval = setInterval(() => {
if (count++ < 10) {
slowStream.push('data\n'.repeat(100));
return;
}

clearInterval(interval);
slowStream.push(null);
}, 100);

return slowStream;
};
19 changes: 1 addition & 18 deletions test/timeout.ts
@@ -1,29 +1,12 @@
import http from 'http';
import net from 'net';
import stream from 'stream';
import getStream from 'get-stream';
import test from 'ava';
import pEvent from 'p-event';
import delay from 'delay';
import got from '../source';
import withServer from './helpers/with-server';

const slowDataStream = () => {
const slowStream = new stream.PassThrough();
let count = 0;

const interval = setInterval(() => {
if (count++ < 10) {
slowStream.push('data\n'.repeat(100));
return;
}

clearInterval(interval);
slowStream.push(null);
}, 100);

return slowStream;
};
import slowDataStream from './helpers/slow-data-stream';

const requestDelay = 800;

Expand Down

0 comments on commit 92b1005

Please sign in to comment.