UNPKG

fetch-h2

Version:

HTTP/1+2 Fetch API client for Node.js

209 lines 8.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeRedirectionMethodError = exports.makeRedirectionError = exports.makeIllegalRedirectError = exports.makeTimeoutError = exports.makeAbortedError = exports.make100Error = exports.handleSignalAndTimeout = exports.setupFetch = void 0; const http2_1 = require("http2"); const url_1 = require("url"); const already_1 = require("already"); const body_1 = require("./body"); const core_1 = require("./core"); const headers_1 = require("./headers"); const utils_1 = require("./utils"); const { // Required for a request HTTP2_HEADER_METHOD, HTTP2_HEADER_SCHEME, HTTP2_HEADER_PATH, HTTP2_HEADER_AUTHORITY, // Methods HTTP2_METHOD_GET, HTTP2_METHOD_HEAD, // Requests HTTP2_HEADER_USER_AGENT, HTTP2_HEADER_ACCEPT, HTTP2_HEADER_COOKIE, HTTP2_HEADER_CONTENT_TYPE, HTTP2_HEADER_CONTENT_LENGTH, HTTP2_HEADER_ACCEPT_ENCODING, } = http2_1.constants; function ensureNotCircularRedirection(redirections) { const urls = [...redirections]; const last = urls.pop(); for (let i = 0; i < urls.length; ++i) if (urls[i] === last) { const err = new Error("Redirection loop detected"); err.urls = urls.slice(i); throw err; } } const makeDefaultEncodings = (mul = 1) => (0, utils_1.hasBuiltinBrotli)() ? [ { name: "br", score: 1.0 * mul }, { name: "gzip", score: 0.8 * mul }, { name: "deflate", score: 0.5 * mul }, ] : [ { name: "gzip", score: 1.0 * mul }, { name: "deflate", score: 0.5 * mul }, ]; const defaultEncodings = makeDefaultEncodings(); const fallbackEncodings = makeDefaultEncodings(0.8); const stringifyEncoding = (acceptEncoding) => `${acceptEncoding.name};q=${acceptEncoding.score}`; const stringifyEncodings = (accepts) => accepts .map(acceptEncoding => stringifyEncoding(acceptEncoding)) .join(", "); function getEncodings(contentDecoders) { if (contentDecoders.length === 0) return stringifyEncodings(defaultEncodings); const makeScore = (index) => 1 - (index / (contentDecoders.length)) * 0.2; return stringifyEncodings([ ...contentDecoders.map(({ name }, index) => ({ name, score: makeScore(index) })), ...fallbackEncodings, ]); } async function setupFetch(session, request, init = {}, extra) { const { redirected } = extra; ensureNotCircularRedirection(redirected); const { url, method, redirect, integrity } = request; const { signal, onTrailers } = init; const { origin, protocol, host, pathname, search, hash, } = new url_1.URL(url); const path = pathname + search + hash; const endStream = method === HTTP2_METHOD_GET || method === HTTP2_METHOD_HEAD; const headers = new headers_1.Headers(request.headers); const cookies = (await session.cookieJar.getCookies(url)) .map(cookie => cookie.cookieString()); const contentDecoders = session.contentDecoders(); const acceptEncoding = getEncodings(contentDecoders); if (headers.has(HTTP2_HEADER_COOKIE)) cookies.push(...(0, utils_1.arrayify)(headers.get(HTTP2_HEADER_COOKIE))); if (!headers.has("host")) headers.set("host", host); const headersToSend = { // Set required headers ...(session.protocol === "http1" ? {} : { [HTTP2_HEADER_METHOD]: method, [HTTP2_HEADER_SCHEME]: protocol.replace(/:.*/, ""), [HTTP2_HEADER_PATH]: path, }), // Set default headers [HTTP2_HEADER_ACCEPT]: session.accept(), [HTTP2_HEADER_USER_AGENT]: session.userAgent(), [HTTP2_HEADER_ACCEPT_ENCODING]: acceptEncoding, }; if (cookies.length > 0) headersToSend[HTTP2_HEADER_COOKIE] = cookies.join("; "); for (const [key, val] of headers.entries()) { if (key === "host" && session.protocol === "http2") // Convert to :authority like curl does: // https://github.com/grantila/fetch-h2/issues/9 headersToSend[HTTP2_HEADER_AUTHORITY] = val; else if (key !== HTTP2_HEADER_COOKIE) headersToSend[key] = val; } const inspector = new body_1.BodyInspector(request); if (!endStream && inspector.length != null && !request.headers.has(HTTP2_HEADER_CONTENT_LENGTH)) headersToSend[HTTP2_HEADER_CONTENT_LENGTH] = "" + inspector.length; if (!endStream && !request.headers.has("content-type") && inspector.mime) headersToSend[HTTP2_HEADER_CONTENT_TYPE] = inspector.mime; function timeoutError() { return new core_1.TimeoutError(`${method} ${url} timed out after ${init.timeout} ms`); } const timeoutAt = extra.timeoutAt || (("timeout" in init && typeof init.timeout === "number") // Setting the timeoutAt here at first time allows async cookie // jar to not take part of timeout for at least the first request // (in a potential redirect chain) ? Date.now() + init.timeout : void 0); function setupTimeout() { if (!timeoutAt) return null; const now = Date.now(); if (now >= timeoutAt) throw timeoutError(); let timerId; return { clear: () => { if (timerId) clearTimeout(timerId); }, promise: new Promise((_resolve, reject) => { timerId = setTimeout(() => { timerId = null; reject(timeoutError()); }, timeoutAt - now); }), }; } const timeoutInfo = setupTimeout(); function abortError() { return new core_1.AbortError(`${method} ${url} aborted`); } if (signal && signal.aborted) throw abortError(); let abortHandler; const signalPromise = signal ? new Promise((_resolve, reject) => { signal.once("abort", abortHandler = () => { reject(abortError()); }); }) : null; function cleanup() { var _a, _b; (_a = timeoutInfo === null || timeoutInfo === void 0 ? void 0 : timeoutInfo.clear) === null || _a === void 0 ? void 0 : _a.call(timeoutInfo); (_b = timeoutInfo === null || timeoutInfo === void 0 ? void 0 : timeoutInfo.promise) === null || _b === void 0 ? void 0 : _b.catch(_err => { }); if (signal && abortHandler) signal.removeListener("abort", abortHandler); } return { cleanup, contentDecoders, endStream, headersToSend, integrity, method, onTrailers, origin, redirect, redirected, request, signal, signalPromise, timeoutAt, timeoutInfo, url, }; } exports.setupFetch = setupFetch; function handleSignalAndTimeout(signalPromise, timeoutInfo, cleanup, fetcher, onError) { return Promise.race([ signalPromise, (timeoutInfo && timeoutInfo.promise), fetcher().catch((0, already_1.rethrow)(onError)), ] .filter(promise => promise)) .finally(cleanup); } exports.handleSignalAndTimeout = handleSignalAndTimeout; function make100Error() { return new Error("Request failed with 100 continue. " + "This can't happen unless a server failure"); } exports.make100Error = make100Error; function makeAbortedError() { return new core_1.AbortError("Request aborted"); } exports.makeAbortedError = makeAbortedError; function makeTimeoutError() { return new core_1.TimeoutError("Request timed out"); } exports.makeTimeoutError = makeTimeoutError; function makeIllegalRedirectError() { return new Error("Server responded illegally with a " + "redirect code but missing 'location' header"); } exports.makeIllegalRedirectError = makeIllegalRedirectError; function makeRedirectionError(location) { return new Error(`URL got redirected to ${location}`); } exports.makeRedirectionError = makeRedirectionError; function makeRedirectionMethodError(location, method) { return new Error(`URL got redirected to ${location}, which ` + `'fetch-h2' doesn't support for ${method}`); } exports.makeRedirectionMethodError = makeRedirectionMethodError; //# sourceMappingURL=fetch-common.js.map