HttpProxy

目的

  • 了解http(请求、响应)的报文内容
  • 学习使用socket转发请求
  • 针对http、https这两种类型的请求通过socket区分转发
  • 解析http转发响应的内容

实现思路

1、手动或代码设置本地代理,把浏览器所有的请求都转发到本地的代理端口上

手动设置

RUNOOB 图标

代码设置

RUNOOB 图标

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

参考资料

posted @ 2020-12-23 23:40  东东东  阅读(648)  评论(0)    收藏  举报