用NodeJS实现HTTP/HTTPS代理

用NodeJS实现HTTP/HTTPS代理

基本想法和代码来自论坛这个帖子, http://cnodejs.org/topic/4f16442ccae1f4aa27001101

做了一些修改,放到git上去了。支持HTTPS访问外部网站。 https://github.com/xuduo35/simpleproxy.git

<pre><code> var net = require(‘net’); var serverip = “127.0.0.1”; var local_port = 8894; var localflag = 0;

if (process.argv.length >= 3) { serverip = process.argv[2]; local_port = 8893; localflag = 1; }

//在本地创建一个server监听本地local_port端口 net.createServer(function (client) { //首先监听浏览器的数据发送事件,直到收到的数据包含完整的http请求头 var buffer = new Buffer(0);

client.on('data', function (data) {
    buffer = buffer_add(buffer, data);
if (buffer_find_body(buffer) == -1) return;

var req = parse_request(buffer);

if (req === false) return;

client.removeAllListeners('data');
relay_connection(req);

});

//从http请求头部取得请求信息后,继续监听浏览器发送数据,同时连接目标服务器,并把目标服务器的数据传给浏览器 function relay_connection(req) { console.log(req.method + ’ ’ + req.host + ‘:’ + req.port);

//如果请求不是CONNECT方法(GET, POST),那么替换掉头部的一些东西
if (req.method != 'CONNECT') {
    //先从buffer中取出头部
    var _body_pos = buffer_find_body(buffer);
    
    if (_body_pos &lt; 0) _body_pos = buffer.length;
    
    var header = buffer.slice(0, _body_pos).toString('utf8');
    
    //替换connection头
    header = header.replace(/(proxy-)?connection\:.+\r\n/ig, '')
        .replace(/Keep-Alive\:.+\r\n/i, '')
        .replace("\r\n", '\r\nConnection: close\r\n');
    
    //替换网址格式(去掉域名部分)
    if (req.httpVersion == '1.1') {
        var url = req.path.replace(/http\:\/\/[^\/]+/, '');
        if (url.path != url) header = header.replace(req.path, url);
    }
    
    buffer = buffer_add(new Buffer(header, 'utf8'), buffer.slice(_body_pos));
}

if (localflag) {
    // encrypt in local, decrypt for proxy in buffer_add
    for (var i = 0; i &lt; buffer.length; i++) {
        buffer[i] += 1;
    }
}

client.pause();

//交换服务器与浏览器的数据
client.on("data", function (data) {
    if (!server.closeflag) {
        server.write(data);
    }
});

//建立到目标服务器的连接
var server = localflag ? net.createConnection(8894, serverip) : net.createConnection(req.port, req.host);

server.pause();

server.on("data", function (data) {
    if (!client.closeflag) {
        // encrypt for local, decrypt for proxy
        for (var i = 0; i &lt; data.length; i++) {
            data[i] += localflag ? 1 : -1;
        }
            
        client.write(data);
    }
});

client.on("end", function () {
    client.closeflag = 1
});

server.on("end", function () {
    server.closeflag = 1
});

server.on("connect", function (socket) {
    client.resume();
    server.resume();
    
    if (req.method == 'CONNECT') {
        if (localflag) {
            server.write(buffer);
            client.write(new Buffer("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n"));
        }
    } else {
        server.write(buffer);
    }
});

}

}).listen(local_port);

console.log(‘Proxy server running at localhost:’+local_port);

//处理各种错误 process.on(‘uncaughtException’, function (err) { console.log("\nError!!!"); console.log(err); });

/* 从请求头部取得请求详细信息 如果是 CONNECT 方法,那么会返回 { method,host,port,httpVersion} 如果是 GET/POST 方法,那么返回 { metod,host,port,path,httpVersion} */ function parse_request(buffer) { var s = buffer.toString(‘utf8’);

var method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];

if (method == ‘CONNECT’) { var arr = s.match(/^([A-Z]+)\s([^:\s]+):(\d+)\sHTTP/(\d.\d)/);

if (arr &amp;&amp; arr[1] &amp;&amp; arr[2] &amp;&amp; arr[3] &amp;&amp; arr[4])
    return {
        method: arr[1],
        host: arr[2],
        port: arr[3],
        httpVersion: arr[4]
    };

} else { var arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP/(\d.\d)/);

if (arr &amp;&amp; arr[1] &amp;&amp; arr[2] &amp;&amp; arr[3]) {
    var host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];
    
    if (host) {
        var _p = host.split(':', 2);
        return {
            method: arr[1],
            host: _p[0],
            port: _p[1] ? _p[1] : 80,
            path: arr[2],
            httpVersion: arr[3]
        };
    }
}

}

return false;

}

/* 两个buffer对象加起来 */ function buffer_add(buf1, buf2) { if (!localflag) { // decrypt for (var i = 0; i < buf2.length; i++) { buf2[i] -= 1; } }

var re = new Buffer(buf1.length + buf2.length);

buf1.copy(re); buf2.copy(re, buf1.length);

return re;

}

/* 从缓存中找到头部结束标记("\r\n\r\n")的位置 */ function buffer_find_body(b) { for (var i = 0, len = b.length - 3; i < len; i++) { if (b[i] == 0x0d && b[i + 1] == 0x0a && b[i + 2] == 0x0d && b[i + 3] == 0x0a) { return i + 4; } }

return -1;

} </code></pre>


7 回复

用NodeJS实现HTTP/HTTPS代理

在本教程中,我们将探讨如何使用Node.js实现一个简单的HTTP/HTTPS代理。此代理可以用于转发客户端请求到指定的目标服务器,并将响应返回给客户端。

基本思路

代理服务器需要监听客户端的请求,并将这些请求转发到目标服务器。然后,代理服务器接收目标服务器的响应,并将其返回给客户端。为了简化问题,我们假设所有请求都是通过TCP连接进行的。

示例代码

以下是使用Node.js实现HTTP/HTTPS代理的基本代码:

const net = require('net');

let serverIp = "127.0.0.1";
let localPort = 8894;
let localFlag = 0;

if (process.argv.length >= 3) {
    serverIp = process.argv[2];
    localPort = 8893;
    localFlag = 1;
}

// 创建一个本地服务器,监听指定端口
net.createServer((client) => {
    let buffer = new Buffer(0);

    // 监听客户端的数据发送事件
    client.on('data', (data) => {
        buffer = buffer_add(buffer, data);

        if (buffer_find_body(buffer) == -1) return;

        let req = parse_request(buffer);

        if (req === false) return;

        client.removeAllListeners('data');
        relay_connection(req);
    });

    // 处理请求并连接目标服务器
    function relay_connection(req) {
        console.log(`${req.method} ${req.host}:${req.port}`);

        if (req.method !== 'CONNECT') {
            let _body_pos = buffer_find_body(buffer);

            if (_body_pos < 0) _body_pos = buffer.length;

            let header = buffer.slice(0, _body_pos).toString('utf8');

            // 替换连接头
            header = header.replace(/(proxy-)?connection\:.+\r\n/ig, '')
                .replace(/Keep-Alive\:.+\r\n/i, '')
                .replace("\r\n", '\r\nConnection: close\r\n');

            buffer = buffer_add(new Buffer(header, 'utf8'), buffer.slice(_body_pos));
        }

        if (localFlag) {
            for (let i = 0; i < buffer.length; i++) {
                buffer[i] += 1;
            }
        }

        client.pause();

        // 建立到目标服务器的连接
        let server = localFlag ? net.createConnection(8894, serverIp) : net.createConnection(req.port, req.host);

        server.pause();

        // 交换服务器与浏览器的数据
        client.on('data', (data) => {
            if (!server.closeflag) {
                server.write(data);
            }
        });

        server.on('data', (data) => {
            if (!client.closeflag) {
                for (let i = 0; i < data.length; i++) {
                    data[i] += localFlag ? 1 : -1;
                }
                client.write(data);
            }
        });

        client.on('end', () => {
            client.closeflag = 1;
        });

        server.on('end', () => {
            server.closeflag = 1;
        });

        server.on('connect', (socket) => {
            client.resume();
            server.resume();

            if (req.method === 'CONNECT') {
                if (localFlag) {
                    server.write(buffer);
                    client.write(new Buffer("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n"));
                }
            } else {
                server.write(buffer);
            }
        });
    }
}).listen(localPort);

console.log(`Proxy server running at localhost:${localPort}`);

process.on('uncaughtException', (err) => {
    console.log("\nError!!!");
    console.log(err);
});

// 解析请求头部
function parse_request(buffer) {
    let s = buffer.toString('utf8');

    let method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];

    if (method === 'CONNECT') {
        let arr = s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d.\d)/);

        if (arr && arr[1] && arr[2] && arr[3] && arr[4])
            return {
                method: arr[1],
                host: arr[2],
                port: arr[3],
                httpVersion: arr[4]
            };
    } else {
        let arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d.\d)/);

        if (arr && arr[1] && arr[2] && arr[3]) {
            let host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];

            if (host) {
                let _p = host.split(':', 2);
                return {
                    method: arr[1],
                    host: _p[0],
                    port: _p[1] ? _p[1] : 80,
                    path: arr[2],
                    httpVersion: arr[3]
                };
            }
        }
    }

    return false;
}

// 合并两个Buffer对象
function buffer_add(buf1, buf2) {
    if (!localFlag) {
        for (let i = 0; i < buf2.length; i++) {
            buf2[i] -= 1;
        }
    }

    let re = new Buffer(buf1.length + buf2.length);

    buf1.copy(re);
    buf2.copy(re, buf1.length);

    return re;
}

// 查找头部结束标记位置
function buffer_find_body(b) {
    for (let i = 0, len = b.length - 3; i < len; i++) {
        if (b[i] == 0x0d && b[i + 1] == 0x0a && b[i + 2] == 0x0d && b[i + 3] == 0x0a) {
            return i + 4;
        }
    }

    return -1;
}

代码解析

  1. 创建代理服务器

    • 使用net.createServer创建一个本地服务器,监听指定端口。
    • 当有客户端连接时,启动一个回调函数处理客户端数据。
  2. 解析请求

    • parse_request函数解析HTTP请求头,提取请求方法、目标主机和端口等信息。
  3. 数据转发

    • 将客户端数据转发到目标服务器。
    • 接收目标服务器的数据并转发回客户端。
    • 使用buffer_addbuffer_find_body函数处理数据缓冲区。
  4. 异常处理

    • 使用process.on('uncaughtException')捕获未处理的异常。

总结

通过上述代码,我们可以实现一个简单的HTTP/HTTPS代理服务器。该代理服务器能够处理客户端的HTTP请求,并将请求转发到目标服务器,再将响应返回给客户端。希望这个示例能帮助你理解如何使用Node.js实现一个基本的代理服务器。


这么多的正则,性能不会有问题吗?

有没有更好的方法?

没什么问题,只是解析HTTP头而已。

确定有测试过https吗?

用github上最新的,我天天在用,肯定可以。

为了实现一个简单的 HTTP/HTTPS 代理服务器,我们可以使用 Node.js 的 net 模块来创建 TCP 服务器,用于处理客户端请求并转发它们到目标服务器。下面是一个基于提供的代码片段的简化版本,用于展示如何实现 HTTP 代理的基本逻辑。

示例代码

const net = require('net');

const PORT = 8894;

const server = net.createServer((clientSocket) => {
    clientSocket.on('data', (data) => {
        const request = parseRequest(data);
        if (!request) return;

        console.log(`${request.method} ${request.host}:${request.port}`);

        const serverSocket = net.connect({ port: request.port, host: request.host }, () => {
            serverSocket.write(data.slice(request.headersEnd));
            clientSocket.pipe(serverSocket);
            serverSocket.pipe(clientSocket);
        });

        serverSocket.on('error', (err) => {
            console.error(`Server error: ${err.message}`);
            clientSocket.destroy();
            serverSocket.destroy();
        });
    });
});

server.listen(PORT, () => {
    console.log(`Proxy server running on port ${PORT}`);
});

function parseRequest(buffer) {
    const requestLine = buffer.toString().split('\n')[0];
    const [method, path, version] = requestLine.match(/^(GET|POST|CONNECT) ([^ ]+) HTTP\/(\d\.\d)$/);

    if (!method || !path || !version) return null;

    if (method === 'CONNECT') {
        const hostPort = path.split(':');
        return { method, host: hostPort[0], port: parseInt(hostPort[1], 10), headersEnd: buffer.indexOf('\r\n\r\n') + 4 };
    } else {
        const host = buffer.toString().match(/Host: ([^\r\n]+)/)[1];
        const [hostName, port] = host.split(':').map(item => item.trim());
        return { method, host: hostName, port: port ? parseInt(port, 10) : 80, path, headersEnd: buffer.indexOf('\r\n\r\n') + 4, version };
    }
}

解释

  1. 创建 TCP 服务器:我们使用 net.createServer() 创建一个 TCP 服务器,监听指定端口(本例中为 8894)。
  2. 处理客户端数据:当客户端连接并发送数据时,我们通过 parseRequest 函数解析 HTTP 请求,区分 GET/POSTCONNECT 方法。
  3. 建立到目标服务器的连接:根据解析的结果,创建到目标服务器的连接,并将客户端的数据流和服务器的数据流互连。
  4. 错误处理:如果在连接过程中出现任何错误,则销毁客户端和服务器的连接。

此代码仅实现了基本功能,没有包括 HTTPS 支持、身份验证、日志记录等高级功能。实际应用中可能需要扩展更多功能。

回到顶部