node.js - Node (v18) HTTPS-modul - Fel: ej stöds
Fråga
Varför får jag ett fel om en icke-stödd begäran när jag kör den här koden i Node.js v18.4.0, men den lyckas i v16.16.0?
Kod
Den direkta koden som ger ett fel är mina enhetstester för biblioteket jag gör för mitt företag internt. Ursäkta vissa utslagningar av koden, men jag har försökt städa upp den så mycket som möjligt för att publicera den offentligt.
// ~/http/tests/Client.spec.ts
import { HttpClient } from '../src';
describe('Standard HTTP Client', () => {
const { request: http } = HttpClient;
it('should save the response when a state is given', async () => {
const response = await http({ method: 'GET', url: 'https://www.google.com' });
expect(response).toBeDefined();
});
});
Resultatet i v16 är en framgång, men resultatet i v18 är:
Error: unsupported
at configSecureContext (node:internal/tls/secure-context:282:15)
at Object.createSecureContext (node:_tls_common:117:3)
at Object.connect (node:_tls_wrap:1624:48)
at Agent.createConnection (node:https:150:22)
at Agent.createSocket (node:_http_agent:346:26)
at Agent.addRequest (node:_http_agent:297:10)
at new ClientRequest (node:_http_client:322:16)
at Client.request (node:https:360:10)
at Client.promiseHandler (/home/user/projects/node/http/src/Client.ts:72:25)
at new Promise (<anonymous>)
Stödjande kod
Här är HttpClient-klassen som jag har implementerat. Om vissa uttalanden verkar omständliga är det troligtvis för att det fanns loggning i den ursprungliga implementeringen som jag har tagit bort för syftet med detta inlägg.
Obs: kommentar tillagd ovanför rad 72 för felmeddelandet, i den privata metoden promiseHandler.
En annan notering, den ursprungliga koden hittades på StackOverflow när jag började detta arbete för flera månader sedan. Jag kan inte vara säker på det ursprungliga inlägg jag såg, men detta är ett troligt svar som gav mig inspiration. Jag utökade det från den ursprungliga implementeringen till en objektorienterad metod och formaliserade dataregistreringarna av en förfrågningskropp och ett svarsobjekt, eftersom vi har vissa förväntade beteenden kring loggning av information vid körning.
// ~/http/src/Client.ts
import { request as http, ClientRequest, IncomingMessage } from 'http';
import { request as https } from 'https';
import './extensions';
import { PromiseExecutor } from './common';
import { IHttpResponse, IRequestArgs } from './interfaces';
import { Request as HttpRequest } from './Request';
import { Response as HttpResponse } from './Response';
type RequestMethod = typeof http | typeof https;
export class Client {
private redirects: Map<URL, number>;
private request?: ClientRequest;
private readonly requestOptions: HttpRequest;
private response?: HttpResponse;
private reject: PromiseExecutor;
private resolve: PromiseExecutor;
constructor(request: IRequestArgs) {
this.requestOptions = new HttpRequest(request);
this.redirects = new Map();
this.redirects.set(this.requestOptions.url as URL, 1);
this.reject = () => void 0;
this.resolve = () => void 0;
}
private get boundPromiseHandler() {
return this.promiseHandler.bind(this);
}
private get boundRequestHandler() {
return this.requestHandler.bind(this);
}
private get boundRequestErrorHandler() {
return this.requestErrorHandler.bind(this);
}
handleRedirect(this: Client, response: IHttpResponse): Promise<IHttpResponse> {
const { headers } = response;
if(!headers || !headers.has('location')) {
return Promise.resolve(response);
}
const location = headers.get('location')!;
const url = new URL(location);
const redirects = this.redirects.get(url) ?? 0;
this.redirects.set(url, redirects + 1);
this.requestOptions.setRequestUrl(url);
if(this.redirects.get(url)! < 2) {
return Client.request(this.requestOptions as IRequestArgs, this);
}
return Promise.resolve(response);
}
get isSecure() {
return [ 'ssh:', 'sftp:', 'https:' ].includes(this.requestOptions.protocol ?? 'http');
}
get promise(): Promise<IHttpResponse> {
return new Promise(this.boundPromiseHandler) as Promise<IHttpResponse>;
}
private promiseHandler(resolve: PromiseExecutor, reject: PromiseExecutor): void {
const { requestOptions: { body, hasPayload = false, ...rest } } = Object.assign(this, { resolve, reject }) as this;
// This is line #72, position 25 is the dot (.) before `requestMethod`
this.request = this.requestMethod(rest, this.boundRequestHandler).on('error', this.boundRequestErrorHandler);
// Data payloads must be written to the request stream
if(hasPayload) {
this.request.write(body);
}
this.request.end();
}
static async request(options: IRequestArgs, client?: Client): Promise<IHttpResponse> {
client = client ?? new Client(options);
const response = await client.promise;
if(Number.between(response.statusCode ?? 0, 300, 399)) {
return client.handleRedirect(response);
}
return response;
}
get requestMethod(): RequestMethod {
if(this.isSecure) {
return https;
}
return http;
}
private requestHandler(response: IncomingMessage) {
this.response = new HttpResponse(response, this.resolve, this.reject);
}
private requestErrorHandler(error?: Error) {
if(error) {
return this.reject(error);
}
return this.reject(new Error('An error occurred while handling the HTTP Response'));
}
toJSON() {
return {
request: this.requestOptions,
response: this.response,
}
}
toString() {
return JSON.stringify(this, null, 2);
}
}
Övrigt
Jag kan lägga till ytterligare stödjande kod, men jag kände att frågan redan var ganska utförlig. Request-klassen är bara en uppräkning av RequestOptions-gränssnitten från node:http och node:https, med några vanliga standardvärden som method=options.method ?? 'GET'. Response-klassen har lite mer innehåll, men den är bara ansvarig för att sammanfoga datadelar som tas emot på data-händelsen, och sedan försöka tolka svarskroppen som JSON.
Den fullständiga koden kan ses i ett GitHub-arkiv som jag just skapade för denna fråga.