|
| 1 | +var express = require('express'); |
| 2 | +var proxy = require('http-proxy-middleware'); |
| 3 | +const https = require('https') |
| 4 | +const zlib = require("zlib") |
| 5 | +const cookiejar = require('cookiejar') |
| 6 | +const iconv = require('iconv-lite') |
| 7 | +const {CookieAccessInfo, CookieJar, Cookie} = cookiejar |
| 8 | + |
| 9 | +function countUtf8Bytes(s) { |
| 10 | + var b = 0, i = 0, c |
| 11 | + for(;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1); |
| 12 | + return b |
| 13 | +} |
| 14 | + |
| 15 | +var enableCors = function(req, res) { |
| 16 | + if (req.headers['access-control-request-method']) { |
| 17 | + res.setHeader('access-control-allow-methods', req.headers['access-control-request-method']); |
| 18 | + } |
| 19 | + |
| 20 | + if (req.headers['access-control-request-headers']) { |
| 21 | + res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']); |
| 22 | + } |
| 23 | + |
| 24 | + if (req.headers.origin) { |
| 25 | + res.setHeader('access-control-allow-origin', req.headers.origin); |
| 26 | + res.setHeader('access-control-allow-credentials', 'true'); |
| 27 | + } |
| 28 | +}; |
| 29 | + |
| 30 | +// only support https for now. |
| 31 | +let router = (req) => { //return target |
| 32 | + let {host, httpType} = getHostFromReq(req) |
| 33 | + let target = `${httpType}://${host}` |
| 34 | + console.log(`router, target:${target}, req.url:${req.url}`) |
| 35 | + return target |
| 36 | +} |
| 37 | + |
| 38 | +let getHostFromReq = (req) => { //return target |
| 39 | + // url: http://127.0.0.1:8011/https/www.youtube.com/xxx/xxx/... |
| 40 | + let https_prefix = '/https/' |
| 41 | + let http_prefix = '/http/' |
| 42 | + let host = '' |
| 43 | + let httpType = 'https' |
| 44 | + if (req.url.startsWith(https_prefix)) { |
| 45 | + host = req.url.slice(https_prefix.length, req.url.length) |
| 46 | + if (host.indexOf('/') !== -1) { |
| 47 | + host = host.slice(0, host.indexOf('/')) |
| 48 | + } |
| 49 | + } else if (req.url.startsWith(http_prefix)) { |
| 50 | + host = req.url.slice(http_prefix.length, req.url.length) |
| 51 | + if (host.indexOf('/') !== -1) { |
| 52 | + host = host.slice(0, host.indexOf('/')) |
| 53 | + } |
| 54 | + httpType = 'http' |
| 55 | + } else if (req.headers['referer'] && req.headers['referer'].indexOf('https/') !== -1) { |
| 56 | + let start = req.headers['referer'].indexOf('https/') + 6 |
| 57 | + host = req.headers['referer'].slice(start, req.headers['referer'].length) |
| 58 | + let end = host.indexOf('/') |
| 59 | + if (end === -1) { |
| 60 | + end = host.length |
| 61 | + } |
| 62 | + host = host.slice(0, end) |
| 63 | + console.log(`============= host:${host}, req referer:${req.headers['referer']}`) |
| 64 | + } else if (req.headers['referer'] && req.headers['referer'].indexOf('http/') !== -1) { |
| 65 | + let start = req.headers['referer'].indexOf('http/') + 5 |
| 66 | + host = req.headers['referer'].slice(start, req.headers['referer'].length) |
| 67 | + let end = host.indexOf('/') |
| 68 | + if (end === -1) { |
| 69 | + end = host.length |
| 70 | + } |
| 71 | + host = host.slice(0, end) |
| 72 | + httpType = 'http' |
| 73 | + console.log(`============= host:${host}, req referer:${req.headers['referer']}`) |
| 74 | + } |
| 75 | + console.log(`getHostFromReq, req.url:${req.url}, referer:${req.headers['referer']}`) |
| 76 | + return {host, httpType} |
| 77 | +} |
| 78 | + |
| 79 | + |
| 80 | +let Proxy = ({cookieDomainRewrite, locationReplaceMap302, regReplaceMap, siteSpecificReplace, pathReplace}) => { |
| 81 | + let handleRespond = ({req, res, body, gbFlag}) => { |
| 82 | + // console.log("res from proxied server:", body); |
| 83 | + /* |
| 84 | + var myRe = new RegExp(`https://`, 'g') // support maximum 300 characters for web site name, like: www.google.com |
| 85 | + body = body.replace(myRe, `http://127.0.0.1:${port}/https/`) |
| 86 | + myRe = new RegExp(`https%3A%2F%2F`, 'g') |
| 87 | + body = body.replace(myRe, `https%3A%2F%2Fround-lake.dustinice.workers.dev%3A443%2Fhttps%2F127.0.0.1%3A${port}%2F`) |
| 88 | + */ |
| 89 | + let myRe |
| 90 | + let {host, httpType} = getHostFromReq(req) |
| 91 | + let location = res.getHeaders()['location'] |
| 92 | + if (res.statusCode == '302' || res.statusCode == '301') { |
| 93 | + for(key in locationReplaceMap302) { |
| 94 | + myRe = new RegExp(key, 'g') // match group |
| 95 | + location = location.replace(myRe, locationReplaceMap302[key]) |
| 96 | + } |
| 97 | + res.setHeader('location', location) |
| 98 | + } |
| 99 | + // console.log(`HandleRespond(), req.url:${req.url}, req.headers:${JSON.stringify(req.headers)}`) |
| 100 | + for(key in regReplaceMap) { |
| 101 | + myRe = new RegExp(key, 'g') // match group |
| 102 | + body = body.replace(myRe, regReplaceMap[key]) |
| 103 | + } |
| 104 | + Object.keys(siteSpecificReplace).forEach( (site) => { |
| 105 | + if (req.url.indexOf(site) !== -1 || req.headers['referer'].indexOf(site) !== -1) { |
| 106 | + keys = Object.keys(siteSpecificReplace[site]) |
| 107 | + keys.forEach( key => { |
| 108 | + myRe = new RegExp(key, 'g') // match group |
| 109 | + body = body.replace(myRe, siteSpecificReplace[site][key]) |
| 110 | + }) |
| 111 | + } |
| 112 | + }) |
| 113 | + if (host) { |
| 114 | + body = pathReplace({host, httpType, body}) |
| 115 | + } |
| 116 | + |
| 117 | + if (gbFlag) { |
| 118 | + body = iconv.encode(body, 'gbk') |
| 119 | + } |
| 120 | + body = zlib.gzipSync(body) |
| 121 | + res.setHeader('content-encoding', 'gzip'); |
| 122 | + console.log(`handleRespond: res.headers:${JSON.stringify(res.getHeaders())}`) |
| 123 | + res.end(body); |
| 124 | + } |
| 125 | + let p = proxy({ |
| 126 | + target: `https://www.google.com`, |
| 127 | + router, |
| 128 | + /* |
| 129 | + pathRewrite: (path, req) => { |
| 130 | + let {host, httpType} = getHostFromReq(req) |
| 131 | + let newpath = path.replace(`/https/${host}`, '') || '/' |
| 132 | + console.log(`newpath:${newpath}`) |
| 133 | + return newpath |
| 134 | + }, |
| 135 | + */ |
| 136 | + hostRewrite: true, |
| 137 | + // autoRewrite: true, |
| 138 | + protocolRewrite: true, |
| 139 | + // followRedirects: true, |
| 140 | + cookieDomainRewrite, |
| 141 | + secure: false, |
| 142 | + changeOrigin: true, |
| 143 | + debug:true, |
| 144 | + onError: (err, req, res) => { |
| 145 | + console.log(`onerror: ${err}`) |
| 146 | + }, |
| 147 | + selfHandleResponse: true, // so that the onProxyRes takes care of sending the response |
| 148 | + onProxyRes: (proxyRes, req, res) => { |
| 149 | + let {host, httpType} = getHostFromReq(req) |
| 150 | + console.log(`proxyRes.status:${proxyRes.statusCode} proxyRes.headers:${JSON.stringify(proxyRes.headers)}`) |
| 151 | + var body = Buffer.from(''); |
| 152 | + proxyRes.on('data', function(data) { |
| 153 | + body = Buffer.concat([body, data]); |
| 154 | + }) |
| 155 | + proxyRes.on('end', function() { |
| 156 | + let gbFlag = false |
| 157 | + if (proxyRes.headers["content-encoding"] === 'gzip') { |
| 158 | + zlib.gunzip(body,function(er,gunzipped){ |
| 159 | + console.log(`zlib.gunzip...`) |
| 160 | + if (proxyRes.headers["content-type"].indexOf('text/') !== -1) { |
| 161 | + if (!gunzipped) { |
| 162 | + res.status(404).send() |
| 163 | + return |
| 164 | + } |
| 165 | + console.log(`utf-8 text...`) |
| 166 | + body = gunzipped.toString('utf-8'); |
| 167 | + if (body.indexOf('content="text/html; charset=gb2312') !== -1) { |
| 168 | + body = iconv.decode(gunzipped, 'gbk') |
| 169 | + gbFlag = true |
| 170 | + } |
| 171 | + handleRespond({req, res, body, gbFlag}) |
| 172 | + } else { |
| 173 | + res.end(body) |
| 174 | + } |
| 175 | + }); |
| 176 | + } else if (proxyRes.headers["content-type"] && proxyRes.headers["content-type"].indexOf('text/') !== -1) { |
| 177 | + console.log(`utf-8 text...`) |
| 178 | + body = body.toString('utf-8'); |
| 179 | + handleRespond({req, res, body, gbFlag}) |
| 180 | + } else { |
| 181 | + res.end(body) |
| 182 | + } |
| 183 | + }) |
| 184 | + const setCookieHeaders = proxyRes.headers['set-cookie'] || [] |
| 185 | + const modifiedSetCookieHeaders = setCookieHeaders |
| 186 | + .map(str => new cookiejar.Cookie(str)) |
| 187 | + .map(cookie => { |
| 188 | + console.log(`cookie:${JSON.stringify(cookie)}`) |
| 189 | + if (cookie.path && cookie.path[0] === '/') { |
| 190 | + cookie.domain = `127.0.0.1` |
| 191 | + cookie.path = `${req.url}` |
| 192 | + } |
| 193 | + cookie.secure = false |
| 194 | + return cookie |
| 195 | + }) |
| 196 | + .map(cookie => cookie.toString()) |
| 197 | + proxyRes.headers['set-cookie'] = modifiedSetCookieHeaders |
| 198 | + Object.keys(proxyRes.headers).forEach(function (key) { |
| 199 | + if (key === 'content-encoding' || |
| 200 | + (key === 'content-length' && proxyRes.headers["content-type"] && proxyRes.headers["content-type"].indexOf('text/') !== -1)) { |
| 201 | + console.log(`skip header:${key}`) |
| 202 | + return |
| 203 | + } |
| 204 | + // res.append(key, proxyRes.headers[key]); |
| 205 | + res.setHeader(key, proxyRes.headers[key]); |
| 206 | + }); |
| 207 | + res.statusCode = proxyRes.statusCode |
| 208 | + console.log(`res.status:${res.statusCode} res.url:${res.url}, res.headers:${JSON.stringify(res.getHeaders())}`) |
| 209 | + }, |
| 210 | + onProxyReq: (proxyReq, req, res) => { |
| 211 | + let {host, httpType} = getHostFromReq(req) |
| 212 | + req.headers['host'] = host |
| 213 | + req.headers['referer'] = host |
| 214 | + let newpath = req.url.replace(`/${httpType}/${host}`, '') || '/' |
| 215 | + console.log(`httpType:${httpType}, host:${host}, req.url:${req.url}, req.headers:${JSON.stringify(req.headers)}`) |
| 216 | + Object.keys(req.headers).forEach(function (key) { |
| 217 | + proxyReq.setHeader(key, req.headers[key]); |
| 218 | + }); |
| 219 | + proxyReq.setHeader('Accept-Encoding', 'gzip') |
| 220 | + proxyReq.setHeader('referer', host) |
| 221 | + proxyReq.path = newpath |
| 222 | + console.log(`req host:${host}, req.url:${req.url}, proxyReq.path:${proxyReq.path}, proxyReq.url:${proxyReq.url} proxyReq headers:${JSON.stringify(proxyReq.getHeaders())}`) |
| 223 | + if(host === '' || !host) { |
| 224 | + console.log(`------------------ sending status 404`) |
| 225 | + res.status(404).send() |
| 226 | + res.end() |
| 227 | + } |
| 228 | + |
| 229 | + }, |
| 230 | + }) |
| 231 | + return p |
| 232 | +} |
| 233 | + |
| 234 | +module.exports = Proxy |
0 commit comments