Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Apr 1, 2020
1 parent d85bc21 commit 394c834
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 23 deletions.
66 changes: 43 additions & 23 deletions source/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,24 @@ export class RequestError extends Error {
value: requestOrResponse
});

requestOrResponse = requestOrResponse.request;
}

if (requestOrResponse instanceof Request) {
Object.defineProperty(this, 'request', {
enumerable: false,
value: requestOrResponse.request
});
} else if (requestOrResponse instanceof Request) {
Object.defineProperty(this, 'request', {
enumerable: false,
value: requestOrResponse
});

this.timings = requestOrResponse.timings;
Object.defineProperty(this, 'response', {
enumerable: false,
value: requestOrResponse[kResponse]
});
}

this.timings = this.request?.timings;

// Recover the original stacktrace
if (!is.undefined(error.stack)) {
const indexOfMessage = this.stack.indexOf(this.message) + this.message.length;
Expand Down Expand Up @@ -476,6 +482,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
declare requestUrl: string;
finalized: boolean;
redirects: string[];
errored: boolean;

constructor(url: string | URL, options: Options = {}, defaults?: Defaults) {
super({
Expand All @@ -488,6 +495,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
this.finalized = false;
this[kServerResponsesPiped] = new Set<ServerResponse>();
this.redirects = [];
this.errored = false;

// TODO: Remove this when targeting Node.js >= 12
this._progressCallbacks = [];
Expand Down Expand Up @@ -948,6 +956,20 @@ export default class Request extends Duplex implements RequestEvents<Request> {

this[kIsFromCache] = typedResponse.isFromCache;

this[kResponseSize] = Number(response.headers['content-length']) || undefined;
this[kResponse] = response;

response.once('end', () => {
this[kResponseSize] = this[kDownloadedSize];
this.emit('downloadProgress', this.downloadProgress);
});

response.on('error', (error: Error) => {
this._beforeError(new ReadError(error, options, response as Response));
});

this.emit('downloadProgress', this.downloadProgress);

const rawCookies = response.headers['set-cookie'];
if (is.object(options.cookieJar) && rawCookies) {
let promises: Array<Promise<unknown>> = rawCookies.map(async (rawCookie: string) => (options.cookieJar as PromiseCookieJar).setCookie(rawCookie, url.toString()));
Expand Down Expand Up @@ -1054,9 +1076,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}
}

this[kResponseSize] = Number(response.headers['content-length']) || undefined;
this[kResponse] = response;

// We need to call `_read()` only when the Request stream is flowing
response.on('readable', () => {
if ((this as any).readableFlowing) {
Expand All @@ -1073,16 +1092,9 @@ export default class Request extends Duplex implements RequestEvents<Request> {
});

response.once('end', () => {
this[kResponseSize] = this[kDownloadedSize];
this.emit('downloadProgress', this.downloadProgress);

this.push(null);
});

response.on('error', (error: Error) => {
this._beforeError(new ReadError(error, options, response as Response));
});

for (const destination of this[kServerResponsesPiped]) {
if (destination.headersSent) {
continue;
Expand All @@ -1101,7 +1113,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
destination.statusCode = statusCode;
}

this.emit('downloadProgress', this.downloadProgress);
this.emit('response', response);
}

Expand Down Expand Up @@ -1276,28 +1287,37 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}
}

async _beforeError(error: RequestError): Promise<void> {
async _beforeError(error: Error): Promise<void> {
this.errored = true;

if (!(error instanceof RequestError)) {
error = new RequestError(error.message, error, this.options, this);
}

try {
const {response} = error;
const {response} = error as RequestError;
if (response && is.undefined(response.body)) {
response.body = await getStream.buffer(response, this.options);
response.body = await getStream(response, {
...this.options,
encoding: (this as any)._readableState.encoding
});
}
} catch (_) {}

try {
for (const hook of this.options.hooks.beforeError) {
// eslint-disable-next-line no-await-in-loop
error = await hook(error);
error = await hook(error as RequestError);
}
} catch (error_) {
error = error_;
error = new RequestError(error_.message, error_, this.options, this);
}

this.destroy(error);
}

_read(): void {
if (kResponse in this) {
if (kResponse in this && !this.errored) {
let data;

while ((data = this[kResponse]!.read()) !== null) {
Expand Down Expand Up @@ -1392,7 +1412,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}

if (error !== null && !is.undefined(error) && !(error instanceof RequestError)) {
error = new RequestError(error.message, error, this.options);
error = new RequestError(error.message, error, this.options, this);
}

callback(error);
Expand Down
22 changes: 22 additions & 0 deletions test/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,25 @@ test('works with pipeline', async t => {
message: 'connect ECONNREFUSED 127.0.0.1:7777'
});
});

test('errors have body', withServer, async (t, server, got) => {
server.get('/', (_request, response) => {
response.setHeader('set-cookie', 'foo=bar');
response.end('yay');
});

const error = await t.throwsAsync<RequestError>(getStream(got.stream('', {
cookieJar: {
setCookie: (_, __) => {
throw new Error('snap');
},
getCookieString: _ => {
return '';
}
}
})));

t.is(error.message, 'snap');
console.log(error.response);
t.is(error.response?.body, 'yay');
});

0 comments on commit 394c834

Please sign in to comment.