Web应用私有化,说白了就是,把别人的网站下载下来在自己服务器上部署。
在此之前,已经做过了这个:
这个本质上是,程序作为代理,缓存一切结果,以后再次通过此代理时就不回源了,以此做到私有化,如下图:

但是这个有一个致命的问题是,条件必须允许使用这个代理(拦截器)。
正常的浏览器没有这个东西,对于需要陌生的用户直接拿浏览器访问这个需求,这就做不到了。
解决问题
要解决这个问题,理论上很简单,如下图:

在浏览器里面,没有了可编程的拦截器,只能一个一个去处理JS和HTML标签咯,只要让它们原本指向原站的东西指向我们的网站就可以啦
(。◕∀◕。)
注入JS
给HTML加一个script标签就好啦,我做了这样的工程化:
// lib
export function FormatExecFunctionJs(...funcs: Function[]): string {
return funcs.map(f => {
return `(()=>{${f.toString()};${f.name}();})();`
}).join('\n')
}
// main
const hackFunctionsData = (this.cfg.hack_funcs ?? []).map(f => ({
name: `f.name-${crypto.createHash('md5').update(f.toString()).digest('hex').substring(0, 4)}.js`,
code: FormatExecFunctionJs(f),
}))
const hackFunctionsHtml = hackFunctionsData.reduce((html, f) => `${html}<script src="/${f.name}"></script>`, "")
for (const func of hackFunctionsData) {
app.get(`/${func.name}`, (_, resp) => {
resp
.type("application/javascript")
.send(func.code)
.end()
})
}上述代码将函数数组this.cfg.hack_funcs 挂载到http服务器的多个挂载点上,并计算需要往html中插的代码hackFunctionsHtml 。
在用户请求html文件时插到head里面就好啦~
if (meta.headers["content-type"].includes("text/html")) {
text = text.replace(/<head>/, (match) => {
return `${match}${hackFunctionsHtml}`
})
}直接将Nodejs服务器上的一个函数toString序列化给浏览器执行。
html和js执行时拦截
JS注入之后直接替换掉原本的fetch等网络请求函数,监听dom树变化,修改src是一个看似可以的方法。
但是实际测试发现这里很难实现,注入了甚至给Image的构造函数覆盖了,给src加了setter,依然会有大量漏掉的请求。
这样做并不可行,而且监听dom树不是很优雅,效率比较低。
执行前替换host
Js、Html、Json等文件里面会有写死的地址,延迟到执行的时候拦截不可行,那么就在之前处理呗。
虽然代码是别人的,但是服务器是我们的呀,于是有了下面的代码:
const scheme = req.protocol;
const host = req.get("host")
const removeHosts = new Set(this.cfg.remove_hosts ?? [])
(d, meta) => {
if (!isText(null, d)) {
return d;
}
let text = d.toString("utf-8");
// 替换域名
text = text.replace(/(https?:)?\/\/[a-zA-Z0-9\-\.]+(?::\d+)?/g, (match) => {
if (removeHosts.has(match)) {
if (match.startsWith("http")) {
return `${scheme}://${host}`
} else {
return `//${host}`
}
}
return match
})
//
return Buffer.from(text)
}上面的代码通过正则表达式匹配类似于http://aa.aa、//bb.cn 这样子的链接,然后查Set看看是不是要替换。
经测试,Set是必要的,不要替换每一个链接,否则会导致两个问题:
像XML开头总是会有一个链接,这个是不希望替换的
正则表达式会将疑似链接的JS代码换掉导致无法运行
最终效果
基于之前的页面容器化,先使用之前的容器化程序去加载页面,丰富数据库,差不多了之后,数据库交给本次的私有化服务器,即可快速高效完成页面私有化~
