Skip to content

Commit

Permalink
More conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Aug 6, 2021
1 parent 593793f commit a56e285
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 71 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"devDependencies": {
"@apify/eslint-config": "^0.1.3",
"@types/node": "^16.4.13",
"body-parser": "^1.19.0",
"eslint": "^7.0.0",
"express": "^4.17.1",
Expand All @@ -28,7 +29,8 @@
"jest": "^26.6.3",
"jest-extended": "^0.11.5",
"jsdoc-to-markdown": "^7.0.0",
"markdown-toc": "^1.2.0"
"markdown-toc": "^1.2.0",
"typescript": "^4.3.5"
},
"scripts": {
"build-docs": "npm run build-toc",
Expand Down
1 change: 1 addition & 0 deletions src/agent/transform-headers-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import http from 'http';
import WrappedAgent from './wrapped-agent';

// @ts-expect-error Private property
const { _storeHeader } = http.OutgoingMessage.prototype;

/**
Expand Down
14 changes: 12 additions & 2 deletions src/agent/wrapped-agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import http, { ClientRequest, ClientRequestArgs } from 'http';
import https from 'https';

/**
* @see https://github.com/nodejs/node/blob/533cafcf7e3ab72e98a2478bc69aedfdf06d3a5e/lib/_http_client.js#L129-L162
* @see https://github.com/nodejs/node/blob/533cafcf7e3ab72e98a2478bc69aedfdf06d3a5e/lib/_http_client.js#L234-L246
Expand All @@ -6,15 +9,19 @@
* so there's no need to replace `agent.addRequest`.
*/
class WrappedAgent {
constructor(agent) {
agent: http.Agent | https.Agent;

constructor(agent: http.Agent | https.Agent) {
this.agent = agent;
}

addRequest(request, options) {
addRequest(request: ClientRequest, options: ClientRequestArgs) {
// @ts-expect-error @types/node has incorrect types
return this.agent.addRequest(request, options);
}

get keepAlive() {
// @ts-expect-error @types/node has incorrect types
return this.agent.keepAlive;
}

Expand All @@ -23,14 +30,17 @@ class WrappedAgent {
}

get options() {
// @ts-expect-error @types/node has incorrect types
return this.agent.options;
}

get defaultPort() {
// @ts-expect-error @types/node has incorrect types
return this.agent.defaultPort;
}

get protocol() {
// @ts-expect-error @types/node has incorrect types
return this.agent.protocol;
}

Expand Down
Empty file removed src/got-exports.js
Empty file.
77 changes: 41 additions & 36 deletions src/hooks/browser-headers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import { URL } from 'url';
import { Options } from 'got-cjs';
import http2 from 'http2-wrapper';

/**
* Merges original generated headers and user provided overrides.
* All header overrides will have the original header case, because of antiscraping.
* @param {object} original
* @param {object} overrides
* @returns
*/
export function mergeHeaders(original: Record<string, string>, overrides: Record<string, string>) {
const fixedHeaders = new Map();

for (const entry of Object.entries(original)) {
fixedHeaders.set(entry[0].toLowerCase(), entry);
}

for (const entry of Object.entries(overrides)) {
fixedHeaders.set(entry[0].toLowerCase(), entry);
}

const headers: Record<string, string> = {};
for (const [key, value] of fixedHeaders.values()) {
headers[key] = value;
}

return headers;
}

/**
* @param {object} options
*/
exports.browserHeadersHook = async function (options) {
export async function browserHeadersHook(options: Options) {
const { context } = options;
const {
headerGeneratorOptions,
Expand All @@ -13,50 +41,27 @@ exports.browserHeadersHook = async function (options) {

if (!useHeaderGenerator) return;

const url = options.url as URL;

let alpnProtocol;
if (options.url.protocol === 'https:') {
if (url.protocol === 'https:') {
alpnProtocol = (await http2.auto.resolveProtocol({
host: options.url.hostname,
port: options.url.port || 443,
host: url.hostname,
port: url.port || 443,
rejectUnauthorized: false,
// @ts-expect-error Open an issue in http2-wrapper
ALPNProtocols: ['h2', 'http/1.1'],
servername: options.url.hostname,
servername: url.hostname,
})).alpnProtocol;
}

const mergedHeaderGeneratorOptions = {
const mergedHeaderGeneratorOptions: Record<string, string> = {
httpVersion: alpnProtocol === 'h2' ? '2' : '1',
...headerGeneratorOptions,
...(headerGeneratorOptions as {[key: string]: unknown}),
};

const generatedHeaders = headerGenerator.getHeaders(mergedHeaderGeneratorOptions);
const generatedHeaders = (headerGenerator as any).getHeaders(mergedHeaderGeneratorOptions);

// TODO: Remove this when Got supports Headers class.
options.headers = exports.mergeHeaders(generatedHeaders, options.headers);
};

/**
* Merges original generated headers and user provided overrides.
* All header overrides will have the original header case, because of antiscraping.
* @param {object} original
* @param {object} overrides
* @returns
*/
exports.mergeHeaders = function (original, overrides) {
const fixedHeaders = new Map();

for (const entry of Object.entries(original)) {
fixedHeaders.set(entry[0].toLowerCase(), entry);
}

for (const entry of Object.entries(overrides)) {
fixedHeaders.set(entry[0].toLowerCase(), entry);
}

const headers = {};
for (const [key, value] of fixedHeaders.values()) {
headers[key] = value;
}

return headers;
};
options.headers = mergeHeaders(generatedHeaders, options.headers as Record<string, string>);
}
19 changes: 15 additions & 4 deletions src/hooks/custom-options.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { Options, OptionsInit } from 'got-cjs';

/**
* @param {object} options
*/
export function customOptionsHook(raw, options) {
export function customOptionsHook(raw: OptionsInit, options: Options) {
if ('proxyUrl' in raw) {
options.context.proxyUrl = raw.proxyUrl;
// @ts-expect-error FIXME
options.context['proxyUrl'] = raw.proxyUrl;

// @ts-expect-error FIXME
delete raw.proxyUrl;
}

if ('headerGeneratorOptions' in raw) {
options.context.headerGeneratorOptions = raw.headerGeneratorOptions;
// @ts-expect-error FIXME
options.context['headerGeneratorOptions'] = raw.headerGeneratorOptions;

// @ts-expect-error FIXME
delete raw.headerGeneratorOptions;
}

if ('useHeaderGenerator' in raw) {
options.context.useHeaderGenerator = raw.useHeaderGenerator;
// @ts-expect-error FIXME
options.context['useHeaderGenerator'] = raw.useHeaderGenerator;

// @ts-expect-error FIXME
delete raw.useHeaderGenerator;
}
}
7 changes: 5 additions & 2 deletions src/hooks/http2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { URL } from 'url';
import { Options } from 'got-cjs';
import http2 from 'http2-wrapper';

export function http2Hook(options) {
if (options.http2 && options.url.protocol !== 'http:') {
export function http2Hook(options: Options) {
if (options.http2 && (options.url as URL).protocol !== 'http:') {
// @ts-expect-error FIXME
options.request = http2.auto;
}
}
8 changes: 2 additions & 6 deletions src/hooks/options-validation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const { default: ow } = require('ow');
import ow from 'ow';

/**
* @param {object} options
*/
function optionsValidationHandler(options) {
export function optionsValidationHandler(options: unknown) {
const validationSchema = {
proxyUrl: ow.optional.string.url,
useHeaderGenerator: ow.optional.boolean,
Expand All @@ -12,7 +12,3 @@ function optionsValidationHandler(options) {

ow(options, ow.object.partialShape(validationSchema));
}

module.exports = {
optionsValidationHandler,
};
41 changes: 23 additions & 18 deletions src/hooks/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import http from 'http';
import https from 'https';
import { URL } from 'url';
import http2 from 'http2-wrapper';
import HttpsProxyAgent from 'https-proxy-agent';
import HttpProxyAgent from 'http-proxy-agent';
import QuickLRU from 'quick-lru';
import { Options } from 'got-cjs';
import TransformHeadersAgent from '../agent/transform-headers-agent';

type Agent = http.Agent | https.Agent;

const {
HttpOverHttp2,
HttpsOverHttp2,
Expand All @@ -12,28 +18,24 @@ const {
Http2OverHttp,
} = http2.proxies;

const x = {
'(https.js:': (a: Agent) => (a as {protocol?: string}).protocol,
'(http.js:': (a: Agent) => (a as {protocol?: string}).protocol,
};

// `agent-base` package does stacktrace checks
// in order to set `agent.protocol`.
// The keys in this object are names of functions
// that will appear in the stacktrace.
const isAmbiguousAgent = (agent) => {
if (!isAmbiguousAgent.x) {
isAmbiguousAgent.x = {
'(https.js:': (a) => a.protocol,
'(http.js:': (a) => a.protocol,
};
}

const { x } = isAmbiguousAgent;

const isAmbiguousAgent = (agent: Agent): boolean => {
return x['(https.js:'](agent) !== x['(http.js:'](agent);
};

/**
* @see https://github.com/TooTallNate/node-agent-base/issues/61
* @param {Agent} agent
*/
const fixAgentBase = (agent) => {
const fixAgentBase = (agent: Agent) => {
if (isAmbiguousAgent(agent)) {
Object.defineProperty(agent, 'protocol', {
value: undefined,
Expand All @@ -46,31 +48,33 @@ const fixAgentBase = (agent) => {
/**
* @param {Agent} agent
*/
const fixAgent = (agent) => {
const fixAgent = (agent: Agent) => {
agent = fixAgentBase(agent);
agent = new TransformHeadersAgent(agent);
agent = new TransformHeadersAgent(agent) as unknown as Agent;

return agent;
};

/**
* @param {object} options
*/
export async function proxyHook(options) {
export async function proxyHook(options: Options) {
const { context: { proxyUrl } } = options;

if (proxyUrl) {
const parsedProxy = new URL(proxyUrl);
const parsedProxy = new URL(proxyUrl as string);

validateProxyProtocol(parsedProxy.protocol);
options.agent = await getAgents(parsedProxy, options.https.rejectUnauthorized);

// TODO: The `!` shouldn't be necessary. Open an issue in Got.
options.agent = await getAgents(parsedProxy, options.https.rejectUnauthorized!);
}
}

/**
* @param {string} protocol
*/
function validateProxyProtocol(protocol) {
function validateProxyProtocol(protocol: string) {
const isSupported = protocol === 'http:' || protocol === 'https:';

if (!isSupported) {
Expand All @@ -85,7 +89,7 @@ export const agentCache = new QuickLRU({ maxSize: 1000 });
* @param {boolean} rejectUnauthorized
* @returns {object}
*/
async function getAgents(parsedProxyUrl, rejectUnauthorized) {
async function getAgents(parsedProxyUrl: URL, rejectUnauthorized: boolean) {
const key = `${rejectUnauthorized}:${parsedProxyUrl.href}`;

let agent = exports.agentCache.get(key);
Expand All @@ -108,6 +112,7 @@ async function getAgents(parsedProxyUrl, rejectUnauthorized) {
host: parsedProxyUrl.hostname,
port: parsedProxyUrl.port,
rejectUnauthorized,
// @ts-expect-error Open an issue in http2-wrapper
ALPNProtocols: ['h2', 'http/1.1'],
servername: parsedProxyUrl.hostname,
});
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
CancelError,
got as gotCjs,
} from 'got-cjs';

// @ts-expect-error Missing types
import HeaderGenerator from 'header-generator';

import TransformHeadersAgent from './agent/transform-headers-agent';
Expand All @@ -39,8 +41,8 @@ const got = gotCjs.extend({
useHeaderGenerator: true,
},
agent: {
http: new TransformHeadersAgent(http.globalAgent),
https: new TransformHeadersAgent(https.globalAgent),
http: new TransformHeadersAgent(http.globalAgent) as unknown as http.Agent,
https: new TransformHeadersAgent(https.globalAgent) as unknown as https.Agent,
},
hooks: {
init: [
Expand Down
Loading

0 comments on commit a56e285

Please sign in to comment.