Skip to main content
Glama
server.ts4.95 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { sleep } from '@medplum/core'; import net from 'node:net'; import type { Hl7ConnectionOptions } from './connection'; import { Hl7Connection } from './connection'; /** * Options for configuring the `Hl7Server#stop` method. */ export interface Hl7ServerStopOptions { /** * Time in milliseconds to allow client connections to gracefully close after the stop method has been called, before forcefully closing them. * * Can be set to `-1` to disable forceful draining of connections during stop. * * Defaults to `10_000`. */ forceDrainTimeoutMs?: number; } export const DEFAULT_FORCE_DRAIN_TIMEOUT_MS = 10_000; export class Hl7Server { readonly handler: (connection: Hl7Connection) => void; server?: net.Server; private encoding: string | undefined = undefined; private enhancedMode = false; private messagesPerMin: number | undefined = undefined; private readonly connections = new Set<Hl7Connection>(); constructor(handler: (connection: Hl7Connection) => void) { this.handler = handler; } async start(port: number, encoding?: string, enhancedMode?: boolean, options?: Hl7ConnectionOptions): Promise<void> { if (encoding) { this.setEncoding(encoding); } if (enhancedMode !== undefined) { this.setEnhancedMode(enhancedMode); } if (options?.messagesPerMin !== undefined) { this.setMessagesPerMin(options.messagesPerMin); } const server = net.createServer((socket) => { const connection = new Hl7Connection(socket, this.encoding, this.enhancedMode, { messagesPerMin: this.messagesPerMin, }); this.handler(connection); this.connections.add(connection); connection.addEventListener('close', () => { this.connections.delete(connection); }); }); await new Promise<void>((resolve) => { const listenOnPort = (port: number): void => { server.listen(port, resolve); }; // Node errors have a code const errorListener = async (e: Error & { code?: string }): Promise<void> => { if (e?.code === 'EADDRINUSE') { await sleep(50); server.close(); listenOnPort(port); } }; server.on('error', errorListener); server.once('listening', () => { server.off('error', errorListener); }); listenOnPort(port); this.server = server; }); } /** * Stops the HL7 server. * * By default, the server will stop accepting new connections after this method is called, and wait for current connections to close naturally. * * If all connections don't close within 10 seconds, the server will forcefully close them before shutting down. * * The default time to wait before forcefully closing connections can be changed by passing an integer value for the optional `options.forceDrainTimeoutMs`. * * Forced drain can also be disabled by passing `-1` for `options.forceDrainTimeoutMs`. * @param options - Optional options to configure the stopping of the server. * @returns Promise that resolves when the server has stopped, or rejects if an error prevents server from stopping. */ async stop(options?: Hl7ServerStopOptions): Promise<void> { return new Promise<void>((resolve, reject) => { if (!this.server) { reject(new Error('Stop was called but there is no server running')); return; } let forceDrainTimeout: NodeJS.Timeout | undefined; if (options?.forceDrainTimeoutMs !== -1) { forceDrainTimeout = setTimeout(() => { for (const connection of this.connections) { // Theoretically close should almost never throw as most errors are caught internal to the method and emitted as error events // We put a .catch here to prevent floating promises and log any errors that somehow make it through connection.close().catch(console.error); } }, options?.forceDrainTimeoutMs ?? DEFAULT_FORCE_DRAIN_TIMEOUT_MS); } this.server.close((err) => { if (err) { reject(err); return; } if (forceDrainTimeout) { clearTimeout(forceDrainTimeout); } this.connections.clear(); this.server = undefined; resolve(); }); }); } setEnhancedMode(enhancedMode: boolean): void { this.enhancedMode = enhancedMode; } getEnhancedMode(): boolean { return this.enhancedMode; } setEncoding(encoding: string | undefined): void { this.encoding = encoding; } getEncoding(): string | undefined { return this.encoding; } setMessagesPerMin(messagesPerMin: number | undefined): void { this.messagesPerMin = messagesPerMin; } getMessagesPerMin(): number | undefined { return this.messagesPerMin; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server