Fiddler应该都知道,抓包、模拟低带宽、修改请求,Fiddler的本质就是一个HTTP代理服务器。FiddlerCore是Fiddler去除了UI的核心组件,可以用于二次开发。Titanium.Web.Proxy是比FiddlerCore更好用的HTTP(S)代理服务器,本文主要介绍一下使用Titanium.Web.Proxy实现Http(s)代理服务器,实现监控HTTP请求的方法,以及相关示例代码。

1、安装引用Titanium.Web.Proxy

Titanium Web Proxyhttps://github.com/justcoding121/Titanium-Web-Proxy

通过NuGet获取Titanium.Web.Proxy

1)使用Nuget管理控制台

Titanium.Web.Proxy集成到项目中的最简单方法是使用NuGet。您可以通过打开包管理器控制台(PM)并键入以下语句来安装Titanium.Web.Proxy

Install-Package Titanium.Web.Proxy

2)使用Nuget图形管理器

使用Nuget的界面的管理器搜索"Titanium.Web.Proxy"=> 找到点出点击"安装"

3)使用.NET CLI命令安装

> dotnet add TodoApi.csproj package Titanium.Web.Proxy

相关文档VS(Visual Studio)中Nuget的使用

2、使用示例代码

1)安装配置HTTP proxy

var proxyServer = new ProxyServer();
// 此代理使用的本地信任根证书 
//proxyServer.CertificateManager.TrustRootCertificate = true;
//proxyServer.CertificateManager.TrustRootCertificate(true); proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.DefaultWindows; proxyServer.CertificateManager.EnsureRootCertificate(); // 可选地设置证书引擎 // 在Mono之下,只有BouncyCastle将得到支持 //proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle; proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeResponse += OnResponse; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) { // 在所有https请求上使用自颁发的通用证书 // 通过不为每个启用http的域创建证书来优化性能 // 当代理客户端不需要证书信任时非常有用 //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") }; // 当接收到连接请求时触发 explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect; // explicit endpoint 是客户端知道代理存在的地方 // 因此,客户端以代理友好的方式发送请求 proxyServer.AddEndPoint(explicitEndPoint); proxyServer.Start(); // 透明endpoint 对于反向代理很有用(客户端不知道代理的存在) // 透明endpoint 通常需要一个网络路由器端口来转发HTTP(S)包或DNS // 发送数据到此endpoint var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) { // 要使用的通用证书主机名 // 当SNI被客户端禁用时 GenericCertificateName = "google.com" }; proxyServer.AddEndPoint(transparentEndPoint); //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; foreach (var endPoint in proxyServer.ProxyEndPoints) Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); // 只有显式代理可以设置为系统代理! proxyServer.SetAsSystemHttpProxy(explicitEndPoint); proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); // 在这里等待(你可以使用其他的函数作为等待函数,我用这个作为演示) Console.Read();

2) 停止退出

// Unsubscribe & Quit
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();

3)请求和响应事件处理

private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
    await Task.Run(() =>
    {
        string hostname = e.HttpClient.Request.RequestUri.Host;
        if (hostname.Contains("dropbox.com"))
        {
            // 排除您不想代理的Https地址
            // 对于使用证书固定的客户端很有用
            // for example dropbox.com
            e.DecryptSsl = false;
        }
    });
}
public async Task OnRequest(object sender, SessionEventArgs e)
{
    Console.WriteLine(e.HttpClient.Request.Url);
    // read request headers
    var requestHeaders = e.HttpClient.Request.Headers;
    var method = e.HttpClient.Request.Method.ToUpper();
    if ((method == "POST" || method == "PUT" || method == "PATCH"))
    {
        // Get/Set request body bytes
        byte[] bodyBytes = await e.GetRequestBody();
        e.SetRequestBody(bodyBytes);
        // Get/Set request body as string
        string bodyString = await e.GetRequestBodyAsString();
        e.SetRequestBodyString(bodyString);
        // store request 
        // 这样你就能从响应处理器中找到它
        e.UserData = e.HttpClient.Request;
    }
    // 取消带有自定义HTML内容的请求
    // Filter URL
    if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("google.com"))
    {
        e.Ok("<!DOCTYPE html>" +
            "<html><body><h1>" +
            "Website Blocked" +
            "</h1>" +
            "<p>Blocked by titanium web proxy.</p>" +
            "</body>" +
            "</html>");
    }
    // Redirect example
    if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
    {
        e.Redirect("https://www.paypal.com");
    }
}
// Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
    // read response headers
    var responseHeaders = e.HttpClient.Response.Headers;
    //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
    if (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST")
    {
        if (e.HttpClient.Response.StatusCode == 200)
        {
            if (e.HttpClient.Response.ContentType != null && e.HttpClient.Response.ContentType.Trim().ToLower().Contains("text/html"))
            {
                byte[] bodyBytes = await e.GetResponseBody();
                e.SetResponseBody(bodyBytes);
                string body = await e.GetResponseBodyAsString();
                e.SetResponseBodyString(body);
            }
        }
    }
    if (e.UserData != null)
    {
        // 从存储在RequestHandler中的UserData属性的访问请求
        var request = (Request)e.UserData;
    }
}
// 允许重写默认的证书验证逻辑
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
    // 根据证书错误,设置IsValid为真/假
    if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
        e.IsValid = true;
    return Task.CompletedTask;
}
// 允许在相互身份验证期间重写默认客户端证书选择逻辑
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
    // set e.clientCertificate to override
    return Task.CompletedTask;
}    

推荐文档