本文参考自 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();