HttpProxy
目的
- 了解http(请求、响应)的报文内容
- 学习使用socket转发请求
- 针对http、https这两种类型的请求通过socket区分转发
- 解析http转发响应的内容
实现思路
1、手动或代码设置本地代理,把浏览器所有的请求都转发到本地的代理端口上
手动设置

代码设置

2、本地监听一个端口
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
tcpListener.Start();
3、接收浏览器请求的报文,解析获取浏览器请求的ip
接收浏览器请求的报文并解析
var readBuffer = new byte[clientSocket.Available];
int count = clientSocket.Receive(readBuffer);
string[] receiveContent = Encoding.ASCII.GetString(readBuffer.ToArray()).Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
获取浏览器请求的ip
private static (string domainName, string host, int port) ShowUrl(string[] receiveContent)
{
const string key = "host:";
var splits = receiveContent
.Single(s => s.StartsWith(key, StringComparison.OrdinalIgnoreCase))
.Substring(key.Length)
.TrimStart()
.Split(':');
string domainName = splits[0];
int port = 80;
switch (splits.Length)
{
case 2:
port = Convert.ToInt32(splits[1]);
break;
}
string host = Dns.GetHostAddresses(domainName)[0].ToString();
string url = $"http://{host}:{port} domainName:{domainName}";
Console.WriteLine(url);
return (domainName, host, port);
}
3、根据浏览器请求的报文区分是http还是https,然后创建socket实现请求转发,获取转发后响应的报文
区分http还是https
string flag = receiveContent[0].Substring(0, 7);
if (flag == "CONNECT")
{
var bytes = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection established\r\n\r\n");
clientSocket.Send(bytes, 0, bytes.Length, 0);
Thread.Sleep(500);
Proxy(result.host, result.port, readBuffer, clientSocket, false);
}
else
{
Proxy(result.host, result.port, readBuffer, clientSocket, true);
}
创建socket实现请求转发,获取转发后请求响应的报文
http请求转发
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(host, port);
socket.Send(readBuffer);
List<byte> receiveByteAlls = new List<byte>();
do
{
byte[] receiveBytes = new byte[1024 * 1024];
int count = socket.Receive(receiveBytes);
receiveByteAlls.AddRange(receiveBytes.Take(count));
}
while (socket.Available > 0);
https请求转发
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(host, port);
while (clientSocket.Available != 0 || socket.Available != 0)
{
try
{
while (clientSocket.Available != 0)
{
byte[] receiveBytes = new byte[1024 * 1024];
int count = clientSocket.Receive(receiveBytes, receiveBytes.Length, 0);
socket.Send(receiveBytes, count, 0);
}
Thread.Sleep(500);
while (socket.Available != 0)
{
byte[] receiveBytes = new byte[1024 * 1024];
int count = socket.Receive(receiveBytes, receiveBytes.Length, 0);
clientSocket.Send(receiveBytes, count, 0);
}
}
catch
{
Console.WriteLine("https连接报错");
socket.Shutdown(SocketShutdown.Both);
socket.Close();
return;
}
Thread.Sleep(500);
}
while (socket.Available > 0);
4、解析转发后响应的http报文,篡改网页内容,重新编码回转给浏览器端
- 通过响应的报文判断是不是301或302跳转,再选择是否解码
string strResponse = Encoding.UTF8.GetString(receiveByteAllArray, 0, iTotalCount);
bool hasRelocated = false;
if (strResponse.StartsWith("HTTP/1.1 302") || strResponse.StartsWith("HTTP/1.1 301"))//跳转
{
}
- 通过响应的报文解码获取头部信息
- 根据头部信息判断内容是否压缩过 比如gzip 如果有压缩先进行解压
- 根据头部信息charset指定的编码方式解码网页内容,如果头部没有charset,可以选择用不同的编码方式解码网页内容固定的几个字节,寻找到网页内容编码的方式然后再进行解码
- 篡改网页内容
- 做反向操作,比如编码、压缩
- 修改头部的Content-Length的值(篡改后网页字节数组的大小)
- 把新生成的网页报文回转发给浏览器端
代码地址
https://gitee.com/ffxxxdd/http-proxy.git

浙公网安备 33010602011771号