| const express = require("express"); |
| const { createProxyMiddleware } = require("http-proxy-middleware"); |
| const { spawn } = require("child_process"); |
|
|
| const app = express(); |
|
|
| |
| |
| |
| |
| |
| |
| function stripWsCompression(proxyReq) { |
| proxyReq.removeHeader("sec-websocket-extensions"); |
| } |
|
|
| |
| app.use("/eth", createProxyMiddleware({ |
| target: "http://127.0.0.1:8545", |
| changeOrigin: true, |
| ws: true, |
| pathRewrite: { "^/eth": "" }, |
| on: { proxyReqWs: stripWsCompression } |
| })); |
|
|
| |
| app.use("/rootstock", createProxyMiddleware({ |
| target: "http://127.0.0.1:8546", |
| changeOrigin: true, |
| ws: true, |
| pathRewrite: { "^/rootstock": "" }, |
| on: { proxyReqWs: stripWsCompression } |
| })); |
|
|
| |
| |
| app.use("/solana", createProxyMiddleware({ |
| target: "http://127.0.0.1:8899", |
| changeOrigin: true, |
| ws: true, |
| pathRewrite: { "^/solana": "" }, |
| router: (req) => ( |
| req.headers.upgrade && req.headers.upgrade.toLowerCase() === "websocket" |
| ? "http://127.0.0.1:8900" |
| : "http://127.0.0.1:8899" |
| ), |
| on: { proxyReqWs: stripWsCompression } |
| })); |
|
|
| |
| const LOG_FILES = { |
| eth: "/data/logs/eth.log", |
| rootstock: "/data/logs/rootstock.log", |
| solana: "/data/logs/solana.log" |
| }; |
|
|
| app.get("/logs", (req, res) => { |
| const links = Object.keys(LOG_FILES) |
| .map((chain) => `<li> |
| <a href="/logs/${chain}">${chain} (last 200 lines)</a> | |
| <a href="/logs/${chain}?follow=1">${chain} (live)</a> |
| </li>`) |
| .join("\n"); |
| res.type("html").send(`<h1>DLT chain logs</h1><ul>${links}</ul>`); |
| }); |
|
|
| app.get("/logs/:chain", (req, res) => { |
| const logFile = LOG_FILES[req.params.chain]; |
| if (!logFile) { |
| return res.status(404).send(`Unknown chain "${req.params.chain}". Valid: ${Object.keys(LOG_FILES).join(", ")}`); |
| } |
|
|
| const follow = req.query.follow === "1"; |
| const lines = Math.max(1, Math.min(parseInt(req.query.tail, 10) || 200, 5000)); |
| const tailArgs = follow ? ["-F", "-n", String(lines), logFile] : ["-n", String(lines), logFile]; |
|
|
| const child = spawn("tail", tailArgs, { stdio: ["ignore", "pipe", "pipe"] }); |
|
|
| res.setHeader("Content-Type", "text/plain; charset=utf-8"); |
| res.setHeader("Cache-Control", "no-cache"); |
| res.setHeader("X-Accel-Buffering", "no"); |
| res.flushHeaders(); |
|
|
| child.stdout.pipe(res, { end: false }); |
| child.stderr.pipe(res, { end: false }); |
|
|
| let heartbeat = null; |
| let maxDuration = null; |
|
|
| if (follow) { |
| heartbeat = setInterval(() => { |
| res.write(`\n# --- heartbeat ${new Date().toISOString()} ---\n`); |
| }, 15000); |
|
|
| |
| |
| maxDuration = setTimeout(() => { |
| res.end("\n# --- session limit reached, reconnect to keep following ---\n"); |
| cleanup(); |
| }, 45 * 60 * 1000); |
| } else { |
| |
| child.on("close", () => res.end()); |
| } |
|
|
| function cleanup() { |
| if (heartbeat) clearInterval(heartbeat); |
| if (maxDuration) clearTimeout(maxDuration); |
| if (!child.killed) child.kill("SIGTERM"); |
| } |
|
|
| req.on("close", cleanup); |
| res.on("close", cleanup); |
| child.on("error", (err) => { |
| if (!res.headersSent) res.status(500); |
| res.end(`\n# error running tail: ${err.message}\n`); |
| cleanup(); |
| }); |
| }); |
|
|
| app.get("/", (req, res) => { |
| res.send("DLT multi-chain RPC gateway running"); |
| }); |
|
|
| app.listen(7860, "0.0.0.0"); |