Initial
This commit is contained in:
425
node_modules/@azure/msal-node/src/network/HttpClient.ts
generated
vendored
Normal file
425
node_modules/@azure/msal-node/src/network/HttpClient.ts
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import {
|
||||
INetworkModule,
|
||||
NetworkRequestOptions,
|
||||
NetworkResponse,
|
||||
HttpStatus,
|
||||
} from "@azure/msal-common/node";
|
||||
import { HttpMethod, Constants, ProxyStatus } from "../utils/Constants.js";
|
||||
import { NetworkUtils } from "../utils/NetworkUtils.js";
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
|
||||
/**
|
||||
* This class implements the API for network requests.
|
||||
*/
|
||||
export class HttpClient implements INetworkModule {
|
||||
private proxyUrl: string;
|
||||
private customAgentOptions: http.AgentOptions | https.AgentOptions;
|
||||
|
||||
constructor(
|
||||
proxyUrl?: string,
|
||||
customAgentOptions?: http.AgentOptions | https.AgentOptions
|
||||
) {
|
||||
this.proxyUrl = proxyUrl || "";
|
||||
this.customAgentOptions = customAgentOptions || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Http Get request
|
||||
* @param url
|
||||
* @param options
|
||||
*/
|
||||
async sendGetRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions,
|
||||
timeout?: number
|
||||
): Promise<NetworkResponse<T>> {
|
||||
if (this.proxyUrl) {
|
||||
return networkRequestViaProxy(
|
||||
url,
|
||||
this.proxyUrl,
|
||||
HttpMethod.GET,
|
||||
options,
|
||||
this.customAgentOptions as http.AgentOptions,
|
||||
timeout
|
||||
);
|
||||
} else {
|
||||
return networkRequestViaHttps(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
options,
|
||||
this.customAgentOptions as https.AgentOptions,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http Post request
|
||||
* @param url
|
||||
* @param options
|
||||
*/
|
||||
async sendPostRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
if (this.proxyUrl) {
|
||||
return networkRequestViaProxy(
|
||||
url,
|
||||
this.proxyUrl,
|
||||
HttpMethod.POST,
|
||||
options,
|
||||
this.customAgentOptions as http.AgentOptions
|
||||
);
|
||||
} else {
|
||||
return networkRequestViaHttps(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
options,
|
||||
this.customAgentOptions as https.AgentOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const networkRequestViaProxy = <T>(
|
||||
destinationUrlString: string,
|
||||
proxyUrlString: string,
|
||||
httpMethod: string,
|
||||
options?: NetworkRequestOptions,
|
||||
agentOptions?: http.AgentOptions,
|
||||
timeout?: number
|
||||
): Promise<NetworkResponse<T>> => {
|
||||
const destinationUrl = new URL(destinationUrlString);
|
||||
const proxyUrl = new URL(proxyUrlString);
|
||||
|
||||
// "method: connect" must be used to establish a connection to the proxy
|
||||
const headers = options?.headers || ({} as Record<string, string>);
|
||||
const tunnelRequestOptions: https.RequestOptions = {
|
||||
host: proxyUrl.hostname,
|
||||
port: proxyUrl.port,
|
||||
method: "CONNECT",
|
||||
path: destinationUrl.hostname,
|
||||
headers: headers,
|
||||
};
|
||||
|
||||
if (agentOptions && Object.keys(agentOptions).length) {
|
||||
tunnelRequestOptions.agent = new http.Agent(agentOptions);
|
||||
}
|
||||
|
||||
// compose a request string for the socket
|
||||
let postRequestStringContent: string = "";
|
||||
if (httpMethod === HttpMethod.POST) {
|
||||
const body = options?.body || "";
|
||||
postRequestStringContent =
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n" +
|
||||
`Content-Length: ${body.length}\r\n` +
|
||||
`\r\n${body}`;
|
||||
} else {
|
||||
// optional timeout is only for get requests (regionDiscovery, for example)
|
||||
if (timeout) {
|
||||
tunnelRequestOptions.timeout = timeout;
|
||||
}
|
||||
}
|
||||
const outgoingRequestString =
|
||||
`${httpMethod.toUpperCase()} ${destinationUrl.href} HTTP/1.1\r\n` +
|
||||
`Host: ${destinationUrl.host}\r\n` +
|
||||
"Connection: close\r\n" +
|
||||
postRequestStringContent +
|
||||
"\r\n";
|
||||
|
||||
return new Promise<NetworkResponse<T>>((resolve, reject) => {
|
||||
const request = http.request(tunnelRequestOptions);
|
||||
|
||||
if (timeout) {
|
||||
request.on("timeout", () => {
|
||||
request.destroy();
|
||||
reject(new Error("Request time out"));
|
||||
});
|
||||
}
|
||||
|
||||
request.end();
|
||||
|
||||
// establish connection to the proxy
|
||||
request.on("connect", (response, socket) => {
|
||||
const proxyStatusCode =
|
||||
response?.statusCode || ProxyStatus.SERVER_ERROR;
|
||||
if (
|
||||
proxyStatusCode < ProxyStatus.SUCCESS_RANGE_START ||
|
||||
proxyStatusCode > ProxyStatus.SUCCESS_RANGE_END
|
||||
) {
|
||||
request.destroy();
|
||||
socket.destroy();
|
||||
reject(
|
||||
new Error(
|
||||
`Error connecting to proxy. Http status code: ${
|
||||
response.statusCode
|
||||
}. Http status message: ${
|
||||
response?.statusMessage || "Unknown"
|
||||
}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// make a request over an HTTP tunnel
|
||||
socket.write(outgoingRequestString);
|
||||
|
||||
const data: Buffer[] = [];
|
||||
socket.on("data", (chunk) => {
|
||||
data.push(chunk);
|
||||
});
|
||||
|
||||
socket.on("end", () => {
|
||||
// combine all received buffer streams into one buffer, and then into a string
|
||||
const dataString = Buffer.concat([...data]).toString();
|
||||
|
||||
// separate each line into it's own entry in an arry
|
||||
const dataStringArray = dataString.split("\r\n");
|
||||
// the first entry will contain the statusCode and statusMessage
|
||||
const httpStatusCode = parseInt(
|
||||
dataStringArray[0].split(" ")[1]
|
||||
);
|
||||
// remove "HTTP/1.1" and the status code to get the status message
|
||||
const statusMessage = dataStringArray[0]
|
||||
.split(" ")
|
||||
.slice(2)
|
||||
.join(" ");
|
||||
// the last entry will contain the body
|
||||
const body = dataStringArray[dataStringArray.length - 1];
|
||||
|
||||
// everything in between the first and last entries are the headers
|
||||
const headersArray = dataStringArray.slice(
|
||||
1,
|
||||
dataStringArray.length - 2
|
||||
);
|
||||
|
||||
// build an object out of all the headers
|
||||
const entries = new Map();
|
||||
headersArray.forEach((header) => {
|
||||
/**
|
||||
* the header might look like "Content-Length: 1531", but that is just a string
|
||||
* it needs to be converted to a key/value pair
|
||||
* split the string at the first instance of ":"
|
||||
* there may be more than one ":" if the value of the header is supposed to be a JSON object
|
||||
*/
|
||||
const headerKeyValue = header.split(new RegExp(/:\s(.*)/s));
|
||||
const headerKey = headerKeyValue[0];
|
||||
let headerValue = headerKeyValue[1];
|
||||
|
||||
// check if the value of the header is supposed to be a JSON object
|
||||
try {
|
||||
const object = JSON.parse(headerValue);
|
||||
|
||||
// if it is, then convert it from a string to a JSON object
|
||||
if (object && typeof object === "object") {
|
||||
headerValue = object;
|
||||
}
|
||||
} catch (e) {
|
||||
// otherwise, leave it as a string
|
||||
}
|
||||
|
||||
entries.set(headerKey, headerValue);
|
||||
});
|
||||
const headers = Object.fromEntries(entries);
|
||||
|
||||
const parsedHeaders = headers as Record<string, string>;
|
||||
const networkResponse = NetworkUtils.getNetworkResponse(
|
||||
parsedHeaders,
|
||||
parseBody(
|
||||
httpStatusCode,
|
||||
statusMessage,
|
||||
parsedHeaders,
|
||||
body
|
||||
) as T,
|
||||
httpStatusCode
|
||||
);
|
||||
|
||||
if (
|
||||
(httpStatusCode < HttpStatus.SUCCESS_RANGE_START ||
|
||||
httpStatusCode > HttpStatus.SUCCESS_RANGE_END) &&
|
||||
// do not destroy the request for the device code flow
|
||||
networkResponse.body["error"] !==
|
||||
Constants.AUTHORIZATION_PENDING
|
||||
) {
|
||||
request.destroy();
|
||||
}
|
||||
resolve(networkResponse);
|
||||
});
|
||||
|
||||
socket.on("error", (chunk) => {
|
||||
request.destroy();
|
||||
socket.destroy();
|
||||
reject(new Error(chunk.toString()));
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (chunk) => {
|
||||
request.destroy();
|
||||
reject(new Error(chunk.toString()));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const networkRequestViaHttps = <T>(
|
||||
urlString: string,
|
||||
httpMethod: string,
|
||||
options?: NetworkRequestOptions,
|
||||
agentOptions?: https.AgentOptions,
|
||||
timeout?: number
|
||||
): Promise<NetworkResponse<T>> => {
|
||||
const isPostRequest = httpMethod === HttpMethod.POST;
|
||||
const body: string = options?.body || "";
|
||||
|
||||
const url = new URL(urlString);
|
||||
const headers = options?.headers || ({} as Record<string, string>);
|
||||
const customOptions: https.RequestOptions = {
|
||||
method: httpMethod,
|
||||
headers: headers,
|
||||
...NetworkUtils.urlToHttpOptions(url),
|
||||
};
|
||||
|
||||
if (agentOptions && Object.keys(agentOptions).length) {
|
||||
customOptions.agent = new https.Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (isPostRequest) {
|
||||
// needed for post request to work
|
||||
customOptions.headers = {
|
||||
...customOptions.headers,
|
||||
"Content-Length": body.length,
|
||||
};
|
||||
} else {
|
||||
// optional timeout is only for get requests (regionDiscovery, for example)
|
||||
if (timeout) {
|
||||
customOptions.timeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<NetworkResponse<T>>((resolve, reject) => {
|
||||
let request: http.ClientRequest;
|
||||
// managed identity sources use http instead of https
|
||||
if (customOptions.protocol === "http:") {
|
||||
request = http.request(customOptions);
|
||||
} else {
|
||||
request = https.request(customOptions);
|
||||
}
|
||||
|
||||
if (isPostRequest) {
|
||||
request.write(body);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
request.on("timeout", () => {
|
||||
request.destroy();
|
||||
reject(new Error("Request time out"));
|
||||
});
|
||||
}
|
||||
|
||||
request.end();
|
||||
|
||||
request.on("response", (response) => {
|
||||
const headers = response.headers;
|
||||
const statusCode = response.statusCode as number;
|
||||
const statusMessage = response.statusMessage;
|
||||
|
||||
const data: Buffer[] = [];
|
||||
response.on("data", (chunk) => {
|
||||
data.push(chunk);
|
||||
});
|
||||
|
||||
response.on("end", () => {
|
||||
// combine all received buffer streams into one buffer, and then into a string
|
||||
const body = Buffer.concat([...data]).toString();
|
||||
|
||||
const parsedHeaders = headers as Record<string, string>;
|
||||
const networkResponse = NetworkUtils.getNetworkResponse(
|
||||
parsedHeaders,
|
||||
parseBody(
|
||||
statusCode,
|
||||
statusMessage,
|
||||
parsedHeaders,
|
||||
body
|
||||
) as T,
|
||||
statusCode
|
||||
);
|
||||
|
||||
if (
|
||||
(statusCode < HttpStatus.SUCCESS_RANGE_START ||
|
||||
statusCode > HttpStatus.SUCCESS_RANGE_END) &&
|
||||
// do not destroy the request for the device code flow
|
||||
networkResponse.body["error"] !==
|
||||
Constants.AUTHORIZATION_PENDING
|
||||
) {
|
||||
request.destroy();
|
||||
}
|
||||
resolve(networkResponse);
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (chunk) => {
|
||||
request.destroy();
|
||||
reject(new Error(chunk.toString()));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if extra parsing is needed on the repsonse from the server
|
||||
* @param statusCode {number} the status code of the response from the server
|
||||
* @param statusMessage {string | undefined} the status message of the response from the server
|
||||
* @param headers {Record<string, string>} the headers of the response from the server
|
||||
* @param body {string} the body from the response of the server
|
||||
* @returns {Object} JSON parsed body or error object
|
||||
*/
|
||||
const parseBody = (
|
||||
statusCode: number,
|
||||
statusMessage: string | undefined,
|
||||
headers: Record<string, string>,
|
||||
body: string
|
||||
) => {
|
||||
/*
|
||||
* Informational responses (100 – 199)
|
||||
* Successful responses (200 – 299)
|
||||
* Redirection messages (300 – 399)
|
||||
* Client error responses (400 – 499)
|
||||
* Server error responses (500 – 599)
|
||||
*/
|
||||
|
||||
let parsedBody;
|
||||
try {
|
||||
parsedBody = JSON.parse(body);
|
||||
} catch (error) {
|
||||
let errorType;
|
||||
let errorDescriptionHelper;
|
||||
if (
|
||||
statusCode >= HttpStatus.CLIENT_ERROR_RANGE_START &&
|
||||
statusCode <= HttpStatus.CLIENT_ERROR_RANGE_END
|
||||
) {
|
||||
errorType = "client_error";
|
||||
errorDescriptionHelper = "A client";
|
||||
} else if (
|
||||
statusCode >= HttpStatus.SERVER_ERROR_RANGE_START &&
|
||||
statusCode <= HttpStatus.SERVER_ERROR_RANGE_END
|
||||
) {
|
||||
errorType = "server_error";
|
||||
errorDescriptionHelper = "A server";
|
||||
} else {
|
||||
errorType = "unknown_error";
|
||||
errorDescriptionHelper = "An unknown";
|
||||
}
|
||||
|
||||
parsedBody = {
|
||||
error: errorType,
|
||||
error_description: `${errorDescriptionHelper} error occured.\nHttp status code: ${statusCode}\nHttp status message: ${
|
||||
statusMessage || "Unknown"
|
||||
}\nHeaders: ${JSON.stringify(headers)}`,
|
||||
};
|
||||
}
|
||||
|
||||
return parsedBody;
|
||||
};
|
||||
89
node_modules/@azure/msal-node/src/network/HttpClientWithRetries.ts
generated
vendored
Normal file
89
node_modules/@azure/msal-node/src/network/HttpClientWithRetries.ts
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import {
|
||||
HeaderNames,
|
||||
INetworkModule,
|
||||
Logger,
|
||||
NetworkRequestOptions,
|
||||
NetworkResponse,
|
||||
} from "@azure/msal-common/node";
|
||||
import { IHttpRetryPolicy } from "../retry/IHttpRetryPolicy.js";
|
||||
import { HttpMethod } from "../utils/Constants.js";
|
||||
|
||||
export class HttpClientWithRetries implements INetworkModule {
|
||||
private httpClientNoRetries: INetworkModule;
|
||||
private retryPolicy: IHttpRetryPolicy;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
httpClientNoRetries: INetworkModule,
|
||||
retryPolicy: IHttpRetryPolicy,
|
||||
logger: Logger
|
||||
) {
|
||||
this.httpClientNoRetries = httpClientNoRetries;
|
||||
this.retryPolicy = retryPolicy;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private async sendNetworkRequestAsyncHelper<T>(
|
||||
httpMethod: HttpMethod,
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
if (httpMethod === HttpMethod.GET) {
|
||||
return this.httpClientNoRetries.sendGetRequestAsync(url, options);
|
||||
} else {
|
||||
return this.httpClientNoRetries.sendPostRequestAsync(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendNetworkRequestAsync<T>(
|
||||
httpMethod: HttpMethod,
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
// the underlying network module (custom or HttpClient) will make the call
|
||||
let response: NetworkResponse<T> =
|
||||
await this.sendNetworkRequestAsyncHelper(httpMethod, url, options);
|
||||
|
||||
if ("isNewRequest" in this.retryPolicy) {
|
||||
this.retryPolicy.isNewRequest = true;
|
||||
}
|
||||
|
||||
let currentRetry: number = 0;
|
||||
while (
|
||||
await this.retryPolicy.pauseForRetry(
|
||||
response.status,
|
||||
currentRetry,
|
||||
this.logger,
|
||||
response.headers[HeaderNames.RETRY_AFTER]
|
||||
)
|
||||
) {
|
||||
response = await this.sendNetworkRequestAsyncHelper(
|
||||
httpMethod,
|
||||
url,
|
||||
options
|
||||
);
|
||||
currentRetry++;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async sendGetRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
return this.sendNetworkRequestAsync(HttpMethod.GET, url, options);
|
||||
}
|
||||
|
||||
public async sendPostRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
return this.sendNetworkRequestAsync(HttpMethod.POST, url, options);
|
||||
}
|
||||
}
|
||||
19
node_modules/@azure/msal-node/src/network/ILoopbackClient.ts
generated
vendored
Normal file
19
node_modules/@azure/msal-node/src/network/ILoopbackClient.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { AuthorizeResponse } from "@azure/msal-common/node";
|
||||
|
||||
/**
|
||||
* Interface for LoopbackClient allowing to replace the default loopback server with a custom implementation.
|
||||
* @public
|
||||
*/
|
||||
export interface ILoopbackClient {
|
||||
listenForAuthCode(
|
||||
successTemplate?: string,
|
||||
errorTemplate?: string
|
||||
): Promise<AuthorizeResponse>;
|
||||
getRedirectUri(): string;
|
||||
closeServer(): void;
|
||||
}
|
||||
117
node_modules/@azure/msal-node/src/network/LoopbackClient.ts
generated
vendored
Normal file
117
node_modules/@azure/msal-node/src/network/LoopbackClient.ts
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Constants as CommonConstants,
|
||||
AuthorizeResponse,
|
||||
HttpStatus,
|
||||
UrlUtils,
|
||||
} from "@azure/msal-common/node";
|
||||
import http from "http";
|
||||
import { NodeAuthError } from "../error/NodeAuthError.js";
|
||||
import { Constants } from "../utils/Constants.js";
|
||||
import { ILoopbackClient } from "./ILoopbackClient.js";
|
||||
|
||||
export class LoopbackClient implements ILoopbackClient {
|
||||
private server: http.Server | undefined;
|
||||
|
||||
/**
|
||||
* Spins up a loopback server which returns the server response when the localhost redirectUri is hit
|
||||
* @param successTemplate
|
||||
* @param errorTemplate
|
||||
* @returns
|
||||
*/
|
||||
async listenForAuthCode(
|
||||
successTemplate?: string,
|
||||
errorTemplate?: string
|
||||
): Promise<AuthorizeResponse> {
|
||||
if (this.server) {
|
||||
throw NodeAuthError.createLoopbackServerAlreadyExistsError();
|
||||
}
|
||||
|
||||
return new Promise<AuthorizeResponse>((resolve, reject) => {
|
||||
this.server = http.createServer(
|
||||
(req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
const url = req.url;
|
||||
if (!url) {
|
||||
res.end(
|
||||
errorTemplate ||
|
||||
"Error occurred loading redirectUrl"
|
||||
);
|
||||
reject(
|
||||
NodeAuthError.createUnableToLoadRedirectUrlError()
|
||||
);
|
||||
return;
|
||||
} else if (url === CommonConstants.FORWARD_SLASH) {
|
||||
res.end(
|
||||
successTemplate ||
|
||||
"Auth code was successfully acquired. You can close this window now."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectUri = this.getRedirectUri();
|
||||
const parsedUrl = new URL(url, redirectUri);
|
||||
const authCodeResponse =
|
||||
UrlUtils.getDeserializedResponse(parsedUrl.search) ||
|
||||
{};
|
||||
if (authCodeResponse.code) {
|
||||
res.writeHead(HttpStatus.REDIRECT, {
|
||||
location: redirectUri,
|
||||
}); // Prevent auth code from being saved in the browser history
|
||||
res.end();
|
||||
}
|
||||
if (authCodeResponse.error) {
|
||||
res.end(
|
||||
errorTemplate ||
|
||||
`Error occurred: ${authCodeResponse.error}`
|
||||
);
|
||||
}
|
||||
resolve(authCodeResponse);
|
||||
}
|
||||
);
|
||||
this.server.listen(0, "127.0.0.1"); // Listen on any available port
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port that the loopback server is running on
|
||||
* @returns
|
||||
*/
|
||||
getRedirectUri(): string {
|
||||
if (!this.server || !this.server.listening) {
|
||||
throw NodeAuthError.createNoLoopbackServerExistsError();
|
||||
}
|
||||
|
||||
const address = this.server.address();
|
||||
if (!address || typeof address === "string" || !address.port) {
|
||||
this.closeServer();
|
||||
throw NodeAuthError.createInvalidLoopbackAddressTypeError();
|
||||
}
|
||||
|
||||
const port = address && address.port;
|
||||
|
||||
return `${Constants.HTTP_PROTOCOL}${Constants.LOCALHOST}:${port}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the loopback server
|
||||
*/
|
||||
closeServer(): void {
|
||||
if (this.server) {
|
||||
// Only stops accepting new connections, server will close once open/idle connections are closed.
|
||||
this.server.close();
|
||||
|
||||
if (typeof this.server.closeAllConnections === "function") {
|
||||
/*
|
||||
* Close open/idle connections. This API is available in Node versions 18.2 and higher
|
||||
*/
|
||||
this.server.closeAllConnections();
|
||||
}
|
||||
this.server.unref();
|
||||
this.server = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user