进阶-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服务的形式运行 - 设置
noFront
为true
, 不对外输出静态站点资源 - 设置
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的代理, 后续会跟进
文档待续