improve gateway

This commit is contained in:
SrGooglo 2025-04-01 21:52:04 +00:00
parent 82b7bc579b
commit f6c7ebd468
7 changed files with 195 additions and 102 deletions

View File

@ -17,7 +17,9 @@ import ServiceManager from "./services/manager"
import Service from "./services/service" import Service from "./services/service"
import * as Managers from "./managers" import * as Managers from "./managers"
global.debugFlag = process.env.DEBUG === "true"
const isProduction = process.env.NODE_ENV === "production" const isProduction = process.env.NODE_ENV === "production"
const sslKey = path.resolve(process.cwd(), ".ssl", "privkey.pem") const sslKey = path.resolve(process.cwd(), ".ssl", "privkey.pem")
const sslCert = path.resolve(process.cwd(), ".ssl", "cert.pem") const sslCert = path.resolve(process.cwd(), ".ssl", "cert.pem")
@ -71,6 +73,10 @@ export default class Gateway {
* Creates and initializes all service instances * Creates and initializes all service instances
*/ */
async createServiceInstances() { async createServiceInstances() {
if (!debugFlag) {
console.log(`🔰 Starting all services, please wait...`)
}
for await (const servicePath of this.services) { for await (const servicePath of this.services) {
const instanceBasePath = path.dirname(servicePath) const instanceBasePath = path.dirname(servicePath)
const servicePkg = require( const servicePkg = require(
@ -99,8 +105,6 @@ export default class Gateway {
// Initialize service // Initialize service
await service.initialize() await service.initialize()
console.log(`📦 [${serviceId}] Service initialized`)
} }
} }
@ -113,10 +117,6 @@ export default class Gateway {
this.serviceRegistry[serviceId].initialized = true this.serviceRegistry[serviceId].initialized = true
this.serviceRegistry[serviceId].ready = true this.serviceRegistry[serviceId].ready = true
console.log(
`✅ [${serviceId}][${this.serviceRegistry[serviceId].index}] Ready`,
)
// Check if all services are ready // Check if all services are ready
this.checkAllServicesReady() this.checkAllServicesReady()
} }
@ -130,7 +130,7 @@ export default class Gateway {
const id = service.id const id = service.id
if (data.type === "log") { if (data.type === "log") {
console.log(`[${id}] ${data.message}`) console.log(`[ipc:${id}] ${data.message}`)
} }
if (data.status === "ready") { if (data.status === "ready") {
@ -234,9 +234,9 @@ export default class Gateway {
//console.clear() //console.clear()
//console.log(comtyAscii) //console.log(comtyAscii)
console.log("\n\n\n") console.log("\n")
console.log(`🎉 All services[${this.services.length}] ready!\n`) console.log(`🎉 All services[${this.services.length}] ready!\n`)
console.log(`USE: select <service>, reload, exit`) console.log(`USE: select <service>, reload, exit\n`)
if (typeof this.gateway.applyConfiguration === "function") { if (typeof this.gateway.applyConfiguration === "function") {
await this.gateway.applyConfiguration() await this.gateway.applyConfiguration()
@ -285,11 +285,21 @@ export default class Gateway {
this.services = await scanServices() this.services = await scanServices()
this.ipcRouter = new IPCRouter() this.ipcRouter = new IPCRouter()
global.eventBus = this.eventBus
global.ipcRouter = this.ipcRouter
if (this.services.length === 0) { if (this.services.length === 0) {
console.error("❌ No services found") console.error("❌ No services found")
return process.exit(1) return process.exit(1)
} }
console.log(comtyAscii)
console.log(
`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${this.state.internalIp} | ${isProduction ? "production" : "development"} | ${this.constructor.gatewayMode} |\n`,
)
console.log(`📦 Found ${this.services.length} service(s)`)
// Initialize gateway // Initialize gateway
this.gateway = new Managers[this.constructor.gatewayMode]({ this.gateway = new Managers[this.constructor.gatewayMode]({
port: this.state.proxyPort, port: this.state.proxyPort,
@ -302,19 +312,6 @@ export default class Gateway {
await this.gateway.initialize() await this.gateway.initialize()
} }
// Make key components available globally
global.eventBus = this.eventBus
global.ipcRouter = this.ipcRouter
global.proxy = this.proxy
//console.clear()
console.log(comtyAscii)
console.log(
`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${this.state.internalIp} | ${isProduction ? "production" : "development"} \n\n\n`,
)
console.log(`📦 Found ${this.services.length} service(s)`)
// Watch for service state changes // Watch for service state changes
Observable.observe(this.serviceRegistry, (changes) => { Observable.observe(this.serviceRegistry, (changes) => {
this.checkAllServicesReady() this.checkAllServicesReady()
@ -325,7 +322,7 @@ export default class Gateway {
// WARNING: Starting relp makes uwebsockets unable to work properly, surging some bugs from nodejs (domain.enter) // WARNING: Starting relp makes uwebsockets unable to work properly, surging some bugs from nodejs (domain.enter)
// use another alternative to parse commands, like stdin reading or something... // use another alternative to parse commands, like stdin reading or something...
//this.startRELP() this.startRELP()
} }
startRELP() { startRELP() {

View File

@ -123,7 +123,7 @@ export default class Proxy {
return reject(err) return reject(err)
} }
console.log( console.log(
`🚀 Gateway listening on ${this.config.port}:${this.config.internalIp}`, `🚀 Gateway listening on ${this.config.internalIp}:${this.config.port}`,
) )
resolve() resolve()
}, },
@ -205,6 +205,7 @@ export default class Proxy {
name: pkg.name, name: pkg.name,
version: pkg.version, version: pkg.version,
lb_version: defaults?.version || "unknown", lb_version: defaults?.version || "unknown",
gateway: "standard",
}), }),
) )
return return

View File

@ -2,7 +2,8 @@ import fs from "node:fs/promises"
import { existsSync, mkdirSync, writeFileSync } from "node:fs" import { existsSync, mkdirSync, writeFileSync } from "node:fs"
import path from "node:path" import path from "node:path"
import { execSync, spawn } from "node:child_process" import { execSync, spawn } from "node:child_process"
import { platform } from "node:os" import defaults from "linebridge/dist/defaults"
import pkg from "../../../package.json"
const localNginxBinary = path.resolve(process.cwd(), "nginx-bin") const localNginxBinary = path.resolve(process.cwd(), "nginx-bin")
@ -43,9 +44,6 @@ export default class NginxManager {
this.nginxProcess = null this.nginxProcess = null
this.isNginxRunning = false this.isNginxRunning = false
// Debug mode
this.debug = options.debug || false
if ( if (
existsSync(this.options.cert_file_name) && existsSync(this.options.cert_file_name) &&
existsSync(this.options.key_file_name) existsSync(this.options.key_file_name)
@ -128,12 +126,19 @@ export default class NginxManager {
.join(this.tempDir, "cache") .join(this.tempDir, "cache")
.replace(/\\/g, "/") .replace(/\\/g, "/")
const mainEndpointJSON = JSON.stringify({
name: pkg.name,
version: "1.21.6",
lb_version: defaults?.version ?? "unknown",
gateway: "nginx",
})
const config = ` const config = `
# Nginx configuration for Comty API Gateway # Nginx configuration for Comty API Gateway
# Auto-generated - Do not edit manually # Auto-generated - Do not edit manually
worker_processes auto; worker_processes auto;
error_log ${normalizedLogsDir}/error.log ${this.debug ? "debug" : "error"}; error_log ${normalizedLogsDir}/error.log ${debugFlag ? "debug" : "error"};
pid ${normalizedTempDir}/nginx.pid; pid ${normalizedTempDir}/nginx.pid;
events { events {
@ -155,7 +160,7 @@ http {
'request_time: $request_time ' 'request_time: $request_time '
'http_version: $server_protocol'; 'http_version: $server_protocol';
access_log ${normalizedLogsDir}/access.log ${this.debug ? "debug" : "main"}; access_log ${normalizedLogsDir}/access.log ${debugFlag ? "debug" : "main"};
sendfile on; sendfile on;
tcp_nodelay on; tcp_nodelay on;
@ -190,7 +195,7 @@ http {
add_header 'Access-Control-Allow-Headers' '*' always; add_header 'Access-Control-Allow-Headers' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,PATCH,POST,DELETE' always; add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,PATCH,POST,DELETE' always;
return 200 '{"ok":1}'; return 200 '${mainEndpointJSON}';
} }
# Include service-specific configurations # Include service-specific configurations
@ -247,7 +252,7 @@ http {
? routePath ? routePath
: `/${routePath}` : `/${routePath}`
if (this.debug) { if (debugFlag) {
console.log( console.log(
`🔍 Registering route for [${serviceId}]: ${normalizedPath} -> ${target} (${websocket ? "WebSocket" : "HTTP"})`, `🔍 Registering route for [${serviceId}]: ${normalizedPath} -> ${target} (${websocket ? "WebSocket" : "HTTP"})`,
) )
@ -349,7 +354,7 @@ http {
// Write the config // Write the config
await fs.writeFile(this.servicesConfigPath, config) await fs.writeFile(this.servicesConfigPath, config)
if (this.debug) { if (debugFlag) {
console.log(`📄 Writted [${this.routes.size}] service routes`) console.log(`📄 Writted [${this.routes.size}] service routes`)
} }
} }
@ -494,7 +499,7 @@ ${locationDirective} {
const cmdString = `"${this.nginxBinary}" ${allArgs.join(" ")}` const cmdString = `"${this.nginxBinary}" ${allArgs.join(" ")}`
if (this.debug) { if (debugFlag) {
console.log(`🔍 Executing: ${cmdString}`) console.log(`🔍 Executing: ${cmdString}`)
} }
@ -566,7 +571,7 @@ ${locationDirective} {
* Stop the Nginx server * Stop the Nginx server
* @returns {Boolean} - Success status * @returns {Boolean} - Success status
*/ */
async close() { async stop() {
try { try {
if (this.nginxProcess) { if (this.nginxProcess) {
// Try graceful shutdown first // Try graceful shutdown first

View File

@ -1,68 +1,162 @@
import repl from "node:repl"
export default class RELP { export default class RELP {
constructor(handlers) { constructor(handlers) {
this.handlers = handlers this.handlers = handlers
this.initCommandLine()
}
repl.start({ initCommandLine() {
prompt: "> ", // Configure line-by-line input mode
useGlobal: true, process.stdin.setEncoding("utf8")
eval: (input, context, filename, callback) => { process.stdin.resume()
let inputs = input.split(" ") process.stdin.setRawMode(true)
// remove last \n from input // Buffer to store user input
inputs[inputs.length - 1] = inputs[inputs.length - 1].replace(/\n/g, "") this.inputBuffer = ""
// find relp command // Show initial prompt
const command = inputs[0] this.showPrompt()
const args = inputs.slice(1)
const command_fn = this.commands.find((relp_command) => { // Handle user input
let exising = false process.stdin.on("data", (data) => {
const key = data.toString()
if (Array.isArray(relp_command.aliases)) { // Ctrl+C to exit
exising = relp_command.aliases.includes(command) if (key === "\u0003") {
} process.exit(0)
}
if (relp_command.cmd === command) { // Enter key
exising = true if (key === "\r" || key === "\n") {
} // Move to a new line
console.log()
return exising // Process the command
}) const command = this.inputBuffer.trim()
if (command) {
this.processCommand(command)
}
if (!command_fn) { // Clear the buffer
return callback(`Command not found: ${command}`) this.inputBuffer = ""
}
return command_fn.fn(callback, ...args) // Show the prompt again
} this.showPrompt()
}) return
} }
commands = [ // Backspace/Delete
{ if (key === "\b" || key === "\x7f") {
cmd: "select", if (this.inputBuffer.length > 0) {
aliases: ["s", "sel"], // Delete a character from the buffer
fn: (cb, service) => { this.inputBuffer = this.inputBuffer.slice(0, -1)
this.handlers.detachAllServicesSTD()
return this.handlers.attachServiceSTD(service) // Update the line in the terminal
} process.stdout.write("\r\x1b[K> " + this.inputBuffer)
}, }
{ return
cmd: "reload", }
aliases: ["r"],
fn: () => { // Normal characters
this.handlers.reloadService() if (key.length === 1 && key >= " ") {
} this.inputBuffer += key
}, process.stdout.write(key)
{ }
cmd: "exit", })
aliases: ["e"],
fn: () => { // Intercept console.log to keep the prompt visible
process.exit(0) const originalConsoleLog = console.log
} console.log = (...args) => {
} // Clear the current line
] process.stdout.write("\r\x1b[K")
}
// Print the message
originalConsoleLog(...args)
// Reprint the prompt and current buffer
this.showPrompt(false)
}
// Do the same with console.error
const originalConsoleError = console.error
console.error = (...args) => {
// Clear the current line
process.stdout.write("\r\x1b[K")
// Print the error message
originalConsoleError(...args)
// Reprint the prompt and current buffer
this.showPrompt(false)
}
}
showPrompt(newLine = true) {
if (newLine) {
process.stdout.write("\r")
}
process.stdout.write("> " + this.inputBuffer)
}
processCommand(input) {
const inputs = input.split(" ")
const command = inputs[0]
const args = inputs.slice(1)
this.inputBuffer = ""
const commandFn = this.commands.find((relpCommand) => {
if (relpCommand.cmd === command) {
return true
}
if (Array.isArray(relpCommand.aliases)) {
return relpCommand.aliases.includes(command)
}
return false
})
if (!commandFn) {
console.error(`Command not found: ${command}`)
return
}
// Adapter to maintain compatibility with the original API
const callback = (result) => {
if (result) {
console.log(result)
}
}
try {
commandFn.fn(callback, ...args)
} catch (error) {
console.error(`Error executing command: ${error.message}`)
}
}
commands = [
{
cmd: "select",
aliases: ["s", "sel"],
fn: (cb, service) => {
this.handlers.detachAllServicesSTD()
return this.handlers.attachServiceSTD(service)
},
},
{
cmd: "reload",
aliases: ["r"],
fn: () => {
this.handlers.reloadService()
},
},
{
cmd: "exit",
aliases: ["e"],
fn: () => {
process.exit(0)
},
},
]
}

View File

@ -51,6 +51,7 @@ export default class ServiceManager {
} }
const service = this.getService(id) const service = this.getService(id)
if (!service) { if (!service) {
console.error(`Service [${id}] not found`) console.error(`Service [${id}] not found`)
return false return false
@ -64,8 +65,6 @@ export default class ServiceManager {
* Reload all services * Reload all services
*/ */
reloadAllServices() { reloadAllServices() {
console.log("Reloading all services...")
for (const service of this.services) { for (const service of this.services) {
service.reload() service.reload()
} }
@ -75,8 +74,6 @@ export default class ServiceManager {
* Stop all services * Stop all services
*/ */
stopAllServices() { stopAllServices() {
console.log("Stopping all services...")
for (const service of this.services) { for (const service of this.services) {
service.stop() service.stop()
} }
@ -88,8 +85,6 @@ export default class ServiceManager {
* @returns {boolean} True if attachment was successful * @returns {boolean} True if attachment was successful
*/ */
attachServiceStd(id) { attachServiceStd(id) {
console.log(`Attaching to service [${id}]`)
if (id === "all") { if (id === "all") {
this.selectedService = "all" this.selectedService = "all"
this.attachAllServicesStd() this.attachAllServicesStd()

View File

@ -51,6 +51,8 @@ export default class Service {
* Start the service process * Start the service process
*/ */
async startProcess() { async startProcess() {
console.log(`🔰 [${this.id}] Starting service...`)
this.instance = await spawnService({ this.instance = await spawnService({
id: this.id, id: this.id,
service: this.path, service: this.path,
@ -60,7 +62,9 @@ export default class Service {
onIPCData: this.handleIPCData.bind(this), onIPCData: this.handleIPCData.bind(this),
}) })
// if debug flag is enabled, attach logs on start
this.instance.logs.attach() this.instance.logs.attach()
return this.instance return this.instance
} }

View File

@ -23,19 +23,16 @@ export default async ({ id, service, cwd, onClose, onError, onIPCData }) => {
instance.logs = { instance.logs = {
stdout: createServiceLogTransformer({ id }), stdout: createServiceLogTransformer({ id }),
stderr: createServiceLogTransformer({ id, color: "bgRed" }), stderr: createServiceLogTransformer({ id, color: "bgRed" }),
attach: () => { attach: (withBuffer = false) => {
instance.logs.stdout.pipe(process.stdout) instance.logs.stdout.pipe(process.stdout)
instance.logs.stderr.pipe(process.stderr) instance.logs.stderr.pipe(process.stderr)
}, },
detach: () => { detach: (withBuffer = false) => {
instance.logs.stdout.unpipe(process.stdout) instance.logs.stdout.unpipe(process.stdout)
instance.logs.stderr.unpipe(process.stderr) instance.logs.stderr.unpipe(process.stderr)
}, },
} }
instance.logs.stdout.history = []
instance.logs.stderr.history = []
// push to buffer history // push to buffer history
instance.stdout.pipe(instance.logs.stdout) instance.stdout.pipe(instance.logs.stdout)
instance.stderr.pipe(instance.logs.stderr) instance.stderr.pipe(instance.logs.stderr)