-
-
Notifications
You must be signed in to change notification settings - Fork 935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite Got #1051
Rewrite Got #1051
Conversation
I got chills down my spine when I read the PR title 😅 I hope you plan to add lots of tests. I'm very paranoid that we'll end up breaking a lot of stuff that we're not currently testing. |
Yup. Every fix & every change in behavior is gonna be tested.
Don't worry! There's gonna be a very detailed changelog. I'll explain why things are done like that and not in another way. |
// got - stream x 4,313 ops/sec ±5.61% (72 runs sampled) | ||
// got - promise core x 6,756 ops/sec ±5.32% (80 runs sampled) | ||
// got - stream core x 6,863 ops/sec ±4.68% (76 runs sampled) | ||
// got - stream core - normalized options x 7,960 ops/sec ±3.83% (81 runs sampled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normalizing cuts out ~1k req/s but the time is needed to duplicate the options, so the original ones won't be altered if you use hooks to modify the request. Hooks are our main adventage while being compared to other HTTP clients.
// got - promise x 3,092 ops/sec ±5.25% (73 runs sampled) | ||
// got - stream x 4,313 ops/sec ±5.61% (72 runs sampled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why, but the got
function cuts out 3k-4k req/s.
@@ -10,7 +10,7 @@ | |||
"node": ">=10" | |||
}, | |||
"scripts": { | |||
"test": "xo && tsc --noEmit && nyc ava", | |||
"test": "xo && tsc --noEmit && nyc --reporter=html --reporter=text ava", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very useful to debug later. Generates both HTML output and text output as usual.
import Request, {knownHookEvents, RequestError, HTTPError} from '../core'; | ||
|
||
if (!knownHookEvents.includes('beforeRetry' as any)) { | ||
knownHookEvents.push('beforeRetry' as any, 'afterResponse' as any); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The normalizing will be done via Request.normalizeArguments
so we just tell Request
that it needs to normalize beforeRetry
too.
if (this._throwHttpErrors && !isHTTPError) { | ||
this.destroy(error); | ||
} else { | ||
this.emit('error', error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot destroy this if the retry
functionality is enabled. This is the responsibility of the Got promise.
|
||
// Download body | ||
try { | ||
body = await getStream.buffer(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is request
, not response
. If we were downloading directly from the response
there would be no progress events emitted.
@@ -0,0 +1,149 @@ | |||
import PCancelable = require('p-cancelable'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file just adds promise-specific options.
@@ -1,144 +0,0 @@ | |||
import duplexer3 = require('duplexer3'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got is based directly on Duplex streams.
} | ||
|
||
if (options.encoding) { | ||
proxy.setEncoding(options.encoding); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change: options.encoding
will no longer affect streams (it's the same behavior as in Got 9), people should do got.stream(...).setEncoding(encoding)
instead. It should be mentioned in the docs that this option is for promises only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also be documented as a doc comment on the options.encoding
type.
// We cannot use `stream.pipeline(...)` here, | ||
// because if we did then `output` would throw | ||
// the original error before throwing `ReadError`. | ||
response.pipe(output); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new implementation doesn't do this. It just proxies the _read
calls (basically a Duplex stream).
@@ -0,0 +1,73 @@ | |||
/* istanbul ignore file: deprecated */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: searchParams
is handled via the Request.normalizeArguments
, so it won't be present here (as well as options.username
and options.password
).
import {TimeoutError as TimedOutError} from './utils/timed-out'; | ||
import {Response, NormalizedOptions} from './types'; | ||
|
||
export class GotError extends Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking: renamed GotError
to RequestError
options.decompress = Boolean(options.decompress); | ||
options.isStream = Boolean(options.isStream); | ||
options.throwHttpErrors = Boolean(options.throwHttpErrors); | ||
options.ignoreInvalidCookies = Boolean(options.ignoreInvalidCookies); | ||
options.cache = options.cache ?? false; | ||
options.responseType = options.responseType ?? 'text'; | ||
options.resolveBodyOnly = Boolean(options.resolveBodyOnly); | ||
options.followRedirect = Boolean(options.followRedirect); | ||
options.dnsCache = options.dnsCache ?? false; | ||
options.useElectronNet = Boolean(options.useElectronNet); | ||
options.methodRewriting = Boolean(options.methodRewriting); | ||
options.allowGetBody = Boolean(options.allowGetBody); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been dropped to get a 1.5k req/s increase. It will be inherited from defaults so it's unnecessary either because the defaults define these booleans.
import {Transform as TransformStream} from 'stream'; | ||
import is from '@sindresorhus/is'; | ||
|
||
export function createProgressStream(name: 'downloadProgress' | 'uploadProgress', emitter: EventEmitter, totalBytes?: number | string): TransformStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been removed in favor of the Duplex stream (yeah, we can do things like this there).
// `request.aborted` is a boolean since v11.0.0: https://github.com/nodejs/node/commit/4b00c4fafaa2ae8c41c1f78823c0feb810ae4723#diff-e3bc37430eb078ccbafe3aa3b570c91a | ||
const isAborted = (): boolean => typeof currentRequest.aborted === 'number' || (currentRequest.aborted as unknown as boolean); | ||
|
||
const emitError = async (error: GeneralError): Promise<void> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been renamed to request._beforeError
.
|
||
isPiped = true; | ||
|
||
await pipeline( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to use pipeline
if we do Duplex
.
} | ||
}; | ||
|
||
emitter.retry = error => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been moved to as-promise/index.ts
.
|
||
Note: Not available when the response is cached. This is hopefully a temporary limitation, see [lukechilds/cacheable-request#86](https://github.com/lukechilds/cacheable-request/issues/86). | ||
*/ | ||
ip: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been corrected to ip?: string;
export interface RetryOptions extends Partial<DefaultRetryOptions> { | ||
retries?: number; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it even here? It has been removed in Got 10...
export interface AgentByProtocol { | ||
http?: http.Agent; | ||
https?: https.Agent; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change: options.agent
can no longer be an instance of http.Agent
. Instead, you need to pass an object like:
{
http: new http.Agent(...),
https: new https.Agent(...),
http2: new http2wrapper.Agent(...)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you mention all the breaking changes in the PR description?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do! :D
Also double-checked the types and the logic in general. Didn't notice any bugs other than those in the PR description (just nitpicks, nothing breaking). |
Wow! This includes a lot of fixes and improvements. Really nice work, @szmarczak! 🎉 |
You might want to list the fix for #1090 as a breaking change in case someone was depending on forwarded authorization headers. |
@@ -7,10 +7,10 @@ | |||
"funding": "https://github.com/sindresorhus/got?sponsor=1", | |||
"main": "dist/source", | |||
"engines": { | |||
"node": ">=10" | |||
"node": ">=10.19.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@szmarczak Sorry to be commenting on a merged PR, but is there a reason you had to bump this? This should probably be mentioned as a breaking change?
Changelog
Bug fixes
beforeRequest
hook is not called before the actual request #994 (tested)resolveBodyOnly
option #1140 (tested)Breaking changes
API
options.encoding
will no longer affect streams (reverting back to the Got 9 behavior), usegot.stream(...).setEncoding(encoding)
instead (to prevent confusion)GotError
toRequestError
options.agent
can no longer be an instance ofhttp.Agent
- instead you need to pass:options.useElectronNet
has been removeddnsCache
is now enabled by defaultdnsCache
no longer can be a Map or a Keyv instance, instead you need to pass a CacheableLookup instanceinit
hooks will be converted toRequestError
s (why:RequestError
provides much more useful information (e.g. Got options) than the usual TypeError etc.)options._pagination
has been renamed tooptions.pagination
options.url
property will no longer be visible in init hooks (design limitation)error.request
property is no longer aClientRequest
instance - instead, it gives a Got streamTypes
Defaults
type toInstanceDefaults
DefaultOptions
type toDefaults
DefaultRetryOptions
type toRequiredRetryOptions
GotOptions
type toOptions
ResponseObject
type has been merged into theResponse
typeGotRequestMethod
type toGotRequestFunction
Enhancements
merge
function is slow #1016 (benchmarked)error.code
instead oferror.message
to compare errors #981 (refactored)as-promise.ts
#932 (refactored)init
hook tobeforeError
hook #929 (tested)cacheable-lookup
#1058 (tested)fastify
in tests instead ofexpress
#1093)+
in query strings #1113 (tested)got.stream(...)
#1129 (tested)request
property (it's a Got stream).Miscellaneous
Known bugs
timings
indicate that the request was successful (even though it errored).downloadProgress
object may show incorrect data if the request has errored.TODO
progress.total
#924 (PR: Testprogress.total
and improve docs #1061)allowGetBody
to allow GET requests with payload #1081core cleanup(can't extract the types into a different file becauseOptions
,RequestError
s andRequest
need each other)normalizeArguments
into a different filecreate.ts
cleanup (will have a follow-up PR)options._pagination
tooptions.pagination
got.paginate(...)
typings #1099FEEL FREE TO TEST THIS
Note that this has
yet undocumented breaking changes,buteverything should work as expected - all tests pass. TypeScript recommended.IssueHunt Summary
Referenced issues
This pull request has been submitted to:
IssueHunt has been backed by the following sponsors. Become a sponsor