推薦一個比FiddlerCore好用的HTTP(S)代理服務器

原文: 推薦一個比FiddlerCore好用的HTTP(S)代理服務器

爲何不用FiddlerCore?

說到FiddlerCore你們可能會比較陌生,那麼它哥Fiddler就比較熟悉了;抓包、模擬低帶寬、修改請求我平時比較經常使用。Fiddler的本質就是一個HTTP代理服務器。
FiddlerCore是Fiddler去除了UI的核心組件,能夠用於二次開發。以下圖所示:


html

Fiddler主要有如下幾點感受很彆扭:git

  1. API命名不規範、屬性/字段混用。
    .NET中廣泛採用Pascal命名規範,而Fiddler的命名就像個大雜燴。例如:public成員用字段,CONFIG類名,oSession參數名
  2. 不支持異步。全部的回調都是同步。
  3. 框架設計不合理,不支持多實例,大量使用了靜態方法。全部方法都堆到了Session類中不易於擴展,起碼Request/Response也得分開啊。

單就以上幾點我感受這框架徹底沒有設計可言,若是沒有別的選擇FiddlerCore我或許就將就用了,好在Github上已經有人先一步從新實現了FiddlerCoregithub

Titanium-Web-Proxy介紹

一個跨平臺、輕量級、低內存、高性能的HTTP(S)代理服務器,開發語言爲C#web

https://github.com/justcoding121/Titanium-Web-Proxywindows

功能特性

  1. 支持HTTP(S)與HTTP 1.1的大部分功能
  2. 支持redirect/block/update 請求
  3. 支持更新Response
  4. 支持HTTP承載的WebSocket
  5. Support mutual SSL authentication
  6. 徹底異步的代理
  7. 支持代理受權與自動代理檢測
  8. Kerberos/NTLM authentication over HTTP protocols for windows domain

使用

安裝NuGet包服務器

Install-Package Titanium.Web.Proxy框架

支持dom

  • .Net Standard 1.6或更高
  • .Net Framework 4.5或更高

設置HTTP代理異步

var proxyServer = new ProxyServer();

//locally trust root certificate used by this proxy 
proxyServer.TrustRootCertificate = true;

//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;

proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;


var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude HTTPS addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }

//Use self-issued generic certificate on all HTTPS requests
//Optimizes performance by not creating a certificate for each HTTPS-enabled domain
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};

//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();

//Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//to send data to this endPoint
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
	//Generic Certificate hostname to use
	//when SNI is disabled by client
	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);

//Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);

//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();

//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;

proxyServer.Stop();

簡單的請求與響應處理async

//To access requestBody from OnResponse handler
private IDictionary<Guid, string> requestBodyHistory 
        = new ConcurrentDictionary<Guid, string>();

public async Task OnRequest(object sender, SessionEventArgs e) {
    Console.WriteLine(e.WebSession.Request.Url);

    ////read request headers
    var requestHeaders = e.WebSession.Request.RequestHeaders;

    var method = e.WebSession.Request.Method.ToUpper();
    if ((method == "POST" || method == "PUT" || method == "PATCH"))
    {
	//Get/Set request body bytes
	byte[] bodyBytes = await e.GetRequestBody();
	await e.SetRequestBody(bodyBytes);

	//Get/Set request body as string
	string bodyString = await e.GetRequestBodyAsString();
	await e.SetRequestBodyString(bodyString);
	
	//store request Body/request headers etc with request Id as key
	//so that you can find it from response handler using request Id
  	requestBodyHistory[e.Id] = bodyString;
    }

    //To cancel a request with a custom HTML content
    //Filter URL
    if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
    {
	await e.Ok("<!DOCTYPE html>" +
	      "<html><body><h1>" +
	      "Website Blocked" +
	      "</h1>" +
	      "<p>Blocked by titanium web proxy.</p>" +
	      "</body>" +
	      "</html>");
    }
    //Redirect example
    if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
    {
	await e.Redirect("https://www.paypal.com");
    }
}

//Modify response
public async Task OnResponse(object sender, SessionEventArgs e) {
    //read response headers
    var responseHeaders = e.WebSession.Response.ResponseHeaders;

    //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
    if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
    {
	if (e.WebSession.Response.ResponseStatusCode == "200")
	{
	    if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
	    {
		byte[] bodyBytes = await e.GetResponseBody();
		await e.SetResponseBody(bodyBytes);

		string body = await e.GetResponseBodyAsString();
		await e.SetResponseBodyString(body);
	    }
	}
    }
    
    //access request body/request headers etc by looking up using requestId
    if(requestBodyHistory.ContainsKey(e.Id))
    {
	var requestBody = requestBodyHistory[e.Id];
    }
}

/// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) {
    //set IsValid to true/false based on Certificate Errors
    if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
	e.IsValid = true;

    return Task.FromResult(0);
}

/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e) {
    //set e.clientCertificate to override
    return Task.FromResult(0);
}

將來路線圖

  • 支持HTTP 2.0
  • 支持Socks協議
相關文章
相關標籤/搜索