进阶-HTTP代理

提到http代理, 一般意义上理解的有很多种, 比较常见的理解是: - 对外发起http请求时, 使用ip代理 - 类似nginx或CDN服务的反向代理 - 建立一个代理服务, 让浏览器或其它设备可以通过该服务请求外界服务 惊喜的是, 以上3种不同场景的"代理", Z1h都提供了相关支持

对外请求使用IP代理

这一技术多用于爬虫, 例如待爬取的站点的某接口限制了同一IP的请求频率(x分钟内不能超过x次等), 就需要将自己的http请求通过别人提供的代理服务器来发送

使用方法

在调用几乎所有可以发起http的非原生方法时, 如果header包含_proxy_, 就等于指定了代理的地址

示例代码

// 普通http请求, 获取ip print(`https://zwei.ren/ip`.get().s) // 通过代理获取ip print(`https://zwei.ren/ip`.get({ _proxy_: 'http://xxx.xxx.xxx.xxx:xxxx', _timeout_: 1, // BTW: 可以通过这种方式设置超时时间 }).s) // 同理, post请求往第二个参数设置代理 print(`https://zwei.ren/ip`.post('', { _proxy_: 'http://xxx.xxx.xxx.xxx:xxxx', }).s)

反向代理

反向代理的用途很广, 例如以下场景: - 日常开发工作中, 前端需要和后端联调时, 后端还没有开发好接口, 或者需要修改接口但还没完成 - 多人并行开发服务端时, 会出现抢占测试环境的情况, 需要针对一些路由或逻辑将端口转发到不同的内网地址 - 需要将一个站点的各个文件完整保存下来 此时, 通过Z1h开启一个http服务, 并且将所有http请求都转发到某个地址, 即可完成反向代理

步骤1: 修改配置

首先要开启http服务, 并且修改api相关配置: - 修改配置文件 - 设置port, 让Z1h以http服务的形式运行 - 设置noFronttrue, 不对外输出静态站点资源 - 设置serverless.api.router/, 意思是使用Z1h来处理全部接口 - 设置serverless.api.dir, 例如设置到 datas/api, 接口将由datas/api/xxx.z1h处理 以上步骤完成后, 最简单的配置示例如下: { port: 30030, // 运行端口号 serverless: { api: { router: "/", dir: "datas/api", } }, noFront: true, } 现在, 可以用命令 ./z1h -conf conf.json运行Z1h服务

步骤2: 处理请求

接着创建接口处理文件: - 创建datas/api/404.z1h文件, 指定了无法找到请求接口文件时的默认处理 - 如果你仅需要代理某路由, 可以指定文件名为路由, 如 api.z1h - 编辑404.z1h文件, 写入$http.proxy(this, 'http://xxx'), 其中xxx为你需要转发请求到的源站地址 Tips: 可以把xxx替换成https://www.baidu.com试试

步骤3: 测试

打开浏览器, 输入 http://127.0.0.1:30030, 即可看到反向代理后的结果(某些站点可能会让浏览器重定向, 可以用curl来请求接口测试)

中间人读取/修改双向数据

编辑404.z1h: $http.proxy(this, 'http://xxx', (data, action) => { // action为request或response switch (action) { case 'request': // action为request时, data包含以下字段: // Url 请求地址 // Method 请求方法 // Header 请求头 // Body 请求体([]byte) // Request 原生请求对象 // Client 原生请求客户 print(`请求到${data.url}, 方法${data.method}, 头部${data.header}, 内容${data.body.s}`) // 可以随意篡改请求内容 data.Header.Set('X-HOOK-REQ', 'Z1h') // data.Method = 'POST' // data.Body = bytearray({name: 'zwr'}) // 返回值不同类型时, 情况如下: // return null/true, 表示继续原来的请求 // return false, 表示拦截请求, 即刻返回该内容 // return *net_http.Response, 拦截请求, 返回指定的状态码等 // 其它情况: 拦截请求, 立刻返回该内容 return null // 可以试试 return {message: 'Forbidden'} case 'response': // action为request时, data为*net_http.Response对象 print(`请求到${data.request.url.string()}的结果, 状态码${data.statusCode}`) // 可以修改响应信息 data.Header.Set('X-HOOK-RSP', 'Z1h') if (data.statusCode == 301 || data.statusCode == 302) return `源站企图把你重定向到 ${data.header.get('Location')}, 已经被阻止了` // 返回值不同类型时, 情况与上文request相同, 区别是此时请求已经由源站处理过了 return null // 可以试试 return {message: 'Done'} } }) // 只拦截请求 $http.proxy(this, 'http://xxx', { request: data => { // TODO }, }) // 只拦截响应 $http.proxy(this, 'http://xxx', { response: data => { // TODO }, })

自制简单的CDN

照样是在前文的基础上编辑: var host = 'http://xxx' var dir = 'cdn'.mkdir() // 使用一个文件来保存 $http.proxy(this, host, { request: data => { print(`Req to:`, data.url) var path = data.url.url().path if (!path || path == "/") path = "index.html" var cacheFile = $file.join(dir, path) // 会忽略掉请求参数,建议读者动手尝试一下根据参数来保存和读取 if ($file.exists(cacheFile) == 1) { // 如果文件已经存在, 响应文件并拦截后续请求 print(`文件${path}使用本地文件`) data.request.serveFile(cacheFile) return false } }, res: res => { if (res.statusCode < 200 || res.statusCode > 399) { print(`请求${res.Request.URL.String()}的状态码为${res.statusCode}, 不保存文件`) return true } print(`正在补充${res.Request.URL.String()}的文件`) var body = res.origin() if (body.len) { // 进行一些内容上的处理, 确保浏览器能正常打开 body = body .replace('https://', 'http://') .replace('https', 'http') .replace(host + '/{0,}', "/") // 保存到本地文件 var path = res.request.url.path if (!path || path == "/") path = "index.html" var cacheFile = $file.join(dir, path) // 会忽略掉请求参数,建议读者动手尝试一下根据参数来保存 path_filepath.Dir(cacheFile).mkdir() cacheFile.write(body) // 下面要去掉encoding, 如果gzip了的话, Content-Length会有变化 res.header.Set('Content-Length', body.len.s) res.body = reader(body) } else { res.Body = reader('') } res.Header.Del('Content-Encoding') }, })

建立一个HTTP代理并进行MITM

这是一个很强大的功能, 如果你有Charles、Flidder、Wireshark等抓包工具的使用经验, 甚至有尝试过嵌入脚本修改请求、返回数据的话, 那么你一定非常清楚这个工具的便利 通过Z1h建立HTTP代理服务, 你可以做到(且不限于)以下事情: - 抓包, 和其它抓包工具一致 - 数据处理, 例如将所有数据都保存、分析 - 监听, 可以监控敏感站点、过滤广告、过滤(或收集)不良信息等 - 篡改请求数据, 或拦截请求mock数据返回 - 篡改响应数据, 或拦截响应 - 支持http和https, https需要自己创建证书 暂不支持websocket的代理, 后续会跟进 文档待续