@kronos-integration/service-http
Version:
306 lines (269 loc) • 7.06 kB
JavaScript
import { createServer as httpCreateServer } from "node:http";
import { createServer as httpsCreateServer } from "node:https";
import {
getAttributesJSON,
prepareAttributesDefinitions,
object_attribute,
default_attribute,
public_key_attribute,
private_key_attribute,
certificate_attribute,
timeout_attribute,
hostname_attribute,
url_attribute
} from "pacc";
import { Service } from "@kronos-integration/service";
import { HTTPEndpoint, endpointRouter } from "./http-endpoint.mjs";
import { WSEndpoint, initializeWS, closeWS } from "./ws-endpoint.mjs";
export { CTXInterceptor } from "./ctx-interceptor.mjs";
export { CTXBodyParamInterceptor } from "./ctx-body-param-interceptor.mjs";
export { CTXJWTVerifyInterceptor } from "./ctx-jwt-verivy-interceptor.mjs";
export { HTTPEndpoint, WSEndpoint };
/**
* HTTP server.
* @property {http.Server} server only present if state is running
*/
export class ServiceHTTP extends Service {
/**
* @return {string} 'http'
*/
static get name() {
return "http";
}
static get description() {
return "http server";
}
static attributes = prepareAttributesDefinitions(
{
jwt: {
...object_attribute,
description: "jwt public key",
attributes: {
public: {
...public_key_attribute,
description: "public key to check token against",
mandatory: true,
persistent: true
}
}
},
listen: {
...object_attribute,
description: "server listen definition",
attributes: {
url: {
...url_attribute,
description: "url of the http(s) server",
needsRestart: true
},
address: {
...hostname_attribute,
description: "hostname/ip-address of the http(s) server",
needsRestart: true
},
socket: {
...default_attribute,
description: "listening port|socket of the http(s) server",
needsRestart: true
}
}
},
key: {
...private_key_attribute,
description: "ssl private key",
needsRestart: true,
serverOption: true
},
cert: {
...certificate_attribute,
description: "ssl certificate",
needsRestart: true,
serverOption: true
},
timeout: {
...object_attribute,
attributes: {
server: {
...timeout_attribute,
description: "server timeout",
default: 120,
set(value, attribute) {
if (value === undefined) {
value = attribute.default;
}
this.timeout ||= {};
this.timeout.server = value;
if (this.server) {
this.server.setTimeout(value * 1000);
return true;
}
return false;
}
}
}
}
},
Service.attributes
);
/**
* @return {string} name with url
*/
get extendetName() {
return `${this.name}(${this.url})`;
}
/**
* On demand create RouteSendEndpoint´s.
* @param {string} name
* @param {Object|string} definition
* @return {Class} RouteSendEndpoint if path is present of name starts with '/'
*/
endpointFactoryFromConfig(name, definition, ic) {
if (definition.ws) {
return WSEndpoint;
}
if (
definition.method ||
definition.path ||
name[0] === "/" ||
name.match(/^\w+:\//)
) {
return HTTPEndpoint;
}
return super.endpointFactoryFromConfig(name, definition, ic);
}
/**
* Should we make a secure connection.
*
* @return {boolean} true if key is present
*/
get isSecure() {
return this.key !== undefined;
}
get scheme() {
return this.isSecure ? "https" : "http";
}
get url() {
const listen = this.listen;
if (listen) {
const socket = this.socket;
const url = listen.url;
if (url) {
if (Number.isInteger(socket)) {
const u = new URL(url);
u.port = socket;
return u.toString().replace(/\/$/, "");
}
return url;
}
if (socket !== undefined) {
return Number.isInteger(socket)
? `${this.scheme}://${this.address}:${socket}`
: `fd:///${socket.fd}`;
}
}
}
get socket() {
const listen = this.listen;
if (listen) {
const socket = listen.socket;
if (socket) {
return socket;
}
const url = listen.url;
if (url) {
const u = new URL(url);
return Number(u.port);
}
}
}
get address() {
const listen = this.listen;
if (listen) {
const address = listen.address;
if (address) {
return address;
}
const url = listen.url;
if (url) {
const u = new URL(url);
return u.hostname;
}
return "localhost";
}
}
async _start() {
await super._start();
try {
const serverOptions = getAttributesJSON(
this,
this.attributes,
(name, attribute) => attribute.serverOption
);
const server = (this.server = (
this.isSecure ? httpsCreateServer : httpCreateServer
)(serverOptions, endpointRouter(this)));
if (this.timeout !== undefined) {
server.setTimeout(this.timeout * 1000);
}
await new Promise((resolve, reject) => {
const listenHandler = err => {
if (err) {
delete this.server;
this.error(err);
reject(err);
} else {
this.trace(
level =>
`Listening (${this.server.listening}) on ${
this.url
} (${JSON.stringify(this.socket)})`
);
resolve();
}
};
server.on("error", listenHandler);
try {
server.listen(
...[this.socket, this.address, listenHandler].filter(x => x)
);
} catch (err) {
delete this.server;
this.error(err);
reject(err);
}
});
initializeWS(this);
} catch (e) {
delete this.server;
throw e;
}
}
async _stop() {
if (this.server) {
const openConnectionsInfoInterval = setInterval(
() =>
this.server.getConnections((err, count) =>
this.info(`${this.fullName}: ${count} connection(s) still open`)
),
2000
);
try {
closeWS(this);
await new Promise((resolve, reject) => {
this.server.close(err => {
if (err) {
reject(err);
} else {
this.server = undefined;
resolve();
}
});
});
} finally {
clearInterval(openConnectionsInfoInterval);
}
}
return super._stop();
}
}
export default ServiceHTTP;