Skip to content
File

Blob: src/node/internal/internal_http_agent.ts

typescript145 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4// Copyright Joyent and Node contributors. All rights reserved. MIT license.
5 
6import { EventEmitter } from 'node-internal:events';
7import { validateOneOf, validateNumber } from 'node-internal:validators';
8import { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';
9import type {
10 RequestOptions,
11 Agent as _Agent,
12 AgentOptions,
13 ClientRequest,
14 IncomingMessage,
15} from 'node:http';
16import type { Socket, NetConnectOpts } from 'node:net';
17import type { Duplex } from 'node:stream';
18 
19// This is mostly a stub implementation.
20// We don't intend to support the Agent API right now beyond providing a very limited stub API.
21//
22export class Agent extends EventEmitter implements _Agent {
23 defaultPort: number;
24 protocol: string;
25 
26 options: AgentOptions & { __proto__: null };
27 agentKeepAliveTimeoutBuffer: number = 1000;
28 keepAliveMsecs: number = 1000;
29 keepAlive: boolean = false;
30 maxSockets: number;
31 maxFreeSockets: number;
32 scheduling: 'lifo' | 'fifo' = 'lifo';
33 maxTotalSockets: number;
34 totalSocketCount: number;
35 readonly freeSockets: NodeJS.ReadOnlyDict<Socket[]> = {};
36 readonly sockets: NodeJS.ReadOnlyDict<Socket[]> = {};
37 readonly requests: NodeJS.ReadOnlyDict<IncomingMessage[]> = {};
38 
39 constructor(options?: AgentOptions) {
40 super({});
41 this.options = { __proto__: null, ...options };
42 
43 // Match Node.js: read defaultPort and protocol from options with fallbacks,
44 // set path = null, and ensure proxyEnv key exists.
45 /* eslint-disable @typescript-eslint/no-unsafe-assignment,
46 @typescript-eslint/no-unsafe-member-access,
47 @typescript-eslint/no-explicit-any */
48 const opts = this.options as any;
49 this.defaultPort = opts.defaultPort || 80;
50 this.protocol = opts.protocol || 'http:';
51 opts.path = null;
52 if (!('proxyEnv' in opts)) {
53 opts.proxyEnv = undefined;
54 }
55 /* eslint-enable @typescript-eslint/no-unsafe-assignment,
56 @typescript-eslint/no-unsafe-member-access,
57 @typescript-eslint/no-explicit-any */
58 
59 if (this.options.noDelay === undefined) this.options.noDelay = true;
60 
61 // Don't confuse net and make it think that we're connecting to a pipe
62 this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;
63 this.keepAlive = this.options.keepAlive || false;
64 this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
65 this.maxFreeSockets = this.options.maxFreeSockets || 256;
66 this.scheduling = this.options.scheduling || 'lifo';
67 this.totalSocketCount = 0;
68 
69 validateOneOf(this.scheduling, 'scheduling', ['fifo', 'lifo']);
70 
71 if (this.options.maxTotalSockets !== undefined) {
72 validateNumber(this.options.maxTotalSockets, 'maxTotalSockets', 1);
73 this.maxTotalSockets = this.options.maxTotalSockets;
74 } else {
75 this.maxTotalSockets = Infinity;
76 }
77 }
78 
79 static defaultMaxSockets: number = Infinity;
80 
81 createConnection(
82 _options: NetConnectOpts,
83 _callback?: (err: Error | null, stream: Duplex) => void
84 ): Duplex {
85 // We can't create a connection like this because our implementation
86 // relies on fetch() not on node:net sockets. In node.js, method
87 // calls node:net's createConnection method.
88 throw new ERR_METHOD_NOT_IMPLEMENTED('createConnection');
89 }
90 
91 getName(options: RequestOptions = {}): string {
92 let name = options.host || 'localhost';
93 
94 name += ':';
95 if (options.port) name += options.port.toString();
96 
97 name += ':';
98 if (options.localAddress) name += options.localAddress;
99 
100 // Pacify parallel/test-http-agent-getname by only appending
101 // the ':' when options.family is set.
102 if (options.family === 4 || options.family === 6)
103 name += `:${options.family}`;
104 
105 if (options.socketPath) name += `:${options.socketPath}`;
106 
107 return name;
108 }
109 
110 addRequest(_req: ClientRequest, _options: unknown): void {
111 // Not implemented. Acts as a no-op.
112 }
113 
114 createSocket(
115 _req: ClientRequest,
116 _options: unknown,
117 _callback: VoidFunction
118 ): void {
119 // Not implemented. Acts as a no-op.
120 }
121 
122 removeSocket(_socket: Socket): void {
123 // Not implemented. Acts as a no-op.
124 }
125 
126 keepSocketAlive(_socket: Socket): boolean {
127 // Not implemented. Acts as a no-op.
128 return false;
129 }
130 
131 reuseSocket(_socket: Socket, _req: ClientRequest): void {
132 // Not implemented. Acts as a no-op.
133 }
134 
135 destroy(_error?: Error): void {
136 // Not implemented. Acts as a no-op.
137 }
138}
139 
140export const globalAgent = new Agent({
141 keepAlive: true,
142 scheduling: 'lifo',
143 timeout: 5000,
144});