UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

346 lines • 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpBase = void 0; exports.applyDefaultHeaders = applyDefaultHeaders; const tslib_1 = require("tslib"); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const deepmerge_1 = tslib_1.__importDefault(require("deepmerge")); const zod_1 = require("zod"); const global_1 = require("../../config/global"); const error_messages_1 = require("../../constants/error-messages"); const expose_cjs_1 = require("../../expose.cjs"); const logger_1 = require("../../logger"); const external_host_error_1 = require("../../types/errors/external-host-error"); const memCache = tslib_1.__importStar(require("../cache/memory")); const hash_1 = require("../hash"); const result_1 = require("../result"); const stats_1 = require("../stats"); const url_1 = require("../url"); const yaml_1 = require("../yaml"); const auth_1 = require("./auth"); const got_1 = require("./got"); const host_rules_1 = require("./host-rules"); const queue_1 = require("./queue"); const retry_after_1 = require("./retry-after"); const throttle_1 = require("./throttle"); const util_1 = require("./util"); function applyDefaultHeaders(options) { const renovateVersion = expose_cjs_1.pkg.version; options.headers = { ...options.headers, 'user-agent': global_1.GlobalConfig.get('userAgent') ?? `RenovateBot/${renovateVersion} (https://github.com/renovatebot/renovate)`, }; } class HttpBase { hostType; options; get baseUrl() { return undefined; } constructor(hostType, options = {}) { this.hostType = hostType; const retryLimit = process.env.NODE_ENV === 'test' ? 0 : 2; this.options = (0, deepmerge_1.default)(options, { method: 'get', context: { hostType }, retry: { calculateDelay: (retryObject) => this.calculateRetryDelay(retryObject), limit: retryLimit, maxRetryAfter: 0, // Don't rely on `got` retry-after handling, just let it fail and then we'll handle it }, }, { isMergeableObject: is_1.default.plainObject }); } async request(requestUrl, httpOptions) { const resolvedUrl = this.resolveUrl(requestUrl, httpOptions); const url = resolvedUrl.toString(); this.processOptions(resolvedUrl, httpOptions); let options = (0, deepmerge_1.default)({ ...this.options, hostType: this.hostType, }, httpOptions, { isMergeableObject: is_1.default.plainObject }); logger_1.logger.trace(`HTTP request: ${options.method.toUpperCase()} ${url}`); options.hooks = { beforeRedirect: [auth_1.removeAuthorization], }; applyDefaultHeaders(options); if (is_1.default.undefined(options.readOnly) && ['head', 'get'].includes(options.method)) { options.readOnly = true; } const hostRule = (0, host_rules_1.findMatchingRule)(url, options); options = (0, host_rules_1.applyHostRule)(url, options, hostRule); if (options.enabled === false) { logger_1.logger.debug(`Host is disabled - rejecting request. HostUrl: ${url}`); throw new Error(error_messages_1.HOST_DISABLED); } options = (0, auth_1.applyAuthorization)(options); options.timeout ??= 60000; const { cacheProvider } = options; const memCacheKey = !cacheProvider && options.memCache !== false && (options.method === 'get' || options.method === 'head') ? (0, hash_1.hash)(`got-${JSON.stringify({ url, headers: options.headers, method: options.method, })}`) : null; const cachedResponse = await cacheProvider?.bypassServer(url); if (cachedResponse) { return cachedResponse; } let resPromise = null; // Cache GET requests unless memCache=false if (memCacheKey) { resPromise = memCache.get(memCacheKey); /* v8 ignore start: temporary code */ if (resPromise && !cacheProvider) { stats_1.ObsoleteCacheHitLogger.write(url); } /* v8 ignore stop: temporary code */ } if (!resPromise) { if (cacheProvider) { await cacheProvider.setCacheHeaders(url, options); } const startTime = Date.now(); const httpTask = () => { const queueMs = Date.now() - startTime; return (0, got_1.fetch)(url, options, { queueMs }); }; const throttle = (0, throttle_1.getThrottle)(url); const throttledTask = throttle ? () => throttle.add(httpTask) : httpTask; const queue = (0, queue_1.getQueue)(url); const queuedTask = queue ? () => queue.add(throttledTask) : throttledTask; const { maxRetryAfter = 60 } = hostRule; resPromise = (0, retry_after_1.wrapWithRetry)(queuedTask, url, retry_after_1.getRetryAfter, maxRetryAfter); if (memCacheKey) { memCache.set(memCacheKey, resPromise); } } try { const res = await resPromise; const deepCopyNeeded = !!memCacheKey && res.statusCode !== 304; const resCopy = (0, util_1.copyResponse)(res, deepCopyNeeded); resCopy.authorization = !!options?.headers?.authorization; if (cacheProvider) { return await cacheProvider.wrapServerResponse(url, resCopy); } return resCopy; } catch (err) { const { abortOnError, abortIgnoreStatusCodes } = options; if (abortOnError && !abortIgnoreStatusCodes?.includes(err.statusCode)) { throw new external_host_error_1.ExternalHostError(err); } const staleResponse = await cacheProvider?.bypassServer(url, true); if (staleResponse) { logger_1.logger.debug({ err }, `Request error: returning stale cache instead for ${url}`); return staleResponse; } this.handleError(requestUrl, httpOptions, err); } } processOptions(_url, _options) { // noop } handleError(_url, _httpOptions, err) { throw err; } resolveUrl(requestUrl, options) { let url = requestUrl; if (url instanceof URL) { // already a aboslute URL return url; } const baseUrl = options?.baseUrl ?? this.baseUrl; if (baseUrl) { url = (0, url_1.resolveBaseUrl)(baseUrl, url); } const parsedUrl = (0, url_1.parseUrl)(url); if (!parsedUrl || !(0, url_1.isHttpUrl)(parsedUrl)) { logger_1.logger.error({ url: requestUrl, baseUrl, resolvedUrl: url }, 'Request Error: cannot parse url'); throw new Error('Invalid URL'); } return parsedUrl; } calculateRetryDelay({ computedValue }) { return computedValue; } get(url, options = {}) { return this.request(url, options); } head(url, options = {}) { // to complex to validate return this.request(url, { ...options, responseType: 'text', method: 'head', }); } getText(url, options = {}) { return this.request(url, { ...options, responseType: 'text' }); } getBuffer(url, options = {}) { return this.request(url, { ...options, responseType: 'buffer' }); } requestJsonUnsafe(method, { url, httpOptions: requestOptions }) { const { body: json, ...httpOptions } = { ...requestOptions }; const opts = { ...httpOptions, method, }; // signal that we expect a json response opts.headers = { accept: 'application/json', ...opts.headers, }; if (json) { opts.json = json; } return this.request(url, { ...opts, responseType: 'json' }); } async requestJson(method, options) { const res = await this.requestJsonUnsafe(method, options); if (options.schema) { res.body = await options.schema.parseAsync(res.body); } return res; } resolveArgs(arg1, arg2, arg3) { const res = { url: arg1 }; if (arg2 instanceof zod_1.ZodType) { res.schema = arg2; } else if (arg2) { res.httpOptions = arg2; } if (arg3) { res.schema = arg3; } return res; } async getPlain(url, options) { const opt = options ?? {}; return await this.getText(url, { headers: { Accept: 'text/plain', }, ...opt, }); } /** * @deprecated use `getYaml` instead */ async getYamlUnchecked(url, options) { const res = await this.getText(url, options); const body = (0, yaml_1.parseSingleYaml)(res.body); return { ...res, body }; } async getYaml(arg1, arg2, arg3) { const url = arg1; let schema; let httpOptions; if (arg3) { schema = arg3; httpOptions = arg2; } else { schema = arg2; } const opts = { ...httpOptions, method: 'get', }; const res = await this.getText(url, opts); const body = await schema.parseAsync((0, yaml_1.parseSingleYaml)(res.body)); return { ...res, body }; } getYamlSafe(arg1, arg2, arg3) { const url = arg1; let schema; let httpOptions; if (arg3) { schema = arg3; httpOptions = arg2; } else { schema = arg2; } let res; if (httpOptions) { res = result_1.Result.wrap(this.getYaml(url, httpOptions, schema)); } else { res = result_1.Result.wrap(this.getYaml(url, schema)); } return res.transform((response) => result_1.Result.ok(response.body)); } /** * Request JSON and return the response without any validation. * * The usage of this method is discouraged, please use `getJson` instead. * * If you're new to Zod schema validation library: * - consult the [documentation of Zod library](https://github.com/colinhacks/zod?tab=readme-ov-file#basic-usage) * - search the Renovate codebase for 'zod' module usage * - take a look at the `schema-utils.ts` file for Renovate-specific schemas and utilities */ getJsonUnchecked(url, options) { return this.requestJson('get', { url, httpOptions: options }); } getJson(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return this.requestJson('get', args); } getJsonSafe(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return result_1.Result.wrap(this.requestJson('get', args)).transform((response) => result_1.Result.ok(response.body)); } /** * @deprecated use `head` instead */ headJson(url, httpOptions) { return this.requestJson('head', { url, httpOptions }); } postJson(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return this.requestJson('post', args); } putJson(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return this.requestJson('put', args); } patchJson(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return this.requestJson('patch', args); } deleteJson(arg1, arg2, arg3) { const args = this.resolveArgs(arg1, arg2, arg3); return this.requestJson('delete', args); } stream(url, options) { let combinedOptions = { ...this.options, hostType: this.hostType, ...options, method: 'get', }; const resolvedUrl = this.resolveUrl(url, options).toString(); applyDefaultHeaders(combinedOptions); if (is_1.default.undefined(combinedOptions.readOnly) && ['head', 'get'].includes(combinedOptions.method)) { combinedOptions.readOnly = true; } const hostRule = (0, host_rules_1.findMatchingRule)(url, combinedOptions); combinedOptions = (0, host_rules_1.applyHostRule)(resolvedUrl, combinedOptions, hostRule); if (combinedOptions.enabled === false) { throw new Error(error_messages_1.HOST_DISABLED); } combinedOptions = (0, auth_1.applyAuthorization)(combinedOptions); return (0, got_1.stream)(resolvedUrl, combinedOptions); } } exports.HttpBase = HttpBase; //# sourceMappingURL=http.js.map