背景:
在实际开发工作中,本地开发调试对跨域问题一般都采用 nginx+hosts 反向代理解决,又或者通过 devServer 正向代理解决。 本文章通过原理的方式去讲解怎么实现反向代理功能,以及实现更多其他功能。
授之以渔地理解原理,拓展更多思维去自己实现更多功能~
要求阅读者具备 HTTP/HTTPS 的基础
网络代理
代理就是中间人,一人分饰两角色:客户端眼中的目标服务器,目标服务器眼中的客户端。
如果它只是简单的转发两端的数据,那么它就是透明代理,一旦它对任意一方的流量数据进行了解析或者修改,那就是非透明代理。
如果它是面向客户端,为客户端服务的,就是正向代理。如果它是面向服务器,为服务器服务的,就是反向代理。
透明代理
透明代理完全透传数据,不对数据做任何篡改
非透明代理
非透明代理可能会对数据进行篡改后,在发送给客户端或者目标服务器
正向代理
正向代理面向的是用户,即客户端,用户对外访问网络,都会先经过代理服务器,然后由代理服务器访问真实目标服务器。如科学上网、VPN
反向代理
反向代理面向的是服务器,用户访问到代理服务器后,由代理分发到真实目标服务器,如 nginx 网关 通过代理服务器分发内容到服务器,避免直接访问服务器,起到安全作用,也可以配置负载均衡
hosts的作用
可用记事本/代码编辑器 打开编辑保存。
hosts 文件用于记录 域名和 IP 的映射关系。
举个例子:当我们在浏览器中输入域名,计算机就能优先从 hosts 文件中解析出 IP 地址,而不用请求网络上的 DNS 服务器。 可用于网络加速和转发
网络代理劫持实现
前文提到,网络代理就是一人分饰两角色:客户端眼中的目标服务器,目标服务器眼中的客户端。所以我们要实现一个代理就要实现服务器和客户端的功能。
HTTP 代理拦截
import http from 'http'
http.createServer(8080) // 该代理监听8080端口
// 当有代理有接收到http请求时,触发该事件
.on('request', (clientToProxyRequest, proxyToClientResponse) => {
// 请求的url
const url = new URL(clientToProxyRequest.url)
// 请求远程服务器
const proxyToServerRequest = http.request({
hostname: url.hostname,
port: url.port,
path: url.path,
method: clientToProxyRequest.method,
headers: clientToProxyRequest.headers,
}, serverToProxyResponse => {
// 把远程服务器返回的状态码,headers原样返回到客户端
proxyToClientResponse.writeHead(serverToProxyResponse.statusCode, serverToProxyResponse.headers)
// 把远程服务器返回的body数据,原样返回到客户端
serverToProxyResponse.pipe(proxyToClientResponse)
})
// 修改客户端的body,原样塞到请求远程服务器
clientToProxyRequest.pipe(proxyToServerRequest)
})
在这个例子可以发现,如果我这个代理要对请求做任何处理,都是非常简单的,可以轻易的改写 header,path,body,甚至是远程服务器的地址,都可以随意的篡改
但目前这个代理是不能代理 HTTPS 请求的。
HTTPS 代理拦截
回顾一下 HTTPS 证书的验证过程,需要验证整个证书链是否可信 和 HTTPS 证书的域名是否匹配。
当 HTTPS 请求经过了我们的代理,就根据请求的域名,使用我们自签发的根证书去动态颁发一张符合该域名的 HTTPS 证书,用这个动态生成的证书与客户端协商,就能拿到客户端加密 HTTPS 数据包用的 AES 加密秘钥了。 如何协商,如何解密,这个流程其实可以不用关心,这是 TLS 协议标准,在标准库都实现了,我们可直接使用 https 模块实现。
import http from 'http'
import https from 'https'
import tls from 'tls'
import net from 'net'
// 创建一个假冒的 https 服务器,会根据请求的域名动态生成 https 证书
const fakeHttpsServer = new https.Server({
SNICallback(hostname, done) {
// hostname 是请求的目标服务器域名
//根据它动态生成一张 HTTPS证书 certMgr是一个自定义函数,通过脚本去生成证书
const httpsDomainCert = certMgr.generateHttpsCertByDomain(hostname);
done(null, tls.createSecureContext{
key: httpsDomainCert.key,
cert: httpsDomainCert.cert,
})
}
})
.on('request', (clientToProxyRequest, proxyToClientResponse) => {
// 这里可以获取到 https 请求的所有数据了,请求目标服务器,把目标服务器返回的数据返回给客户端,和 http 代理一样操作
// ...
})
.listen()
应用场景
那么应用场景就很多了。 通过 hosts 指向本地创建的代理服务器,在自己的服务器中为所欲为,就可以做到很多的事情:
- 转发功能。
- 数据抓包,打印记录数据。
- 加解密数据,对数据源进行编码后返回。
- 解决跨域问题。
- 更改请求头。