在本文中,将探讨如何使用C#语言构建一个具有非标准代理功能的多线程HTTP代理服务器。这个服务器不仅能够处理HTTP流量,还能够处理HTTPS流量,并且加入了一个简单的缓存机制。需要注意的是,本文提供的代码仅用于调试和测试目的,作者不鼓励将其用于任何可能危及敏感信息安全的场景。在任何用户不知情的环境中使用此代码,将由使用者自行承担由此产生的数据收集责任。
如果熟悉Fiddler,那么已经知道这个代理服务器是如何工作的了。它本质上是在HTTP客户端和服务器之间进行“中间人”攻击,以转储和调试HTTP流量。在本文中,将使用System.Net.Security.SslStream
类来处理所有的复杂操作。
代码中最重要的部分是,当客户端请求一个CONNECT操作时,不仅仅是传递TCP流量,而是要处理SSL握手,建立一个SSL会话,并从客户端接收请求。同时,将相同的请求发送到目标HTTPS服务器。
首先,让创建一个能够处理多个并发TCP连接的服务器。将使用System.Threading.Thread
对象在单独的线程中开始监听连接。这个线程的任务是监听传入的连接,然后生成一个新的线程来处理处理,从而允许监听线程在处理一个客户端时继续监听新连接,而不会阻塞。
public sealed class ProxyServer {
private TcpListener _listener;
private Thread _listenerThread;
public void Start() {
_listener = new TcpListener(IPAddress.Loopback, 8888);
_listenerThread = new Thread(new ParameterizedThreadStart(Listen));
_listenerThread.Start(_listener);
}
public void Stop() {
_listener.Stop();
_listenerThread.Abort();
_listenerThread.Join();
}
private static void Listen(Object obj) {
TcpListener listener = (TcpListener)obj;
try {
while (true) {
TcpClient client = listener.AcceptTcpClient();
while (!ThreadPool.QueueUserWorkItem(new WaitCallback(ProxyServer.ProcessClient), client));
}
} catch (ThreadAbortException) { }
catch (SocketException) { }
}
private static void ProcessClient(Object obj) {
TcpClient client = (TcpClient)obj;
try {
// do your processing here
} catch (Exception ex) {
// handle exception
} finally {
client.Close();
}
}
}
以上代码展示了如何以多线程方式处理并发TCP客户端。接下来,将使用SslStream
来充当HTTPS服务器,并“欺骗”客户端,使其相信它正在与目标服务器通信。需要注意的是,浏览器实际上不应该被欺骗,因为SSL证书链的存在,但根据用户的浏览器不同,服务器身份的问题可能或可能不会被明显地提示。
假设正处于上述ProcessClient
方法中的try
块内。将读取HTTP命令的第一行。
Stream clientStream = client.GetStream();
StreamReader clientStreamReader = new StreamReader(clientStream);
String httpCmd = clientStreamReader.ReadLine();
String[] splitBuffer = httpCmd.Split(spaceSplit, 3);
String method = splitBuffer[0];
String remoteUri = splitBuffer[1];
Version version = new Version(1, 0);
HttpWebRequest webReq;
if (method == "CONNECT") {
remoteUri = "https://" + splitBuffer[1];
while (!String.IsNullOrEmpty(clientStreamReader.ReadLine())) ;
StreamWriter connectStreamWriter = new StreamWriter(clientStream);
connectStreamWriter.WriteLine("HTTP/1.0 200 Connection established");
connectStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString()));
connectStreamWriter.WriteLine("Proxy-agent: matt-dot-net");
connectStreamWriter.WriteLine();
connectStreamWriter.Flush();
SslStream sslStream = new SslStream(clientStream, false);
sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, true);
}
在这个代码段中,可以看到定义了一个X509Certificate2 _certificate
,这个证书需要使用像makecert.exe
这样的工具来创建自签名证书。已经包含了一个证书文件在源代码中,以便服务器能够运行,但因为它不包含私钥,所以实际上处理SSL流量时,需要运行makecert.exe
。
在HttpWebRequest
对象上,如果使用Windows Internet选项来指定使用代理服务器,需要将代理属性设置为null
。这是因为HttpWebRequest
会默认使用Windows的Internet设置,而将有一个代理服务器试图将自己作为代理服务器使用!