1654 lines
56 KiB
Plaintext
1654 lines
56 KiB
Plaintext
import process from 'node:process';
|
|
import { promisify, inspect } from 'node:util';
|
|
import { checkServerIdentity } from 'node:tls';
|
|
// DO NOT use destructuring for `https.request` and `http.request` as it's not compatible with `nock`.
|
|
import http from 'node:http';
|
|
import https from 'node:https';
|
|
import is, { assert } from '@sindresorhus/is';
|
|
import lowercaseKeys from 'lowercase-keys';
|
|
import CacheableLookup from 'cacheable-lookup';
|
|
import http2wrapper from 'http2-wrapper';
|
|
import { isFormData } from 'form-data-encoder';
|
|
import parseLinkHeader from './parse-link-header.js';
|
|
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
function validateSearchParameters(searchParameters) {
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const key in searchParameters) {
|
|
const value = searchParameters[key];
|
|
assert.any([is.string, is.number, is.boolean, is.null_, is.undefined], value);
|
|
}
|
|
}
|
|
const globalCache = new Map();
|
|
let globalDnsCache;
|
|
const getGlobalDnsCache = () => {
|
|
if (globalDnsCache) {
|
|
return globalDnsCache;
|
|
}
|
|
globalDnsCache = new CacheableLookup();
|
|
return globalDnsCache;
|
|
};
|
|
const defaultInternals = {
|
|
request: undefined,
|
|
agent: {
|
|
http: undefined,
|
|
https: undefined,
|
|
http2: undefined,
|
|
},
|
|
h2session: undefined,
|
|
decompress: true,
|
|
timeout: {
|
|
connect: undefined,
|
|
lookup: undefined,
|
|
read: undefined,
|
|
request: undefined,
|
|
response: undefined,
|
|
secureConnect: undefined,
|
|
send: undefined,
|
|
socket: undefined,
|
|
},
|
|
prefixUrl: '',
|
|
body: undefined,
|
|
form: undefined,
|
|
json: undefined,
|
|
cookieJar: undefined,
|
|
ignoreInvalidCookies: false,
|
|
searchParams: undefined,
|
|
dnsLookup: undefined,
|
|
dnsCache: undefined,
|
|
context: {},
|
|
hooks: {
|
|
init: [],
|
|
beforeRequest: [],
|
|
beforeError: [],
|
|
beforeRedirect: [],
|
|
beforeRetry: [],
|
|
afterResponse: [],
|
|
},
|
|
followRedirect: true,
|
|
maxRedirects: 10,
|
|
cache: undefined,
|
|
throwHttpErrors: true,
|
|
username: '',
|
|
password: '',
|
|
http2: false,
|
|
allowGetBody: false,
|
|
headers: {
|
|
'user-agent': 'got (https://github.com/sindresorhus/got)',
|
|
},
|
|
methodRewriting: false,
|
|
dnsLookupIpVersion: undefined,
|
|
parseJson: JSON.parse,
|
|
stringifyJson: JSON.stringify,
|
|
retry: {
|
|
limit: 2,
|
|
methods: [
|
|
'GET',
|
|
'PUT',
|
|
'HEAD',
|
|
'DELETE',
|
|
'OPTIONS',
|
|
'TRACE',
|
|
],
|
|
statusCodes: [
|
|
408,
|
|
413,
|
|
429,
|
|
500,
|
|
502,
|
|
503,
|
|
504,
|
|
521,
|
|
522,
|
|
524,
|
|
],
|
|
errorCodes: [
|
|
'ETIMEDOUT',
|
|
'ECONNRESET',
|
|
'EADDRINUSE',
|
|
'ECONNREFUSED',
|
|
'EPIPE',
|
|
'ENOTFOUND',
|
|
'ENETUNREACH',
|
|
'EAI_AGAIN',
|
|
],
|
|
maxRetryAfter: undefined,
|
|
calculateDelay: ({ computedValue }) => computedValue,
|
|
backoffLimit: Number.POSITIVE_INFINITY,
|
|
noise: 100,
|
|
},
|
|
localAddress: undefined,
|
|
method: 'GET',
|
|
createConnection: undefined,
|
|
cacheOptions: {
|
|
shared: undefined,
|
|
cacheHeuristic: undefined,
|
|
immutableMinTimeToLive: undefined,
|
|
ignoreCargoCult: undefined,
|
|
},
|
|
https: {
|
|
alpnProtocols: undefined,
|
|
rejectUnauthorized: undefined,
|
|
checkServerIdentity: undefined,
|
|
certificateAuthority: undefined,
|
|
key: undefined,
|
|
certificate: undefined,
|
|
passphrase: undefined,
|
|
pfx: undefined,
|
|
ciphers: undefined,
|
|
honorCipherOrder: undefined,
|
|
minVersion: undefined,
|
|
maxVersion: undefined,
|
|
signatureAlgorithms: undefined,
|
|
tlsSessionLifetime: undefined,
|
|
dhparam: undefined,
|
|
ecdhCurve: undefined,
|
|
certificateRevocationLists: undefined,
|
|
},
|
|
encoding: undefined,
|
|
resolveBodyOnly: false,
|
|
isStream: false,
|
|
responseType: 'text',
|
|
url: undefined,
|
|
pagination: {
|
|
transform(response) {
|
|
if (response.request.options.responseType === 'json') {
|
|
return response.body;
|
|
}
|
|
return JSON.parse(response.body);
|
|
},
|
|
paginate({ response }) {
|
|
const rawLinkHeader = response.headers.link;
|
|
if (typeof rawLinkHeader !== 'string' || rawLinkHeader.trim() === '') {
|
|
return false;
|
|
}
|
|
const parsed = parseLinkHeader(rawLinkHeader);
|
|
const next = parsed.find(entry => entry.parameters.rel === 'next' || entry.parameters.rel === '"next"');
|
|
if (next) {
|
|
return {
|
|
url: new URL(next.reference, response.url),
|
|
};
|
|
}
|
|
return false;
|
|
},
|
|
filter: () => true,
|
|
shouldContinue: () => true,
|
|
countLimit: Number.POSITIVE_INFINITY,
|
|
backoff: 0,
|
|
requestLimit: 10000,
|
|
stackAllItems: false,
|
|
},
|
|
setHost: true,
|
|
maxHeaderSize: undefined,
|
|
signal: undefined,
|
|
enableUnixSockets: false,
|
|
};
|
|
const cloneInternals = (internals) => {
|
|
const { hooks, retry } = internals;
|
|
const result = {
|
|
...internals,
|
|
context: { ...internals.context },
|
|
cacheOptions: { ...internals.cacheOptions },
|
|
https: { ...internals.https },
|
|
agent: { ...internals.agent },
|
|
headers: { ...internals.headers },
|
|
retry: {
|
|
...retry,
|
|
errorCodes: [...retry.errorCodes],
|
|
methods: [...retry.methods],
|
|
statusCodes: [...retry.statusCodes],
|
|
},
|
|
timeout: { ...internals.timeout },
|
|
hooks: {
|
|
init: [...hooks.init],
|
|
beforeRequest: [...hooks.beforeRequest],
|
|
beforeError: [...hooks.beforeError],
|
|
beforeRedirect: [...hooks.beforeRedirect],
|
|
beforeRetry: [...hooks.beforeRetry],
|
|
afterResponse: [...hooks.afterResponse],
|
|
},
|
|
searchParams: internals.searchParams ? new URLSearchParams(internals.searchParams) : undefined,
|
|
pagination: { ...internals.pagination },
|
|
};
|
|
if (result.url !== undefined) {
|
|
result.prefixUrl = '';
|
|
}
|
|
return result;
|
|
};
|
|
const cloneRaw = (raw) => {
|
|
const { hooks, retry } = raw;
|
|
const result = { ...raw };
|
|
if (is.object(raw.context)) {
|
|
result.context = { ...raw.context };
|
|
}
|
|
if (is.object(raw.cacheOptions)) {
|
|
result.cacheOptions = { ...raw.cacheOptions };
|
|
}
|
|
if (is.object(raw.https)) {
|
|
result.https = { ...raw.https };
|
|
}
|
|
if (is.object(raw.cacheOptions)) {
|
|
result.cacheOptions = { ...result.cacheOptions };
|
|
}
|
|
if (is.object(raw.agent)) {
|
|
result.agent = { ...raw.agent };
|
|
}
|
|
if (is.object(raw.headers)) {
|
|
result.headers = { ...raw.headers };
|
|
}
|
|
if (is.object(retry)) {
|
|
result.retry = { ...retry };
|
|
if (is.array(retry.errorCodes)) {
|
|
result.retry.errorCodes = [...retry.errorCodes];
|
|
}
|
|
if (is.array(retry.methods)) {
|
|
result.retry.methods = [...retry.methods];
|
|
}
|
|
if (is.array(retry.statusCodes)) {
|
|
result.retry.statusCodes = [...retry.statusCodes];
|
|
}
|
|
}
|
|
if (is.object(raw.timeout)) {
|
|
result.timeout = { ...raw.timeout };
|
|
}
|
|
if (is.object(hooks)) {
|
|
result.hooks = {
|
|
...hooks,
|
|
};
|
|
if (is.array(hooks.init)) {
|
|
result.hooks.init = [...hooks.init];
|
|
}
|
|
if (is.array(hooks.beforeRequest)) {
|
|
result.hooks.beforeRequest = [...hooks.beforeRequest];
|
|
}
|
|
if (is.array(hooks.beforeError)) {
|
|
result.hooks.beforeError = [...hooks.beforeError];
|
|
}
|
|
if (is.array(hooks.beforeRedirect)) {
|
|
result.hooks.beforeRedirect = [...hooks.beforeRedirect];
|
|
}
|
|
if (is.array(hooks.beforeRetry)) {
|
|
result.hooks.beforeRetry = [...hooks.beforeRetry];
|
|
}
|
|
if (is.array(hooks.afterResponse)) {
|
|
result.hooks.afterResponse = [...hooks.afterResponse];
|
|
}
|
|
}
|
|
// TODO: raw.searchParams
|
|
if (is.object(raw.pagination)) {
|
|
result.pagination = { ...raw.pagination };
|
|
}
|
|
return result;
|
|
};
|
|
const getHttp2TimeoutOption = (internals) => {
|
|
const delays = [internals.timeout.socket, internals.timeout.connect, internals.timeout.lookup, internals.timeout.request, internals.timeout.secureConnect].filter(delay => typeof delay === 'number');
|
|
if (delays.length > 0) {
|
|
return Math.min(...delays);
|
|
}
|
|
return undefined;
|
|
};
|
|
const init = (options, withOptions, self) => {
|
|
const initHooks = options.hooks?.init;
|
|
if (initHooks) {
|
|
for (const hook of initHooks) {
|
|
hook(withOptions, self);
|
|
}
|
|
}
|
|
};
|
|
export default class Options {
|
|
constructor(input, options, defaults) {
|
|
Object.defineProperty(this, "_unixOptions", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "_internals", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "_merging", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "_init", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
assert.any([is.string, is.urlInstance, is.object, is.undefined], input);
|
|
assert.any([is.object, is.undefined], options);
|
|
assert.any([is.object, is.undefined], defaults);
|
|
if (input instanceof Options || options instanceof Options) {
|
|
throw new TypeError('The defaults must be passed as the third argument');
|
|
}
|
|
this._internals = cloneInternals(defaults?._internals ?? defaults ?? defaultInternals);
|
|
this._init = [...(defaults?._init ?? [])];
|
|
this._merging = false;
|
|
this._unixOptions = undefined;
|
|
// This rule allows `finally` to be considered more important.
|
|
// Meaning no matter the error thrown in the `try` block,
|
|
// if `finally` throws then the `finally` error will be thrown.
|
|
//
|
|
// Yes, we want this. If we set `url` first, then the `url.searchParams`
|
|
// would get merged. Instead we set the `searchParams` first, then
|
|
// `url.searchParams` is overwritten as expected.
|
|
//
|
|
/* eslint-disable no-unsafe-finally */
|
|
try {
|
|
if (is.plainObject(input)) {
|
|
try {
|
|
this.merge(input);
|
|
this.merge(options);
|
|
}
|
|
finally {
|
|
this.url = input.url;
|
|
}
|
|
}
|
|
else {
|
|
try {
|
|
this.merge(options);
|
|
}
|
|
finally {
|
|
if (options?.url !== undefined) {
|
|
if (input === undefined) {
|
|
this.url = options.url;
|
|
}
|
|
else {
|
|
throw new TypeError('The `url` option is mutually exclusive with the `input` argument');
|
|
}
|
|
}
|
|
else if (input !== undefined) {
|
|
this.url = input;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
error.options = this;
|
|
throw error;
|
|
}
|
|
/* eslint-enable no-unsafe-finally */
|
|
}
|
|
merge(options) {
|
|
if (!options) {
|
|
return;
|
|
}
|
|
if (options instanceof Options) {
|
|
for (const init of options._init) {
|
|
this.merge(init);
|
|
}
|
|
return;
|
|
}
|
|
options = cloneRaw(options);
|
|
init(this, options, this);
|
|
init(options, options, this);
|
|
this._merging = true;
|
|
// Always merge `isStream` first
|
|
if ('isStream' in options) {
|
|
this.isStream = options.isStream;
|
|
}
|
|
try {
|
|
let push = false;
|
|
for (const key in options) {
|
|
// `got.extend()` options
|
|
if (key === 'mutableDefaults' || key === 'handlers') {
|
|
continue;
|
|
}
|
|
// Never merge `url`
|
|
if (key === 'url') {
|
|
continue;
|
|
}
|
|
if (!(key in this)) {
|
|
throw new Error(`Unexpected option: ${key}`);
|
|
}
|
|
// @ts-expect-error Type 'unknown' is not assignable to type 'never'.
|
|
const value = options[key];
|
|
if (value === undefined) {
|
|
continue;
|
|
}
|
|
// @ts-expect-error Type 'unknown' is not assignable to type 'never'.
|
|
this[key] = value;
|
|
push = true;
|
|
}
|
|
if (push) {
|
|
this._init.push(options);
|
|
}
|
|
}
|
|
finally {
|
|
this._merging = false;
|
|
}
|
|
}
|
|
/**
|
|
Custom request function.
|
|
The main purpose of this is to [support HTTP2 using a wrapper](https://github.com/szmarczak/http2-wrapper).
|
|
|
|
@default http.request | https.request
|
|
*/
|
|
get request() {
|
|
return this._internals.request;
|
|
}
|
|
set request(value) {
|
|
assert.any([is.function_, is.undefined], value);
|
|
this._internals.request = value;
|
|
}
|
|
/**
|
|
An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance.
|
|
This is necessary because a request to one protocol might redirect to another.
|
|
In such a scenario, Got will switch over to the right protocol agent for you.
|
|
|
|
If a key is not present, it will default to a global agent.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
import HttpAgent from 'agentkeepalive';
|
|
|
|
const {HttpsAgent} = HttpAgent;
|
|
|
|
await got('https://sindresorhus.com', {
|
|
agent: {
|
|
http: new HttpAgent(),
|
|
https: new HttpsAgent()
|
|
}
|
|
});
|
|
```
|
|
*/
|
|
get agent() {
|
|
return this._internals.agent;
|
|
}
|
|
set agent(value) {
|
|
assert.plainObject(value);
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const key in value) {
|
|
if (!(key in this._internals.agent)) {
|
|
throw new TypeError(`Unexpected agent option: ${key}`);
|
|
}
|
|
// @ts-expect-error - No idea why `value[key]` doesn't work here.
|
|
assert.any([is.object, is.undefined], value[key]);
|
|
}
|
|
if (this._merging) {
|
|
Object.assign(this._internals.agent, value);
|
|
}
|
|
else {
|
|
this._internals.agent = { ...value };
|
|
}
|
|
}
|
|
get h2session() {
|
|
return this._internals.h2session;
|
|
}
|
|
set h2session(value) {
|
|
this._internals.h2session = value;
|
|
}
|
|
/**
|
|
Decompress the response automatically.
|
|
|
|
This will set the `accept-encoding` header to `gzip, deflate, br` unless you set it yourself.
|
|
|
|
If this is disabled, a compressed response is returned as a `Buffer`.
|
|
This may be useful if you want to handle decompression yourself or stream the raw compressed data.
|
|
|
|
@default true
|
|
*/
|
|
get decompress() {
|
|
return this._internals.decompress;
|
|
}
|
|
set decompress(value) {
|
|
assert.boolean(value);
|
|
this._internals.decompress = value;
|
|
}
|
|
/**
|
|
Milliseconds to wait for the server to end the response before aborting the request with `got.TimeoutError` error (a.k.a. `request` property).
|
|
By default, there's no timeout.
|
|
|
|
This also accepts an `object` with the following fields to constrain the duration of each phase of the request lifecycle:
|
|
|
|
- `lookup` starts when a socket is assigned and ends when the hostname has been resolved.
|
|
Does not apply when using a Unix domain socket.
|
|
- `connect` starts when `lookup` completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected.
|
|
- `secureConnect` starts when `connect` completes and ends when the handshaking process completes (HTTPS only).
|
|
- `socket` starts when the socket is connected. See [request.setTimeout](https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback).
|
|
- `response` starts when the request has been written to the socket and ends when the response headers are received.
|
|
- `send` starts when the socket is connected and ends with the request has been written to the socket.
|
|
- `request` starts when the request is initiated and ends when the response's end event fires.
|
|
*/
|
|
get timeout() {
|
|
// We always return `Delays` here.
|
|
// It has to be `Delays | number`, otherwise TypeScript will error because the getter and the setter have incompatible types.
|
|
return this._internals.timeout;
|
|
}
|
|
set timeout(value) {
|
|
assert.plainObject(value);
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const key in value) {
|
|
if (!(key in this._internals.timeout)) {
|
|
throw new Error(`Unexpected timeout option: ${key}`);
|
|
}
|
|
// @ts-expect-error - No idea why `value[key]` doesn't work here.
|
|
assert.any([is.number, is.undefined], value[key]);
|
|
}
|
|
if (this._merging) {
|
|
Object.assign(this._internals.timeout, value);
|
|
}
|
|
else {
|
|
this._internals.timeout = { ...value };
|
|
}
|
|
}
|
|
/**
|
|
When specified, `prefixUrl` will be prepended to `url`.
|
|
The prefix can be any valid URL, either relative or absolute.
|
|
A trailing slash `/` is optional - one will be added automatically.
|
|
|
|
__Note__: `prefixUrl` will be ignored if the `url` argument is a URL instance.
|
|
|
|
__Note__: Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion.
|
|
For example, when the prefix URL is `https://example.com/foo` and the input is `/bar`, there's ambiguity whether the resulting URL would become `https://example.com/foo/bar` or `https://example.com/bar`.
|
|
The latter is used by browsers.
|
|
|
|
__Tip__: Useful when used with `got.extend()` to create niche-specific Got instances.
|
|
|
|
__Tip__: You can change `prefixUrl` using hooks as long as the URL still includes the `prefixUrl`.
|
|
If the URL doesn't include it anymore, it will throw.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
await got('unicorn', {prefixUrl: 'https://cats.com'});
|
|
//=> 'https://cats.com/unicorn'
|
|
|
|
const instance = got.extend({
|
|
prefixUrl: 'https://google.com'
|
|
});
|
|
|
|
await instance('unicorn', {
|
|
hooks: {
|
|
beforeRequest: [
|
|
options => {
|
|
options.prefixUrl = 'https://cats.com';
|
|
}
|
|
]
|
|
}
|
|
});
|
|
//=> 'https://cats.com/unicorn'
|
|
```
|
|
*/
|
|
get prefixUrl() {
|
|
// We always return `string` here.
|
|
// It has to be `string | URL`, otherwise TypeScript will error because the getter and the setter have incompatible types.
|
|
return this._internals.prefixUrl;
|
|
}
|
|
set prefixUrl(value) {
|
|
assert.any([is.string, is.urlInstance], value);
|
|
if (value === '') {
|
|
this._internals.prefixUrl = '';
|
|
return;
|
|
}
|
|
value = value.toString();
|
|
if (!value.endsWith('/')) {
|
|
value += '/';
|
|
}
|
|
if (this._internals.prefixUrl && this._internals.url) {
|
|
const { href } = this._internals.url;
|
|
this._internals.url.href = value + href.slice(this._internals.prefixUrl.length);
|
|
}
|
|
this._internals.prefixUrl = value;
|
|
}
|
|
/**
|
|
__Note #1__: The `body` option cannot be used with the `json` or `form` option.
|
|
|
|
__Note #2__: If you provide this option, `got.stream()` will be read-only.
|
|
|
|
__Note #3__: If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError` unless the method is `GET` and the `allowGetBody` option is set to `true`.
|
|
|
|
__Note #4__: This option is not enumerable and will not be merged with the instance defaults.
|
|
|
|
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
|
|
|
|
Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
|
|
*/
|
|
get body() {
|
|
return this._internals.body;
|
|
}
|
|
set body(value) {
|
|
assert.any([is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, isFormData, is.undefined], value);
|
|
if (is.nodeStream(value)) {
|
|
assert.truthy(value.readable);
|
|
}
|
|
if (value !== undefined) {
|
|
assert.undefined(this._internals.form);
|
|
assert.undefined(this._internals.json);
|
|
}
|
|
this._internals.body = value;
|
|
}
|
|
/**
|
|
The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).
|
|
|
|
If the `Content-Type` header is not present, it will be set to `application/x-www-form-urlencoded`.
|
|
|
|
__Note #1__: If you provide this option, `got.stream()` will be read-only.
|
|
|
|
__Note #2__: This option is not enumerable and will not be merged with the instance defaults.
|
|
*/
|
|
get form() {
|
|
return this._internals.form;
|
|
}
|
|
set form(value) {
|
|
assert.any([is.plainObject, is.undefined], value);
|
|
if (value !== undefined) {
|
|
assert.undefined(this._internals.body);
|
|
assert.undefined(this._internals.json);
|
|
}
|
|
this._internals.form = value;
|
|
}
|
|
/**
|
|
JSON body. If the `Content-Type` header is not set, it will be set to `application/json`.
|
|
|
|
__Note #1__: If you provide this option, `got.stream()` will be read-only.
|
|
|
|
__Note #2__: This option is not enumerable and will not be merged with the instance defaults.
|
|
*/
|
|
get json() {
|
|
return this._internals.json;
|
|
}
|
|
set json(value) {
|
|
if (value !== undefined) {
|
|
assert.undefined(this._internals.body);
|
|
assert.undefined(this._internals.form);
|
|
}
|
|
this._internals.json = value;
|
|
}
|
|
/**
|
|
The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url).
|
|
|
|
Properties from `options` will override properties in the parsed `url`.
|
|
|
|
If no protocol is specified, it will throw a `TypeError`.
|
|
|
|
__Note__: The query string is **not** parsed as search params.
|
|
|
|
@example
|
|
```
|
|
await got('https://example.com/?query=a b'); //=> https://example.com/?query=a%20b
|
|
await got('https://example.com/', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b
|
|
|
|
// The query string is overridden by `searchParams`
|
|
await got('https://example.com/?query=a b', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b
|
|
```
|
|
*/
|
|
get url() {
|
|
return this._internals.url;
|
|
}
|
|
set url(value) {
|
|
assert.any([is.string, is.urlInstance, is.undefined], value);
|
|
if (value === undefined) {
|
|
this._internals.url = undefined;
|
|
return;
|
|
}
|
|
if (is.string(value) && value.startsWith('/')) {
|
|
throw new Error('`url` must not start with a slash');
|
|
}
|
|
const urlString = `${this.prefixUrl}${value.toString()}`;
|
|
const url = new URL(urlString);
|
|
this._internals.url = url;
|
|
if (url.protocol === 'unix:') {
|
|
url.href = `http://unix${url.pathname}${url.search}`;
|
|
}
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
const error = new Error(`Unsupported protocol: ${url.protocol}`);
|
|
error.code = 'ERR_UNSUPPORTED_PROTOCOL';
|
|
throw error;
|
|
}
|
|
if (this._internals.username) {
|
|
url.username = this._internals.username;
|
|
this._internals.username = '';
|
|
}
|
|
if (this._internals.password) {
|
|
url.password = this._internals.password;
|
|
this._internals.password = '';
|
|
}
|
|
if (this._internals.searchParams) {
|
|
url.search = this._internals.searchParams.toString();
|
|
this._internals.searchParams = undefined;
|
|
}
|
|
if (url.hostname === 'unix') {
|
|
if (!this._internals.enableUnixSockets) {
|
|
throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
|
|
}
|
|
const matches = /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`);
|
|
if (matches?.groups) {
|
|
const { socketPath, path } = matches.groups;
|
|
this._unixOptions = {
|
|
socketPath,
|
|
path,
|
|
host: '',
|
|
};
|
|
}
|
|
else {
|
|
this._unixOptions = undefined;
|
|
}
|
|
return;
|
|
}
|
|
this._unixOptions = undefined;
|
|
}
|
|
/**
|
|
Cookie support. You don't have to care about parsing or how to store them.
|
|
|
|
__Note__: If you provide this option, `options.headers.cookie` will be overridden.
|
|
*/
|
|
get cookieJar() {
|
|
return this._internals.cookieJar;
|
|
}
|
|
set cookieJar(value) {
|
|
assert.any([is.object, is.undefined], value);
|
|
if (value === undefined) {
|
|
this._internals.cookieJar = undefined;
|
|
return;
|
|
}
|
|
let { setCookie, getCookieString } = value;
|
|
assert.function_(setCookie);
|
|
assert.function_(getCookieString);
|
|
/* istanbul ignore next: Horrible `tough-cookie` v3 check */
|
|
if (setCookie.length === 4 && getCookieString.length === 0) {
|
|
setCookie = promisify(setCookie.bind(value));
|
|
getCookieString = promisify(getCookieString.bind(value));
|
|
this._internals.cookieJar = {
|
|
setCookie,
|
|
getCookieString: getCookieString,
|
|
};
|
|
}
|
|
else {
|
|
this._internals.cookieJar = value;
|
|
}
|
|
}
|
|
/**
|
|
You can abort the `request` using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const request = got('https://httpbin.org/anything', {
|
|
signal: abortController.signal
|
|
});
|
|
|
|
setTimeout(() => {
|
|
abortController.abort();
|
|
}, 100);
|
|
```
|
|
*/
|
|
get signal() {
|
|
return this._internals.signal;
|
|
}
|
|
set signal(value) {
|
|
assert.object(value);
|
|
this._internals.signal = value;
|
|
}
|
|
/**
|
|
Ignore invalid cookies instead of throwing an error.
|
|
Only useful when the `cookieJar` option has been set. Not recommended.
|
|
|
|
@default false
|
|
*/
|
|
get ignoreInvalidCookies() {
|
|
return this._internals.ignoreInvalidCookies;
|
|
}
|
|
set ignoreInvalidCookies(value) {
|
|
assert.boolean(value);
|
|
this._internals.ignoreInvalidCookies = value;
|
|
}
|
|
/**
|
|
Query string that will be added to the request URL.
|
|
This will override the query string in `url`.
|
|
|
|
If you need to pass in an array, you can do it using a `URLSearchParams` instance.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
const searchParams = new URLSearchParams([['key', 'a'], ['key', 'b']]);
|
|
|
|
await got('https://example.com', {searchParams});
|
|
|
|
console.log(searchParams.toString());
|
|
//=> 'key=a&key=b'
|
|
```
|
|
*/
|
|
get searchParams() {
|
|
if (this._internals.url) {
|
|
return this._internals.url.searchParams;
|
|
}
|
|
if (this._internals.searchParams === undefined) {
|
|
this._internals.searchParams = new URLSearchParams();
|
|
}
|
|
return this._internals.searchParams;
|
|
}
|
|
set searchParams(value) {
|
|
assert.any([is.string, is.object, is.undefined], value);
|
|
const url = this._internals.url;
|
|
if (value === undefined) {
|
|
this._internals.searchParams = undefined;
|
|
if (url) {
|
|
url.search = '';
|
|
}
|
|
return;
|
|
}
|
|
const searchParameters = this.searchParams;
|
|
let updated;
|
|
if (is.string(value)) {
|
|
updated = new URLSearchParams(value);
|
|
}
|
|
else if (value instanceof URLSearchParams) {
|
|
updated = value;
|
|
}
|
|
else {
|
|
validateSearchParameters(value);
|
|
updated = new URLSearchParams();
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const key in value) {
|
|
const entry = value[key];
|
|
if (entry === null) {
|
|
updated.append(key, '');
|
|
}
|
|
else if (entry === undefined) {
|
|
searchParameters.delete(key);
|
|
}
|
|
else {
|
|
updated.append(key, entry);
|
|
}
|
|
}
|
|
}
|
|
if (this._merging) {
|
|
// These keys will be replaced
|
|
for (const key of updated.keys()) {
|
|
searchParameters.delete(key);
|
|
}
|
|
for (const [key, value] of updated) {
|
|
searchParameters.append(key, value);
|
|
}
|
|
}
|
|
else if (url) {
|
|
url.search = searchParameters.toString();
|
|
}
|
|
else {
|
|
this._internals.searchParams = searchParameters;
|
|
}
|
|
}
|
|
get searchParameters() {
|
|
throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
|
|
}
|
|
set searchParameters(_value) {
|
|
throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
|
|
}
|
|
get dnsLookup() {
|
|
return this._internals.dnsLookup;
|
|
}
|
|
set dnsLookup(value) {
|
|
assert.any([is.function_, is.undefined], value);
|
|
this._internals.dnsLookup = value;
|
|
}
|
|
/**
|
|
An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups.
|
|
Useful when making lots of requests to different *public* hostnames.
|
|
|
|
`CacheableLookup` uses `dns.resolver4(..)` and `dns.resolver6(...)` under the hood and fall backs to `dns.lookup(...)` when the first two fail, which may lead to additional delay.
|
|
|
|
__Note__: This should stay disabled when making requests to internal hostnames such as `localhost`, `database.local` etc.
|
|
|
|
@default false
|
|
*/
|
|
get dnsCache() {
|
|
return this._internals.dnsCache;
|
|
}
|
|
set dnsCache(value) {
|
|
assert.any([is.object, is.boolean, is.undefined], value);
|
|
if (value === true) {
|
|
this._internals.dnsCache = getGlobalDnsCache();
|
|
}
|
|
else if (value === false) {
|
|
this._internals.dnsCache = undefined;
|
|
}
|
|
else {
|
|
this._internals.dnsCache = value;
|
|
}
|
|
}
|
|
/**
|
|
User data. `context` is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
const instance = got.extend({
|
|
hooks: {
|
|
beforeRequest: [
|
|
options => {
|
|
if (!options.context || !options.context.token) {
|
|
throw new Error('Token required');
|
|
}
|
|
|
|
options.headers.token = options.context.token;
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
const context = {
|
|
token: 'secret'
|
|
};
|
|
|
|
const response = await instance('https://httpbin.org/headers', {context});
|
|
|
|
// Let's see the headers
|
|
console.log(response.body);
|
|
```
|
|
*/
|
|
get context() {
|
|
return this._internals.context;
|
|
}
|
|
set context(value) {
|
|
assert.object(value);
|
|
if (this._merging) {
|
|
Object.assign(this._internals.context, value);
|
|
}
|
|
else {
|
|
this._internals.context = { ...value };
|
|
}
|
|
}
|
|
/**
|
|
Hooks allow modifications during the request lifecycle.
|
|
Hook functions may be async and are run serially.
|
|
*/
|
|
get hooks() {
|
|
return this._internals.hooks;
|
|
}
|
|
set hooks(value) {
|
|
assert.object(value);
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const knownHookEvent in value) {
|
|
if (!(knownHookEvent in this._internals.hooks)) {
|
|
throw new Error(`Unexpected hook event: ${knownHookEvent}`);
|
|
}
|
|
const typedKnownHookEvent = knownHookEvent;
|
|
const hooks = value[typedKnownHookEvent];
|
|
assert.any([is.array, is.undefined], hooks);
|
|
if (hooks) {
|
|
for (const hook of hooks) {
|
|
assert.function_(hook);
|
|
}
|
|
}
|
|
if (this._merging) {
|
|
if (hooks) {
|
|
// @ts-expect-error FIXME
|
|
this._internals.hooks[typedKnownHookEvent].push(...hooks);
|
|
}
|
|
}
|
|
else {
|
|
if (!hooks) {
|
|
throw new Error(`Missing hook event: ${knownHookEvent}`);
|
|
}
|
|
// @ts-expect-error FIXME
|
|
this._internals.hooks[knownHookEvent] = [...hooks];
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
Defines if redirect responses should be followed automatically.
|
|
|
|
Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`.
|
|
This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). You can optionally turn on this behavior also for other redirect codes - see `methodRewriting`.
|
|
|
|
@default true
|
|
*/
|
|
get followRedirect() {
|
|
return this._internals.followRedirect;
|
|
}
|
|
set followRedirect(value) {
|
|
assert.boolean(value);
|
|
this._internals.followRedirect = value;
|
|
}
|
|
get followRedirects() {
|
|
throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
|
|
}
|
|
set followRedirects(_value) {
|
|
throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
|
|
}
|
|
/**
|
|
If exceeded, the request will be aborted and a `MaxRedirectsError` will be thrown.
|
|
|
|
@default 10
|
|
*/
|
|
get maxRedirects() {
|
|
return this._internals.maxRedirects;
|
|
}
|
|
set maxRedirects(value) {
|
|
assert.number(value);
|
|
this._internals.maxRedirects = value;
|
|
}
|
|
/**
|
|
A cache adapter instance for storing cached response data.
|
|
|
|
@default false
|
|
*/
|
|
get cache() {
|
|
return this._internals.cache;
|
|
}
|
|
set cache(value) {
|
|
assert.any([is.object, is.string, is.boolean, is.undefined], value);
|
|
if (value === true) {
|
|
this._internals.cache = globalCache;
|
|
}
|
|
else if (value === false) {
|
|
this._internals.cache = undefined;
|
|
}
|
|
else {
|
|
this._internals.cache = value;
|
|
}
|
|
}
|
|
/**
|
|
Determines if a `got.HTTPError` is thrown for unsuccessful responses.
|
|
|
|
If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing.
|
|
This may be useful if you are checking for resource availability and are expecting error responses.
|
|
|
|
@default true
|
|
*/
|
|
get throwHttpErrors() {
|
|
return this._internals.throwHttpErrors;
|
|
}
|
|
set throwHttpErrors(value) {
|
|
assert.boolean(value);
|
|
this._internals.throwHttpErrors = value;
|
|
}
|
|
get username() {
|
|
const url = this._internals.url;
|
|
const value = url ? url.username : this._internals.username;
|
|
return decodeURIComponent(value);
|
|
}
|
|
set username(value) {
|
|
assert.string(value);
|
|
const url = this._internals.url;
|
|
const fixedValue = encodeURIComponent(value);
|
|
if (url) {
|
|
url.username = fixedValue;
|
|
}
|
|
else {
|
|
this._internals.username = fixedValue;
|
|
}
|
|
}
|
|
get password() {
|
|
const url = this._internals.url;
|
|
const value = url ? url.password : this._internals.password;
|
|
return decodeURIComponent(value);
|
|
}
|
|
set password(value) {
|
|
assert.string(value);
|
|
const url = this._internals.url;
|
|
const fixedValue = encodeURIComponent(value);
|
|
if (url) {
|
|
url.password = fixedValue;
|
|
}
|
|
else {
|
|
this._internals.password = fixedValue;
|
|
}
|
|
}
|
|
/**
|
|
If set to `true`, Got will additionally accept HTTP2 requests.
|
|
|
|
It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol.
|
|
|
|
__Note__: This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy.
|
|
|
|
__Note__: Overriding `options.request` will disable HTTP2 support.
|
|
|
|
@default false
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
const {headers} = await got('https://nghttp2.org/httpbin/anything', {http2: true});
|
|
|
|
console.log(headers.via);
|
|
//=> '2 nghttpx'
|
|
```
|
|
*/
|
|
get http2() {
|
|
return this._internals.http2;
|
|
}
|
|
set http2(value) {
|
|
assert.boolean(value);
|
|
this._internals.http2 = value;
|
|
}
|
|
/**
|
|
Set this to `true` to allow sending body for the `GET` method.
|
|
However, the [HTTP/2 specification](https://tools.ietf.org/html/rfc7540#section-8.1.3) says that `An HTTP GET request includes request header fields and no payload body`, therefore when using the HTTP/2 protocol this option will have no effect.
|
|
This option is only meant to interact with non-compliant servers when you have no other choice.
|
|
|
|
__Note__: The [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) doesn't specify any particular behavior for the GET method having a payload, therefore __it's considered an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern)__.
|
|
|
|
@default false
|
|
*/
|
|
get allowGetBody() {
|
|
return this._internals.allowGetBody;
|
|
}
|
|
set allowGetBody(value) {
|
|
assert.boolean(value);
|
|
this._internals.allowGetBody = value;
|
|
}
|
|
/**
|
|
Request headers.
|
|
|
|
Existing headers will be overwritten. Headers set to `undefined` will be omitted.
|
|
|
|
@default {}
|
|
*/
|
|
get headers() {
|
|
return this._internals.headers;
|
|
}
|
|
set headers(value) {
|
|
assert.plainObject(value);
|
|
if (this._merging) {
|
|
Object.assign(this._internals.headers, lowercaseKeys(value));
|
|
}
|
|
else {
|
|
this._internals.headers = lowercaseKeys(value);
|
|
}
|
|
}
|
|
/**
|
|
Specifies if the HTTP request method should be [rewritten as `GET`](https://tools.ietf.org/html/rfc7231#section-6.4) on redirects.
|
|
|
|
As the [specification](https://tools.ietf.org/html/rfc7231#section-6.4) prefers to rewrite the HTTP method only on `303` responses, this is Got's default behavior.
|
|
Setting `methodRewriting` to `true` will also rewrite `301` and `302` responses, as allowed by the spec. This is the behavior followed by `curl` and browsers.
|
|
|
|
__Note__: Got never performs method rewriting on `307` and `308` responses, as this is [explicitly prohibited by the specification](https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7).
|
|
|
|
@default false
|
|
*/
|
|
get methodRewriting() {
|
|
return this._internals.methodRewriting;
|
|
}
|
|
set methodRewriting(value) {
|
|
assert.boolean(value);
|
|
this._internals.methodRewriting = value;
|
|
}
|
|
/**
|
|
Indicates which DNS record family to use.
|
|
|
|
Values:
|
|
- `undefined`: IPv4 (if present) or IPv6
|
|
- `4`: Only IPv4
|
|
- `6`: Only IPv6
|
|
|
|
@default undefined
|
|
*/
|
|
get dnsLookupIpVersion() {
|
|
return this._internals.dnsLookupIpVersion;
|
|
}
|
|
set dnsLookupIpVersion(value) {
|
|
if (value !== undefined && value !== 4 && value !== 6) {
|
|
throw new TypeError(`Invalid DNS lookup IP version: ${value}`);
|
|
}
|
|
this._internals.dnsLookupIpVersion = value;
|
|
}
|
|
/**
|
|
A function used to parse JSON responses.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
import Bourne from '@hapi/bourne';
|
|
|
|
const parsed = await got('https://example.com', {
|
|
parseJson: text => Bourne.parse(text)
|
|
}).json();
|
|
|
|
console.log(parsed);
|
|
```
|
|
*/
|
|
get parseJson() {
|
|
return this._internals.parseJson;
|
|
}
|
|
set parseJson(value) {
|
|
assert.function_(value);
|
|
this._internals.parseJson = value;
|
|
}
|
|
/**
|
|
A function used to stringify the body of JSON requests.
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
await got.post('https://example.com', {
|
|
stringifyJson: object => JSON.stringify(object, (key, value) => {
|
|
if (key.startsWith('_')) {
|
|
return;
|
|
}
|
|
|
|
return value;
|
|
}),
|
|
json: {
|
|
some: 'payload',
|
|
_ignoreMe: 1234
|
|
}
|
|
});
|
|
```
|
|
|
|
@example
|
|
```
|
|
import got from 'got';
|
|
|
|
await got.post('https://example.com', {
|
|
stringifyJson: object => JSON.stringify(object, (key, value) => {
|
|
if (typeof value === 'number') {
|
|
return value.toString();
|
|
}
|
|
|
|
return value;
|
|
}),
|
|
json: {
|
|
some: 'payload',
|
|
number: 1
|
|
}
|
|
});
|
|
```
|
|
*/
|
|
get stringifyJson() {
|
|
return this._internals.stringifyJson;
|
|
}
|
|
set stringifyJson(value) {
|
|
assert.function_(value);
|
|
this._internals.stringifyJson = value;
|
|
}
|
|
/**
|
|
An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes.
|
|
|
|
Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1).
|
|
|
|
The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value.
|
|
The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry).
|
|
|
|
By default, it retries *only* on the specified methods, status codes, and on these network errors:
|
|
|
|
- `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached.
|
|
- `ECONNRESET`: Connection was forcibly closed by a peer.
|
|
- `EADDRINUSE`: Could not bind to any free port.
|
|
- `ECONNREFUSED`: Connection was refused by the server.
|
|
- `EPIPE`: The remote side of the stream being written has been closed.
|
|
- `ENOTFOUND`: Couldn't resolve the hostname to an IP address.
|
|
- `ENETUNREACH`: No internet connection.
|
|
- `EAI_AGAIN`: DNS lookup timed out.
|
|
|
|
__Note__: If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`.
|
|
__Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.
|
|
*/
|
|
get retry() {
|
|
return this._internals.retry;
|
|
}
|
|
set retry(value) {
|
|
assert.plainObject(value);
|
|
assert.any([is.function_, is.undefined], value.calculateDelay);
|
|
assert.any([is.number, is.undefined], value.maxRetryAfter);
|
|
assert.any([is.number, is.undefined], value.limit);
|
|
assert.any([is.array, is.undefined], value.methods);
|
|
assert.any([is.array, is.undefined], value.statusCodes);
|
|
assert.any([is.array, is.undefined], value.errorCodes);
|
|
assert.any([is.number, is.undefined], value.noise);
|
|
if (value.noise && Math.abs(value.noise) > 100) {
|
|
throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
|
|
}
|
|
for (const key in value) {
|
|
if (!(key in this._internals.retry)) {
|
|
throw new Error(`Unexpected retry option: ${key}`);
|
|
}
|
|
}
|
|
if (this._merging) {
|
|
Object.assign(this._internals.retry, value);
|
|
}
|
|
else {
|
|
this._internals.retry = { ...value };
|
|
}
|
|
const { retry } = this._internals;
|
|
retry.methods = [...new Set(retry.methods.map(method => method.toUpperCase()))];
|
|
retry.statusCodes = [...new Set(retry.statusCodes)];
|
|
retry.errorCodes = [...new Set(retry.errorCodes)];
|
|
}
|
|
/**
|
|
From `http.RequestOptions`.
|
|
|
|
The IP address used to send the request from.
|
|
*/
|
|
get localAddress() {
|
|
return this._internals.localAddress;
|
|
}
|
|
set localAddress(value) {
|
|
assert.any([is.string, is.undefined], value);
|
|
this._internals.localAddress = value;
|
|
}
|
|
/**
|
|
The HTTP method used to make the request.
|
|
|
|
@default 'GET'
|
|
*/
|
|
get method() {
|
|
return this._internals.method;
|
|
}
|
|
set method(value) {
|
|
assert.string(value);
|
|
this._internals.method = value.toUpperCase();
|
|
}
|
|
get createConnection() {
|
|
return this._internals.createConnection;
|
|
}
|
|
set createConnection(value) {
|
|
assert.any([is.function_, is.undefined], value);
|
|
this._internals.createConnection = value;
|
|
}
|
|
/**
|
|
From `http-cache-semantics`
|
|
|
|
@default {}
|
|
*/
|
|
get cacheOptions() {
|
|
return this._internals.cacheOptions;
|
|
}
|
|
set cacheOptions(value) {
|
|
assert.plainObject(value);
|
|
assert.any([is.boolean, is.undefined], value.shared);
|
|
assert.any([is.number, is.undefined], value.cacheHeuristic);
|
|
assert.any([is.number, is.undefined], value.immutableMinTimeToLive);
|
|
assert.any([is.boolean, is.undefined], value.ignoreCargoCult);
|
|
for (const key in value) {
|
|
if (!(key in this._internals.cacheOptions)) {
|
|
throw new Error(`Cache option \`${key}\` does not exist`);
|
|
}
|
|
}
|
|
if (this._merging) {
|
|
Object.assign(this._internals.cacheOptions, value);
|
|
}
|
|
else {
|
|
this._internals.cacheOptions = { ...value };
|
|
}
|
|
}
|
|
/**
|
|
Options for the advanced HTTPS API.
|
|
*/
|
|
get https() {
|
|
return this._internals.https;
|
|
}
|
|
set https(value) {
|
|
assert.plainObject(value);
|
|
assert.any([is.boolean, is.undefined], value.rejectUnauthorized);
|
|
assert.any([is.function_, is.undefined], value.checkServerIdentity);
|
|
assert.any([is.string, is.object, is.array, is.undefined], value.certificateAuthority);
|
|
assert.any([is.string, is.object, is.array, is.undefined], value.key);
|
|
assert.any([is.string, is.object, is.array, is.undefined], value.certificate);
|
|
assert.any([is.string, is.undefined], value.passphrase);
|
|
assert.any([is.string, is.buffer, is.array, is.undefined], value.pfx);
|
|
assert.any([is.array, is.undefined], value.alpnProtocols);
|
|
assert.any([is.string, is.undefined], value.ciphers);
|
|
assert.any([is.string, is.buffer, is.undefined], value.dhparam);
|
|
assert.any([is.string, is.undefined], value.signatureAlgorithms);
|
|
assert.any([is.string, is.undefined], value.minVersion);
|
|
assert.any([is.string, is.undefined], value.maxVersion);
|
|
assert.any([is.boolean, is.undefined], value.honorCipherOrder);
|
|
assert.any([is.number, is.undefined], value.tlsSessionLifetime);
|
|
assert.any([is.string, is.undefined], value.ecdhCurve);
|
|
assert.any([is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
|
|
for (const key in value) {
|
|
if (!(key in this._internals.https)) {
|
|
throw new Error(`HTTPS option \`${key}\` does not exist`);
|
|
}
|
|
}
|
|
if (this._merging) {
|
|
Object.assign(this._internals.https, value);
|
|
}
|
|
else {
|
|
this._internals.https = { ...value };
|
|
}
|
|
}
|
|
/**
|
|
[Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data.
|
|
|
|
To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set `responseType` to `buffer` instead.
|
|
Don't set this option to `null`.
|
|
|
|
__Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`.
|
|
|
|
@default 'utf-8'
|
|
*/
|
|
get encoding() {
|
|
return this._internals.encoding;
|
|
}
|
|
set encoding(value) {
|
|
if (value === null) {
|
|
throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
|
|
}
|
|
assert.any([is.string, is.undefined], value);
|
|
this._internals.encoding = value;
|
|
}
|
|
/**
|
|
When set to `true` the promise will return the Response body instead of the Response object.
|
|
|
|
@default false
|
|
*/
|
|
get resolveBodyOnly() {
|
|
return this._internals.resolveBodyOnly;
|
|
}
|
|
set resolveBodyOnly(value) {
|
|
assert.boolean(value);
|
|
this._internals.resolveBodyOnly = value;
|
|
}
|
|
/**
|
|
Returns a `Stream` instead of a `Promise`.
|
|
This is equivalent to calling `got.stream(url, options?)`.
|
|
|
|
@default false
|
|
*/
|
|
get isStream() {
|
|
return this._internals.isStream;
|
|
}
|
|
set isStream(value) {
|
|
assert.boolean(value);
|
|
this._internals.isStream = value;
|
|
}
|
|
/**
|
|
The parsing method.
|
|
|
|
The promise also has `.text()`, `.json()` and `.buffer()` methods which return another Got promise for the parsed body.
|
|
|
|
It's like setting the options to `{responseType: 'json', resolveBodyOnly: true}` but without affecting the main Got promise.
|
|
|
|
__Note__: When using streams, this option is ignored.
|
|
|
|
@example
|
|
```
|
|
const responsePromise = got(url);
|
|
const bufferPromise = responsePromise.buffer();
|
|
const jsonPromise = responsePromise.json();
|
|
|
|
const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]);
|
|
// `response` is an instance of Got Response
|
|
// `buffer` is an instance of Buffer
|
|
// `json` is an object
|
|
```
|
|
|
|
@example
|
|
```
|
|
// This
|
|
const body = await got(url).json();
|
|
|
|
// is semantically the same as this
|
|
const body = await got(url, {responseType: 'json', resolveBodyOnly: true});
|
|
```
|
|
*/
|
|
get responseType() {
|
|
return this._internals.responseType;
|
|
}
|
|
set responseType(value) {
|
|
if (value === undefined) {
|
|
this._internals.responseType = 'text';
|
|
return;
|
|
}
|
|
if (value !== 'text' && value !== 'buffer' && value !== 'json') {
|
|
throw new Error(`Invalid \`responseType\` option: ${value}`);
|
|
}
|
|
this._internals.responseType = value;
|
|
}
|
|
get pagination() {
|
|
return this._internals.pagination;
|
|
}
|
|
set pagination(value) {
|
|
assert.object(value);
|
|
if (this._merging) {
|
|
Object.assign(this._internals.pagination, value);
|
|
}
|
|
else {
|
|
this._internals.pagination = value;
|
|
}
|
|
}
|
|
get auth() {
|
|
throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
|
|
}
|
|
set auth(_value) {
|
|
throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
|
|
}
|
|
get setHost() {
|
|
return this._internals.setHost;
|
|
}
|
|
set setHost(value) {
|
|
assert.boolean(value);
|
|
this._internals.setHost = value;
|
|
}
|
|
get maxHeaderSize() {
|
|
return this._internals.maxHeaderSize;
|
|
}
|
|
set maxHeaderSize(value) {
|
|
assert.any([is.number, is.undefined], value);
|
|
this._internals.maxHeaderSize = value;
|
|
}
|
|
get enableUnixSockets() {
|
|
return this._internals.enableUnixSockets;
|
|
}
|
|
set enableUnixSockets(value) {
|
|
assert.boolean(value);
|
|
this._internals.enableUnixSockets = value;
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
toJSON() {
|
|
return { ...this._internals };
|
|
}
|
|
[Symbol.for('nodejs.util.inspect.custom')](_depth, options) {
|
|
return inspect(this._internals, options);
|
|
}
|
|
createNativeRequestOptions() {
|
|
const internals = this._internals;
|
|
const url = internals.url;
|
|
let agent;
|
|
if (url.protocol === 'https:') {
|
|
agent = internals.http2 ? internals.agent : internals.agent.https;
|
|
}
|
|
else {
|
|
agent = internals.agent.http;
|
|
}
|
|
const { https } = internals;
|
|
let { pfx } = https;
|
|
if (is.array(pfx) && is.plainObject(pfx[0])) {
|
|
pfx = pfx.map(object => ({
|
|
buf: object.buffer,
|
|
passphrase: object.passphrase,
|
|
}));
|
|
}
|
|
return {
|
|
...internals.cacheOptions,
|
|
...this._unixOptions,
|
|
// HTTPS options
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
ALPNProtocols: https.alpnProtocols,
|
|
ca: https.certificateAuthority,
|
|
cert: https.certificate,
|
|
key: https.key,
|
|
passphrase: https.passphrase,
|
|
pfx: https.pfx,
|
|
rejectUnauthorized: https.rejectUnauthorized,
|
|
checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity,
|
|
ciphers: https.ciphers,
|
|
honorCipherOrder: https.honorCipherOrder,
|
|
minVersion: https.minVersion,
|
|
maxVersion: https.maxVersion,
|
|
sigalgs: https.signatureAlgorithms,
|
|
sessionTimeout: https.tlsSessionLifetime,
|
|
dhparam: https.dhparam,
|
|
ecdhCurve: https.ecdhCurve,
|
|
crl: https.certificateRevocationLists,
|
|
// HTTP options
|
|
lookup: internals.dnsLookup ?? internals.dnsCache?.lookup,
|
|
family: internals.dnsLookupIpVersion,
|
|
agent,
|
|
setHost: internals.setHost,
|
|
method: internals.method,
|
|
maxHeaderSize: internals.maxHeaderSize,
|
|
localAddress: internals.localAddress,
|
|
headers: internals.headers,
|
|
createConnection: internals.createConnection,
|
|
timeout: internals.http2 ? getHttp2TimeoutOption(internals) : undefined,
|
|
// HTTP/2 options
|
|
h2session: internals.h2session,
|
|
};
|
|
}
|
|
getRequestFunction() {
|
|
const url = this._internals.url;
|
|
const { request } = this._internals;
|
|
if (!request && url) {
|
|
return this.getFallbackRequestFunction();
|
|
}
|
|
return request;
|
|
}
|
|
getFallbackRequestFunction() {
|
|
const url = this._internals.url;
|
|
if (!url) {
|
|
return;
|
|
}
|
|
if (url.protocol === 'https:') {
|
|
if (this._internals.http2) {
|
|
if (major < 15 || (major === 15 && minor < 10)) {
|
|
const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above');
|
|
error.code = 'EUNSUPPORTED';
|
|
throw error;
|
|
}
|
|
return http2wrapper.auto;
|
|
}
|
|
return https.request;
|
|
}
|
|
return http.request;
|
|
}
|
|
freeze() {
|
|
const options = this._internals;
|
|
Object.freeze(options);
|
|
Object.freeze(options.hooks);
|
|
Object.freeze(options.hooks.afterResponse);
|
|
Object.freeze(options.hooks.beforeError);
|
|
Object.freeze(options.hooks.beforeRedirect);
|
|
Object.freeze(options.hooks.beforeRequest);
|
|
Object.freeze(options.hooks.beforeRetry);
|
|
Object.freeze(options.hooks.init);
|
|
Object.freeze(options.https);
|
|
Object.freeze(options.cacheOptions);
|
|
Object.freeze(options.agent);
|
|
Object.freeze(options.headers);
|
|
Object.freeze(options.timeout);
|
|
Object.freeze(options.retry);
|
|
Object.freeze(options.retry.errorCodes);
|
|
Object.freeze(options.retry.methods);
|
|
Object.freeze(options.retry.statusCodes);
|
|
}
|
|
}
|