2024-11-17 20:35:20 +00:00

212 lines
5.5 KiB
JavaScript

import httpProxy from "http-proxy"
import defaults from "linebridge/dist/defaults"
import pkg from "../package.json"
import http from "node:http"
import https from "node:https"
import fs from "node:fs"
import path from "node:path"
function getHttpServerEngine(extraOptions = {}, handler = () => { }) {
const sslKey = path.resolve(process.cwd(), ".ssl", "privkey.pem")
const sslCert = path.resolve(process.cwd(), ".ssl", "cert.pem")
if (fs.existsSync(sslKey) && fs.existsSync(sslCert)) {
return https.createServer(
{
key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert),
...extraOptions
},
handler
)
} else {
return http.createServer(extraOptions, handler)
}
}
export default class Proxy {
constructor() {
this.proxys = new Map()
this.wsProxys = new Map()
this.http = getHttpServerEngine({}, this.handleHttpRequest)
this.http.on("upgrade", this.handleHttpUpgrade)
}
http = null
register = ({ serviceId, path, target, pathRewrite, ws } = {}) => {
if (!path) {
throw new Error("Path is required")
}
if (!target) {
throw new Error("Target is required")
}
if (this.proxys.has(path)) {
console.warn(`Proxy already registered [${path}], skipping...`)
return false
}
const proxy = httpProxy.createProxyServer({
target: target,
})
proxy.on("proxyReq", (proxyReq, req, res, options) => {
proxyReq.setHeader("x-linebridge-version", pkg.version)
proxyReq.setHeader("x-forwarded-for", req.socket.remoteAddress)
})
proxy.on("error", (e) => {
console.error(e)
})
const proxyObj = {
serviceId: serviceId ?? "default_service",
path: path,
target: target,
pathRewrite: pathRewrite,
proxy: proxy,
}
if (ws) {
console.log(`🔗 Registering websocket proxy [${path}] -> [${target}]`)
this.wsProxys.set(path, proxyObj)
} else {
console.log(`🔗 Registering path proxy [${path}] -> [${target}]`)
this.proxys.set(path, proxyObj)
}
return true
}
unregister = (path) => {
if (!this.proxys.has(path)) {
console.warn(`Proxy not registered [${path}], skipping...`)
return false
}
console.log(`🔗 Unregistering path proxy [${path}]`)
this.proxys.get(path).proxy.close()
this.proxys.delete(path)
}
unregisterAllFromService = (serviceId) => {
this.proxys.forEach((value, key) => {
if (value.serviceId === serviceId) {
this.unregister(value.path)
}
})
}
listen = async (port = 9000, host = "0.0.0.0", cb) => {
return await new Promise((resolve, reject) => {
this.http.listen(port, host, () => {
console.log(`🔗 Proxy listening on ${host}:${port}`)
if (cb) {
cb(this)
}
resolve(this)
})
})
}
rewritePath = (rewriteConfig, path) => {
let result = path
const rules = []
for (const [key, value] of Object.entries(rewriteConfig)) {
rules.push({
regex: new RegExp(key),
value: value,
})
}
for (const rule of rules) {
if (rule.regex.test(path)) {
result = result.replace(rule.regex, rule.value)
break
}
}
return result
}
setCorsHeaders = (res) => {
res.setHeader("Access-Control-Allow-Origin", "*")
res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.setHeader("Access-Control-Allow-Headers", "*")
return res
}
handleHttpRequest = (req, res) => {
res = this.setCorsHeaders(res)
const sanitizedUrl = req.url.split("?")[0]
// preflight continue with code 204
if (req.method === "OPTIONS") {
res.statusCode = 204
res.end()
return
}
if (sanitizedUrl === "/") {
return res.end(JSON.stringify({
name: pkg.name,
version: pkg.version,
lb_version: defaults.version
}))
}
const namespace = `/${sanitizedUrl.split("/")[1]}`
const route = this.proxys.get(namespace)
if (!route) {
res.statusCode = 404
res.end(JSON.stringify({
error: "Gateway route not found",
details: "The gateway route you are trying to access does not exist, maybe the service is down...",
namespace: namespace,
}))
return null
}
if (route.pathRewrite) {
req.url = this.rewritePath(route.pathRewrite, req.url)
}
route.proxy.web(req, res)
}
handleHttpUpgrade = (req, socket, head) => {
const namespace = `/${req.url.split("/")[1]}`
const route = this.wsProxys.get(namespace)
if (!route) {
// destroy socket
socket.destroy()
return false
}
if (route.pathRewrite) {
req.url = this.rewritePath(route.pathRewrite, req.url)
}
route.proxy.ws(req, socket, head)
}
close = () => {
this.http.close()
}
}