基于正向代理实现的万能代理接口代理转发

今天把项目中的反向代理脚本程序抽成了一个插件,通过配置文件配置代理的http请求,这样使用起来比较方便,每位开发成员都可以用自己配置的代理调试代码。也可以用来直接做http代理,你甚至都不用Charles或者fiddler,直接开启proxy-ajax,然后手机上设置代理就可以了。

使用方法:

npm install fedp -g

fedp init  // 会提示你在那种场景使用,自动生成配置文件

fedp -c config.js // 会提示你在那种场景使用,自定义配置文件

github: https://github.com/chalecao/fedp     请帮我点亮小星星,感谢star

使用很简单,修改配置文件就好了。访问地址是:127.0.0.1:8889/访问路径

  • 根据配置会自动代理静态资源,包括css,图片,可以代理到本地或者远程
  • 自定义接口,根据匹配关键字,返回指定的数据结构
/**
 * fedp配置文件
 */

const targetMockServer = 'https://127.0.0.1/';

module.exports = {
  port: 8889, // proxy server port
  domain: false, // true to apply new domain, false to use ip, or you self domain string like "test.tmall.com"
  debug: false, // enable debug
  mock: true, // enable mock
  env: '', // pre预发,prod线上,daily日常,对应于不同请求
  debugPort: 9001, // debug server port
  cmds: [ // cmds you want run
    // "tap server"
  ],
  scripts: [ // scripts you want to inject to the html
  ],
  // urlsuffix: "src/indexWeb.html#POSMarketingIndex",
  // simulator: "//irma.work.ucweb.local/#/remote/remote-control-devices",   // web simulator url
  // simulator: "//mds.alibaba-inc.com/device/93c29bb90005",   // web simulator url, 需要填写云真机的url
  // domainy: [{                     // proxy domain config, if not for remote debug, no need to config,
  //     path: "g-assets.daily.taobao.net",
  //     data: "__ip__:8889"
  // }],
  proxy: {
    host: [{ // proxy requestoption host config
      path: '.(js|css)',
      data: '127.0.0.1',
    }, {
      path: '.*',
      data: 'localhost',
    }],
    hostname: [{ // proxy requestoption host config
      path: '.(js|css)',
      data: '127.0.0.1',
    }, { // proxy requestoption hostname config
      path: '.*',
      data: 'localhost',
    }],
    port: [{ // proxy requestoption port config
      path: '.*',
      data: 3634,
    }],
  },
  mocky: [
    {
      path: 'desc',
      data: './mock/desc.js',
    }, {
      path: 'xiaoer',
      data: './mock/xiaoer.js',
    }, {
      path: 'search',
      data: './mock/search.js',
    }, {
      path: 'create',
      data: './mock/create.js',
    }, {
      path: 'getActivity',
      data: './mock/getActivity.js',
    }, {
      path: 'join',
      data: './mock/join.js',
    }, {
      path: 'activity/scenes/info',
      data: './mock/scenes.js',
    }, {
      path: '.activity',
      data: './mock/activity.js',
    }, {
      path: 'mockServer',
      routeTo: targetMockServer,
    }],
}

反向代理与正向代理

正向代理,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。此时正向代理表现的像一个客户端,请求资源,返回数据。

正向代理很好理解,比如手机上wifi设置的代理就是正向代理,浏览器设置的代理也是正向代理,正向代理只是简单的为你转发请求,把请求到的数据回传到源请求。

反向代理(Reverse Proxy)方式是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器,代理请求。

反向代理可以看成一个服务器,比如我们常用的nginx反向代理,服务器上只是监听了一个80端口,但是这个80端口既提供网络服务,又提供接口服务,可以通过不同的域名或者请求路径区分开不同的服务,这就用到了反向代理。对于客户端只是向反向代理请求数据,至于反向代理是自身的服务,还是其他组合的服务,客户端并不知情。

node实现正向代理

在正向代理中,我们通常会使用两种形式的代理:

第一种是 RFC 7230 – HTTP/1.1: Message Syntax and Routing(即修订后的 RFC 2616,HTTP/1.1 协议的第一部分)描述的普通代理。这种代理扮演的是「中间人」角色,对于连接到它的客户端来说,它是服务端;对于要连接的服务端来说,它是客户端。它就负责在两端之间来回传送 HTTP 报文。

第二种是 Tunneling TCP based protocols through Web proxy servers(通过 Web 代理服务器用隧道方式传输基于 TCP 的协议)描述的隧道代理。它通过 HTTP 协议正文部分(Body)完成通讯,以 HTTP 的方式实现任意基于 TCP 的应用层协议代理。这种代理使用 HTTP 的 CONNECT 方法建立连接,但 CONNECT 最开始并不是 RFC 2616 – HTTP/1.1 的一部分,直到 2014 年发布的 HTTP/1.1 修订版中,才增加了对 CONNECT 及隧道代理的描述,详见 RFC 7231 – HTTP/1.1: Semantics and Content。实际上这种代理早就被广泛实现。

第一种方式,代理就是拿到源请求的请求数据,然后代理请求目的资源,最后代理将请求的结果返回到源请求。

var http = require('http');
var net = require('net');
var url = require('url');

function request(cReq, cRes) {
    var u = url.parse(cReq.url);

    var options = {
        hostname : u.hostname,
        port     : u.port || 80,
        path     : u.path,
        method     : cReq.method,
        headers     : cReq.headers
    };

    var pReq = http.request(options, function(pRes) {
        cRes.writeHead(pRes.statusCode, pRes.headers);
        pRes.pipe(cRes);
    }).on('error', function(e) {
        cRes.end();
    });

    cReq.pipe(pReq);
}

http.createServer().on('request', request).listen(8888, '0.0.0.0');

上面这种方式只适合普通的http请求,对于https请求就不行了,因为无法解析出请求的具体路径和参数,所以自然也不能使用上面的方法。那么我们只能采用第二种方式,隧道代理。其实在node中已经在net包中封装好了对应的方法。

var http = require('http');
var net = require('net');
var url = require('url');

function connect(cReq, cSock) {
    var u = url.parse('https://' + cReq.url);

    var pSock = net.connect(u.port, u.hostname, function() {
        cSock.write('HTTP/1.1 200 Connection Establishedrnrn');
        pSock.pipe(cSock);
    }).on('error', function(e) {
        cSock.end();
    });

    cSock.pipe(pSock);
}

http.createServer().on('connect', connect).listen(8888, '0.0.0.0');

从原理上讲,假如我通过代理访问 A 网站,浏览器首先通过 CONNECT 请求,让代理创建一条到 A 网站的 TCP 连接;一旦 TCP 连接建好,代理无脑转发后续流量即可。所以这种代理,理论上适用于任意基于 TCP 的应用层协议,HTTPS 网站使用的 TLS 协议当然也可以。这也是这种代理为什么被称为隧道的原因。把上面两种形式结合起来,就是一个完美的正向代理。

需要注意的是CONNECT请求还是通过http代理链接的,毕竟也是明文传输,可能会被监测和拦截,如果访问youtube这样的网站,就会被屏蔽。所以,如果要想访问一些其他网站,可以使用https代理,也可以使用shadow socket 或者 SOCK5代理或者加密的VPN。。

node实现反向代理

反向代理实现起来就比较复杂了,对于客户端来说反向代理更像是一个服务器。他可以代理一个具体的请求到另外一个目的地址,然后返回响应,也可以根据指定规则返回指定数据。

require('http').createServer(function (req, res) {

        // 在这里可以自定义你的路由分发
        var host = req.headers.host,
            rurl = req.url,
            ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
        console.log("");
        console.log('client ip: '.blue + ip + ' , host: '.green + host);
        console.log("request URL: ".cyan + rurl );
        //查找匹配规则
        let p = _proxy.find(function (p) {
            var rule = new RegExp(p.path);
            return rule.exec(rurl) && p.path;
        });
        if (p) {
        //如果匹配,那么走反向代理
            console.log("find rule for above url!".yellow)
            if (p.data) {
                //如果制定接口返回数据,那么直接返回数据
                getData(resolve(p.data)).then(function (value) {
                    //判断是不是jsonp接口
                    let callbackName = new RegExp("callback=(.*)&", "g").exec(req.url);
                    if (callbackName && callbackName[1]) {
                        console.log("jsonp match given data! ".red);
                        res.writeHead(200, {
                            'Content-Type': 'application/json'
                        });
                        res.end(callbackName[1] + "(" + JSON.stringify(value) + ")");
                    } else {
                        console.log("ajax match given data! ".red);
                        res.writeHead(200, {
                            'Content-Type': 'application/json'
                        });
                        res.end(JSON.stringify(value));

                    }
                })

            } else if (p.routeTo) {
                //如果匹配的是反向代理的地址,那么代理接口
                console.log("proxy to: ".red + proxyConfig[p.routeTo]);
                // 设置req
                req._originUrl = req.url;
                proxy.web(req, res, {
                    target: proxyConfig[p.routeTo]
                });
            } else {
                //该规则没有配置,那么走正向代理
                request(req, res)
            }
        } else {
        //如果没有规则匹配,那么走正向代理
            request(req, res)
        }

    });
    server.listen((port || proxyConfig.port));

上面给出了部分代码实现逻辑。反向代理主要走两个逻辑:

  1. 如果指定某个http请求返回的数据,那么直接返回这个数据
  2. 如果指定某个http的代理地址,那么代理这个地址,http的代理是基于http-proxy这个npm包来做的。

ajax请求跨域带cookie

这里顺带介绍一下这个知识点,跨域请求常用的方案是CORS,经常会遇到跨域请求带cookie的情况,默认ajax跨域请求是不带cookie的。如果需要带cookie,需要这样写:

原生ajax请求方式:

var xhr = new XMLHttpRequest();
xhr.open("POST", "https://xxxx.com/demo/b/index.php", true);
xhr.withCredentials = true; //支持跨域发送cookies
xhr.send();

jquery为例:
$.ajax({
    type: "POST",
    url: "https://xxx.com/api/test",
    dataType: 'jsonp',
    xhrFields: {
        withCredentials: true //配置跨域带cookie
    },
    crossDomain: true,
    success:function(){
    },
    error:function(){
    }
})

服务端CORS配置:

1

2

header("Access-Control-Allow-Credentials: true"); //允许跨域带cookie

header(“Access-Control-Allow-Origin: https://www.xxx.com”); //允许跨域请求的域名

使用姿势

好的工具需要有正确的使用姿势。

  1. 如果你需要调试的接口都已上线,你只是简单的想代理到本地,那么可以将静态文件代理到本地即可。这时候用到的是正向代理,可能需要浏览器插件配置http代理到本地的代理服务器,然后通过配置文件配置需要代理的静态资源。
  2. 如果需要调试的接口还在开发,或者在某台服务器上,那么这个代理服务器就很有用。你可以把需要调试的接口代理到指定的服务器,把其他接口依然代理到线上,这用到的就是正向和反向代理。

参考资料

  1. node-http-proxy:https://github.com/nodejitsu/node-http-proxy
  2. HTTP权威指南: https://book.douban.com/subject/10746113/
  3. Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing: https://tools.ietf.org/html/rfc7230
  4. Tunneling TCP based protocols through Web proxy servers: https://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01

5. HTTP 代理原理及实现: https://imququ.com/post/web-proxy.html



请遵守《互联网环境法规》文明发言,欢迎讨论问题
扫码反馈

扫一扫,反馈当前页面

咨询反馈
扫码关注
返回顶部