0

我正在尝试使用 Node.js 创建一个 HTTP/S MitM 转发代理。

我处理这个项目的方法是重用@runk 在Node HTTP 代理项目问题跟踪器上提出问题后创建的NPM 代理缓存项目./lib/proxy.js文件中的解决方案。

我的Proxy()课看起来像这样:

var request = require('request')
  , https = require('https')
  , http = require('http')
  , net = require('net')
  , url = require('url')
  , os = require('os')
  , fs = require('fs');

var SOCKET_PATH = os.tmpdir() + 'mitm.sock';
console.log('[SOCKET PATH] ' + SOCKET_PATH);

function Proxy (config) {
    config = config || {};

    if(fs.existsSync(SOCKET_PATH)) {
        fs.unlinkSync(SOCKET_PATH);
    }

    var options = {
        key: fs.readFileSync('./certs/dummy.key', 'utf8'),
        cert: fs.readFileSync('./certs/dummy.crt', 'utf8')
    };

    // HTTPS Server
    https.createServer(options, this.handler).listen(config.port + 1, this.hostname, function (e) {
        if(e) {
            console.log('[HTTPS] Server listen() error !');
            throw e;
        }
    });

    // HTTP Server
    var server = http.createServer(this.handler);
    server.listen(config.port, this.hostname, function (e) {
        if(e) {
            console.log('[HTTP] Server listen() error !');
            throw e;
        }
    });

    // Intercept CONNECT requests for HTTPS handshake
    server.addListener('connect', this.httpsHandler);
}

Proxy.prototype.handler = function (req, res) {
    var schema = !!req.client.pair ? 'https' : 'http'
      , path = url.parse(req.url).path;

    var dest = schema + '://' + req.headers['host'] + path;

    console.log('(1) - [' + schema.toUpperCase() + '] ' + req.method + ' ' + req.url);

    var params = {
        rejectUnauthorized: false,
        url: dest
    };

    if(req.method.toUpperCase() !== 'GET') {
        return console.log('[HTTP] Request is not HTTP GET.');
    }

    var onResponse = function (e, response) {
        if(e == null && response.statusCode === 200) {
            return r.pipe(res);
        }

        var body = 'Status ' + response.statusCode + ' returned';
        if(e) {
            body = e.toString();
        }

        res.end(body);
    };

    var r = request(params);
    r.on('response', onResponse.bind(null, null));
    r.on('error', onResponse.bind(null));
};

Proxy.prototype.httpsHandler = function (request, socketRequest, bodyHead) {
    var httpVersion = request['httpVersion']
      , url = request['url'];

    console.log('(2) - [HTTPS] ' + request['method'] + ' ' + request['url']);

    var proxySocket = new net.Socket();

    // ProxySocket event handlers
    proxySocket.connect(SOCKET_PATH, function () {
        proxySocket.write(bodyHead);
        proxySocket.write('HTTP/' + httpVersion + ' 200 Connection established\r\n\r\n');
    });

    proxySocket.on('data', function (chunk) {
        console.log('ProxySocket - "data"');
        socketRequest.write(chunk);
    });

    proxySocket.on('end', function () {
        console.log('ProxySocket - "end"');
        socketRequest.end();
    });

    proxySocket.on('error', function (e) {
        console.log('ProxySocket - "error"');
        console.log(e);
        console.log(e.stack);
        socketRequest.write('HTTP/' + httpVersion + ' 500 Connection error\r\n\r\n');
        socketRequest.end();
    });

    // SocketRequest event handlers
    socketRequest.on('data', function (chunk) {
        console.log('SocketRequest - "data"');
        proxySocket.write(chunk);
    });

    socketRequest.on('end', function () {
        console.log('SocketRequest - "end"');
        proxySocket.end();
    });

    socketRequest.on('error', function (e) {
        console.log('socketRequest - "error"');
        console.log(e);
        console.log(e.stack);
        proxySocket.end();
    });

};

module.exports = Proxy;

Index.js启动程序的文件如下所示:

var Proxy = require('./lib/proxy');

var proxy = new Proxy({
    hostname: '127.0.0.1',
    port: 8000
});

这是我的目录/文件结构:

/my_project
    /certs
        dummy.crt // Copied from the NPM Proxy Cache project
        dummy.csr // Copied from the NPM Proxy Cache project
        dummy.key // Copied from the NPM Proxy Cache project
    /lib
        proxy.js
    index.js

我正在通过将(在Mac OSX Maverick)HTTP 和 HTTPS 代理设置为 IP 地址127.0.0.1和端口来测试我的程序8000

浏览仅限 HTTP 的网站时,一切正常,但如果我浏览 HTTPS 网站,我会收到以下错误:

{[Error: connect ENOENT] code: 'ENOENT', errno: 'ENOENT', syscall: 'connect'}
Error: connect ENOENT
    at errnoException (net.js:904:11)
    at Object.afterConnect [as oncomplete] (net.js:895:19)
  • 这个问题可能来自哪里以及如何解决这个问题的任何想法?

非常感谢您提前!

(如果你想测试我的代码,NPM 模块request是运行代码所需的唯一依赖项。)

编辑:可以从这里下载证书:https ://github.com/runk/npm-proxy-cache/tree/master/cert 。

4

1 回答 1

2

我是npm-proxy-cache. 事实上,我创建了另一个名为thin https://www.npmjs.org/package/thin的项目,我希望将来 npm 代理缓存能够利用它。尽管它仍然非常粗糙,但它可以使用并且可以满足您的需求。

例如

代理代码

var Thin = require('thin')

var proxy = new Thin;

// `req` and `res` params are `http.ClientRequest` and `http.ServerResponse` accordingly
// be sure to check http://nodejs.org/api/http.html for more details
proxy.use(function(req, res, next) {
  console.log('Proxying:', req.url);
  next();
});

// you can add different layers of "middleware" similar to "connect", 
// but with few exclusions
proxy.use(function(req, res, next) {
  if (req.url === '/foobar')
    return res.end('intercepted');
  next();
});

proxy.listen(8081, 'localhost', function(err) {
  // .. error handling code ..
});

服务器代码

var express = require('express'); // v3.4
var app = express();

app.use(express.urlencoded({limit: '10mb'}));

app.get('/test', function(req, res){
  console.log(req.protocol, 'get req.query', req.query);
  res.end('get: hello world');
});

app.post('/test', function(req, res) {
  console.log(req.protocol, 'post req.query', req.query);
  console.log(req.protocol, 'post req.body', req.body);
  res.end('post: hello world');
});

app.listen(3000);


var fs = require('fs');
var https = require('https');

https.createServer({
  key: fs.readFileSync('./cert/dummy.key'), // your mitm server keys
  cert: fs.readFileSync('./cert/dummy.crt')
}, app).listen(3001);

您需要在两个终端会话中启动代理和服务器,然后

curl -d "foo=baz" -k -x https://localhost:8081 https://localhost:3001/test?foo=bar
curl -d "foo=baz" -x http://localhost:8081 http://localhost:3000/test?foo=bar

之后,您应该能够看到服务器的以下输出

https post req.query { foo: 'bar' }
https post req.body { foo: 'baz' }
http post req.query { foo: 'bar' }
http post req.body { foo: 'baz' }

拦截器的小例子

curl -d "foo=baz" -k -x https://localhost:8081 https://localhost:3001/foobar

它应该返回intercepted

希望有帮助:)

于 2014-06-05T22:59:45.903 回答