更新时间:2023-11-30 11:03:31
本章示例为通过【边缘函数】实现站点代理。
代码说明:支持反向代理某个站点(以某网站为例)。其中,【边缘函数】中涉及到了多种改写操作,包括:请求URL、请求头、响应头、响应包体等。
详细代码
const UPSTREAM = "www.douban.com"
const PATH_PREFIX = "/douban-proxy"
const RW_START = "/--rw--"
const RW_STOP = "--wr--"
let g_origin_url
let g_path_pfrefix
async function handleRequest(request) {
g_origin_url = new URL(request.url)
if (request.method == "POST") {
let body = await request.text()
console.log("post body", request.url, "body", body)
}
const {urlString, pathPrefix} = restoreURL(request.url)
g_path_pfrefix = pathPrefix
const url = new URL(urlString)
console.log("request.url", request.url)
console.log("restore url", urlString)
// return new Response("xx1:" + request.url + "\nxx2:" + urlString)
const newRequest = new Request(urlString, request)
const headers = newRequest.headers
headers.set("Host", url.host)
headers.delete("Accept-Encoding")
const referer = headers.get("Referer")
if (referer) {
const {urlString} = restoreURL(referer)
// console.log('referer', urlString)
headers.set("Referer", urlString)
}
const origin = headers.get("Origin")
if (origin) {
const {urlString} = restoreURL(origin)
// console.log('origin', urlString)
headers.set("Origin", urlString)
}
const response = await fetch(newRequest, {cdnProxy: false, redirect: "manual"})
const responseHeaders = response.headers
let cookie = responseHeaders.get("Set-Cookie")
if (cookie) {
cookie = cookie.replace(/(domain=)([^;]+);/gi, '$1'+g_origin_url.host+';')
responseHeaders.set("Set-Cookie", cookie)
}
const contentType = responseHeaders.get("Content-Type")
if (contentType.includes("text/html")) {
return new HTMLRewriter()
.on("a", new URLHandler(["href"]))
.on("link", new URLHandler(["href"]))
.on("script", new URLHandler(["src"]))
.on("iframe", new URLHandler(["src"]))
.on("div", new URLHandler(["style"]))
.on("img", new URLHandler(["src", "data-origin"]))
.transform(response)
} else if (contentType.includes("text/css")) {
let text = await response.text()
text = rewriteText(text, /url\((.*?)\)/g)
return new Response(text, response)
} else if (contentType.includes("application/x-javascript")) {
let text = await response.text()
text = rewriteText(text, /"https?:(\/\/.*?)"/gi)
return new Response(text, response)
} else if (url.pathname.endsWith("login/qrlogin_code")) {
let json = await response.json()
json["payload"]["img"] = rewriteURL(json["payload"]["img"])
return new Response(JSON.stringify(json, null, 2), response)
} else {
return response
}
}
function rewriteText(text, reg) {
let result = text.replace(reg, function(match, str){
let result = match.replace(str, rewriteURL(str));
result = result.replace("https", "http")
return result
});
// if (text != result) {
// console.log("text", text, result)
// }
return result
}
class URLHandler {
constructor(attrs) {
this.attrs = attrs
}
text(text) {
let result = rewriteText(text.text, /':?(\/\/.*?)'/g)
result = rewriteText(result, /'https?:(\/\/.*?)'/gi)
if (result != text.text) {
text.replace(result)
}
}
element(element) {
for (let attr of this.attrs) {
const href1 = element.getAttribute(attr)
if (!href1) return
let href2
if (attr == "style") {
href2 = rewriteText(href1, /url\((.*?)\)/g)
} else {
href2 = rewriteURL(href1)
}
// console.log('rewrite', href1, href2)
element.setAttribute(attr, href2)
}
}
}
function rewriteURL(originURL) {
if (!originURL.startsWith("/") && !originURL.startsWith("http")) {
return originURL
}
if (originURL.startsWith("https://")) {
originURL = originURL.replace("https://", "http://")
}
originURL = originURL.replace(///g, "/")
let fullURL = originURL
if (originURL.startsWith("//")) {
fullURL = "http:" + originURL
} else if (originURL.startsWith("/")) {
return g_path_pfrefix + originURL
}
try {
const url = new URL(fullURL)
let host = ''
if (url.host != UPSTREAM) {
host = `${RW_START}${url.host.replace(/\./g, "---")}${RW_STOP}`
}
const rw = `${g_origin_url.host}${PATH_PREFIX}${host}`
return originURL.replace(url.host, rw)
} catch (e) {
console.error("rewriter error", e, originURL)
return originURL
}
}
function restoreURL(rewritedURL) {
if (rewritedURL.endsWith(PATH_PREFIX) || rewritedURL.endsWith(RW_STOP)) {
rewritedURL += "/"
}
const url = new URL(rewritedURL)
let pathname = url.pathname
let pathPrefix, host
if (pathname.startsWith(PATH_PREFIX)) {
pathname = pathname.substring(PATH_PREFIX.length)
}
if (pathname.startsWith(RW_START) && pathname.includes(RW_STOP)) {
const stop = pathname.indexOf(RW_STOP)
pathPrefix = PATH_PREFIX + pathname.substring(0, stop + RW_STOP.length)
host = pathname.substring(RW_START.length, stop).replace(/---/g, ".")
} else {
host = UPSTREAM
pathPrefix = PATH_PREFIX
}
return {urlString: rewritedURL.replace(url.protocol, "https:").replace(url.host, host).replace(pathPrefix, ''),
pathPrefix: pathPrefix}
}
addEventListener("fetch", event => {
return event.respondWith(handleRequest(event.request))
})
注:该案例,提供了多维度的改写代码逻辑,并展示了流式和非流式改写方式的结合。