正/反向代理+代理原理和应用

2,135 阅读4分钟

背景:

在实际开发工作中,本地开发调试对跨域问题一般都采用 nginx+hosts 反向代理解决,又或者通过 devServer 正向代理解决。 本文章通过原理的方式去讲解怎么实现反向代理功能,以及实现更多其他功能。

授之以渔地理解原理,拓展更多思维去自己实现更多功能~

要求阅读者具备 HTTP/HTTPS 的基础

网络代理

代理就是中间人,一人分饰两角色:客户端眼中的目标服务器,目标服务器眼中的客户端。

如果它只是简单的转发两端的数据,那么它就是透明代理,一旦它对任意一方的流量数据进行了解析或者修改,那就是非透明代理。

如果它是面向客户端,为客户端服务的,就是正向代理。如果它是面向服务器,为服务器服务的,就是反向代理。

透明代理

透明代理完全透传数据,不对数据做任何篡改

非透明代理

非透明代理可能会对数据进行篡改后,在发送给客户端或者目标服务器

正向代理

正向代理面向的是用户,即客户端,用户对外访问网络,都会先经过代理服务器,然后由代理服务器访问真实目标服务器。如科学上网、VPN

反向代理

反向代理面向的是服务器,用户访问到代理服务器后,由代理分发到真实目标服务器,如 nginx 网关 通过代理服务器分发内容到服务器,避免直接访问服务器,起到安全作用,也可以配置负载均衡

hosts的作用

image.png

可用记事本/代码编辑器 打开编辑保存。

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 指向本地创建的代理服务器,在自己的服务器中为所欲为,就可以做到很多的事情:

  1. 转发功能。
  2. 数据抓包,打印记录数据。
  3. 加解密数据,对数据源进行编码后返回。
  4. 解决跨域问题。
  5. 更改请求头。