Why not Fiddler core?
When it comes to Fiddler core, you may not be familiar with it, so its brother Fiddler is familiar with it. I usually use it to grab packets, simulate low bandwidth, and modify requests. Fiddler is essentially an HTTP proxy server.
FiddlerCore is the core component of Fiddler's UI, which can be used for secondary development. As shown in the figure below:
Fiddler feels uncomfortable in the following aspects:
- API naming is not standardized and property / field is mixed.
Pascal is widely used in. NET, and Fiddler's naming is like a hodgepodge. For example: public member field, CONFIG class name, oSession parameter name
- Asynchronous is not supported. All callbacks are synchronous.
- The frame design is unreasonable, does not support multiple instances, and uses a lot of static methods. It's not easy to expand all the methods piled into the Session class. At least Request/Response should be separated.
As for the above points, I feel that this framework has no design at all. If there is no other choice, Fiddler core may be used. Fortunately, on Github, someone has implemented Fiddler core step by step
Introduction to titanium web proxy
A cross platform, lightweight, low memory, high-performance HTTP(S) proxy server, development language C#
https://github.com/justcoding121/Titanium-Web-Proxy
Functional features
- Supports most functions of HTTP(S) and HTTP 1.1
- Support for redirect/block/update requests
- Support update Response
- Support WebSocket hosted by HTTP
- Support mutual SSL authentication
- Fully asynchronous agent
- Support agent authorization and automatic agent detection
- Kerberos/NTLM authentication over HTTP protocols for windows domain
Use
Install NuGet package
Install-Package Titanium.Web.Proxy
Support
- . Net Standard 1.6 or higher
- . Net Framework 4.5 or higher
Set up HTTP proxy
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();
Simple request and response processing
//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); }
Future Roadmap
- Support for HTTP 2.0
- Support for Socks protocol