胡焦24

You got a dream, you gotta to protect it!

站内搜索

对http协议代理的理解

发布日期:2026-03-02 |文章分类: 默认分类

本文参考自 https://github.com/qgy18/proxy-demo

http协议中的字段

http协议中,主要根据这三个字段来识别:REMOTE_ADDR 、HTTP_VIA、HTTP_X_FORWARDED_FOR 

REMOTE_ADDR 必须要有有效值,不然无法建立 tcp 连接,可以设为实际的 ip 或者代理的 ip

没有代理服务器的情况

REMOTE_ADDR = 你的 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

高匿名代理服务器

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

透明代理

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP (补充:这个字段由代理服务器填充,有时会填充网关信息等)

HTTP_X_FORWARDED_FOR = 你的真实 IP

欺骗代理

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP (补充:这个字段由代理服务器填充,有时会填充网关信息等)

HTTP_X_FORWARDED_FOR = 随机的 IP(告诉了访问对象你使用了代理服务器,但编造了一个虚假的随机IP代替你的真实IP欺骗它)

普通代理

firefox 浏览器设置 http 代理(或使用 curl 命令),请求 http 页面地址 http://news.youth.cn/gn/202308/t20230805_14698898.htm

通过报文可以发现,都是正常的 GET 请求,在请求的时候代理只是单纯的流量中转

请求的流程为(firefox –> 代理工具 –> 请求目标)

代理工具实现

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');

隧道代理

firefox 浏览器设置 https 代理,访问百度站点首页,通过报文可以发现,对比正常的 https 请求来说,多了 CONNECT 请求和响应

相当于对 CONNECT 请求响应之后,浏览器和网站之间的流量,对代理来说都是透传中转

代理工具实现

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

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

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

    cSock.pipe(pSock);
}

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

全功能代理

合并上面两个处理方式,就可以得到一个支持 http 以及 https 流量的代理,firrfox 中需要同时设置 http 和 https 代理

代理工具实现

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);
}

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

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

    cSock.pipe(pSock);
}

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

http走隧道代理

上面的代码逻辑只是让 http 走了普通代理,https 走了隧道代理

同样也可以让 http 走隧道代理,和 https 的隧道代理一样,需要先发送一个 CONNECT 请求,客户端测试代码如下

var http = require('http');

var options = {
    hostname : '127.0.0.1',
    port     : 8888,
    path     : 'imququ.com:80',
    method     : 'CONNECT'
};

var req = http.request(options);

req.on('connect', function(res, socket) {
    socket.write('GET / HTTP/1.1\r\n' +
                 'Host: imququ.com\r\n' +
                 'Connection: Close\r\n' +
                 '\r\n');

    socket.on('data', function(chunk) {
        console.log(chunk.toString());
    });

    socket.on('end', function() {
        console.log('socket end.');
    });
});

req.end();

升级代理为https

将浏览器到代理升级为https,需要申请相关的证书,代理服务启动的时候加载,以下是代理服务端最终版本代理

代理工具实现

var http = require('http');
var https = require('https');
var fs = require('fs');
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);
}

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

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

    cSock.pipe(pSock);
}

var options = {
    key: fs.readFileSync('./private.pem'),
    cert: fs.readFileSync('./public.crt')
};

https.createServer(options)
    .on('request', request)
    .on('connect', connect)
    .listen(8888, '0.0.0.0');

请求测试的代码

var https = require('https');

var options = {
    hostname : '127.0.0.1',
    port     : 8888,
    path     : 'imququ.com:80',
    method     : 'CONNECT'
};

//禁用证书验证,不然自签名的证书无法建立 TLS 连接
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

var req = https.request(options);

req.on('connect', function(res, socket) {
    socket.write('GET / HTTP/1.1\r\n' +
                 'Host: imququ.com\r\n' +
                 'Connection: Close\r\n' +
                 '\r\n');

    socket.on('data', function(chunk) {
        console.log(chunk.toString());
    });

    socket.on('end', function() {
        console.log('socket end.');
    });
});

req.end();