C # 'da basit bir proxy nasıl oluşturulur?


143

Privoxy'yi birkaç hafta önce indirdim ve eğlence için bunun basit bir versiyonunun nasıl yapılabileceğini merak ettim.

Proxy'ye istek göndermek için tarayıcıyı (istemci) yapılandırmam gerektiğini anlıyorum. Proxy isteği web'e gönderir (bunun bir http proxy olduğunu varsayalım). Proxy yanıtı alacaktır ... ancak proxy isteği tarayıcıya (istemciye) nasıl geri gönderebilir?

Web'de C # ve http proxy için arama yaptım, ancak sahnenin arkasında nasıl çalıştığını anlamama izin veren bir şey bulamadım. (Ters proxy istemiyorum ama emin değilim) inanıyorum.

Bu küçük projeye devam etmeme izin verecek herhangi bir açıklamanız veya bilginiz var mı?

Güncelleme

Anladığım şey bu (aşağıdaki grafiğe bakın).

Adım 1 İstemciyi (tarayıcı) Proxy dinlediği bağlantı noktasından 127.0.0.1'e gönderilecek tüm istek için yapılandırıyorum. Bu şekilde, istek doğrudan Internet'e gönderilmez, ancak proxy tarafından işlenir.

Adım2 Proxy yeni bir bağlantı görür, HTTP üstbilgisini okur ve yürütmesi gereken isteği görür. Talebi yerine getirir.

Adım3 Proxy istekten bir yanıt alır. Şimdi web'den müşteriye cevap göndermeli ama nasıl ???

alternatif metin

Faydalı bağlantı

Mentalis Proxy : Proxy olan bu projeyi buldum (ama istediğimden daha fazla). Kaynağı kontrol edebilirim ama gerçekten konsepti daha iyi anlamak için temel bir şey istedim.

ASP Proxy : Ben de burada bazı bilgiler alabilirsiniz.

Reflektör isteme : Bu basit bir örnektir.

İşte Basit Http Proxy'si olan bir Git Hub Deposu .


2015'te 2008 ekran görüntüm yok. Üzgünüm.
Patrick Desjardins

Aslında, archive.org'un buna sahip olduğu ortaya çıkıyor . Sizi rahatsız ettiğimiz için özür dileriz.
Ilmari Karonen

Yanıtlar:


35

Sen ile bir tane yapabilir HttpListener, gelen istekleri ve dinlemek için sınıfa HttpWebRequestisteklerini aktarmak amacıyla sınıfa.


Nereye geçiyorum? Bilgileri nereye geri göndereceğimi nasıl bilebilirim? Tarayıcı, 9999'da istemcinin isteği alıp web'e gönderdiğini söyledi. Bir cevap alın ... danışan ne yapıyor? Hangi adrese gönderilsin?
Patrick Desjardins

2
HttpListener kullanıyorsanız, HttpListener.GetContext () yanıtını yazmanız yeterlidir. Response.OutputStream. Adresin bakımına gerek yok.
OregonGhost

İlginç, bu şekilde kontrol edeceğim.
Patrick Desjardins

8
Bunun için HttpListener kullanmam. Bunun yerine bir ASP.NET uygulaması oluşturun ve uygulamayı IIS içinde barındırın. HttpListener kullanırken, IIS tarafından sağlanan işlem modelinden vazgeçersiniz. Bu, süreç yönetimi (başlatma, hata tespiti, geri dönüşüm), iş parçacığı havuzu yönetimi vb. Gibi şeyleri kaybettiğiniz anlamına gelir.
Mauricio Scheffer

2
Yani, birçok istemci bilgisayar için kullanmayı planlıyorsanız ... bir oyuncak proxy için HttpListener tamam ...
Mauricio Scheffer

94

HttpListener ya da onun gibi bir şey kullanmazdım, bu şekilde çok fazla sorunla karşılaşacaksınız.

En önemlisi, desteklenmesi büyük bir acı olacaktır:

  • Proxy Canlılar
  • SSL çalışmaz (doğru bir şekilde pop-up'lar alırsınız)
  • .NET kitaplıkları kesinlikle bazı isteklerin başarısız olmasına neden olan RFC'leri izler (IE, FF ve dünyadaki herhangi bir tarayıcı çalışsa bile).

Yapmanız gereken:

  • TCP bağlantı noktasını dinleme
  • Tarayıcı isteğini ayrıştırma
  • TCP düzeyinde bu ana bilgisayara Host bağlantısını ayıklayın
  • Özel üstbilgiler vb. Eklemek istemiyorsanız her şeyi ileri ve geri yönlendirin.

Farklı gereksinimlerle .NET'te 2 farklı HTTP proxy yazdım ve bunu yapmanın en iyi yolu olduğunu söyleyebilirim.

Mentalis bunu yapıyor, ama onların kodu "delege spagetti", GoTo daha kötü :)


1
TCP bağlantıları için hangi sınıfları kullandınız?
Cameron

8
@cameron TCPListener ve SslStream.
dr. kötü

2
HTTPS'nin neden işe yaramadığına dair size bilgi verebilir misiniz?
Restuta

10
SSL'nin çalışması için @Restuta, TCP düzeyinde dokunmadan bağlantıyı yönlendirmelisiniz ve HttpListener bunu yapamaz. SSL'nin nasıl çalıştığını okuyabilir ve hedef sunucuda kimlik doğrulaması gerektirdiğini görürsünüz. Bu nedenle istemci google.com'a bağlanmaya çalışacak, ancak aslında google.com olmayan Httplistener'ınızı bağlayacak ve bir sertifika uyumsuzluğu hatası alacak ve dinleyiciniz imzalı sertifika kullanamayacağı için yanlış sertifika vb. Alacaksınız. istemcinin kullanacağı bilgisayara bir CA yükleyerek. Oldukça kirli bir çözüm.
dr. kötü

1
@ dr.evil: +++ 1 şaşırtıcı ipuçları için teşekkürler, ancak verileri istemciye (tarayıcı) nasıl geri göndereceğimi merak ediyorum, TcpClient'e nasıl yanıt verebileceğimi söyleyeyim?
saber

26

Son zamanlarda hafif bir proxy c # .net TcpListener ve TcpClient kullanarak yazdım .

https://github.com/titanium007/Titanium-Web-Proxy

Güvenli HTTP'yi doğru şekilde destekler, istemci makinenin proxy tarafından kullanılan kök sertifikaya güvenmesi gerekir. Ayrıca WebSockets geçişini de destekler. HTTP 1.1'in tüm özellikleri, ardışık düzen hariç desteklenir. Pipelining zaten birçok modern tarayıcı tarafından kullanılmamaktadır. Ayrıca Windows kimlik doğrulamasını (düz, özet) destekler.

Projeye başvurarak uygulamanızı bağlayabilir ve ardından tüm trafiği görebilir ve değiştirebilirsiniz. (İstek ve yanıt).

Performans açısından, makinemde test ettim ve fark edilir bir gecikme olmadan çalışıyor.


ve hala 2020'de, paylaşım için teşekkürler :)
Mark Adamson

20

Proxy aşağıdaki şekilde çalışabilir.

Adım 1, istemciyi proxyHost: proxyPort kullanacak şekilde yapılandırın.

Proxy, proxyHost: proxyPort üzerinde dinleyen bir TCP sunucusudur. Tarayıcı Proxy ile bağlantıyı açar ve Http isteği gönderir. Proxy bu isteği ayrıştırır ve "Ana Bilgisayar" üstbilgisini algılamaya çalışır. Bu başlık Proxy'ye bağlantıyı nerede açacağını söyleyecektir.

Adım 2: Proxy, "Ana Bilgisayar" başlığında belirtilen adrese bağlantı açar. Ardından bu uzak sunucuya HTTP isteği gönderir. Yanıtı okur.

Adım 3: Uzak HTTP sunucusundan yanıt okunduktan sonra, Proxy yanıtı tarayıcıyla daha önce açılan bir TCP bağlantısı üzerinden gönderir.

Şematik olarak şöyle görünecektir:

Browser                            Proxy                     HTTP server
  Open TCP connection  
  Send HTTP request  ----------->                       
                                 Read HTTP header
                                 detect Host header
                                 Send request to HTTP ----------->
                                 Server
                                                      <-----------
                                 Read response and send
                   <-----------  it back to the browser
Render content

14

Sadece trafiği durdurmak istiyorsanız, proxy oluşturmak için kemancı çekirdeğini kullanabilirsiniz ...

http://fiddler.wikidot.com/fiddlercore

ne yaptığını görmek için UI ile önce kemancı çalıştırın, http / https trafiğinde hata ayıklamanıza izin veren bir proxy'dir. C # ile yazılmıştır ve kendi uygulamalarınıza oluşturabileceğiniz bir çekirdeğe sahiptir.

Unutmayın FiddlerCore ticari uygulamalar için ücretsiz değildir.


6

HTTPListener kullanıyorsanız, çok fazla sorun yaşayacaksınız, istekleri ayrıştırmanız ve başlıklarla ve ...

  1. Tarayıcı isteklerini dinlemek için tcp dinleyicisini kullanma
  2. isteğin yalnızca ilk satırını ayrıştırın ve bağlanmak için ana bilgisayar etki alanını ve bağlantı noktasını alın
  3. kesin ham isteği tarayıcı isteğinin ilk satırında bulunan ana bilgisayara gönderin
  4. hedef siteden veri almak (bu bölümde sorunum var)
  5. ana bilgisayardan alınan kesin verileri tarayıcıya gönderme

tarayıcı isteğinde ne olduğunu bilmenize ve ayrıştırmanıza gerek olmadığını görüyorsunuz, yalnızca ilk satırdan hedef site adresini alın ilk satır genellikle bu GET'i beğenir http://google.com HTTP1.1 veya CONNECT facebook.com: 443 (bu, SSL istekleri içindir)



5

Socks4 uygulaması çok basit bir protokoldür. İlk bağlantıyı dinler, istemci tarafından istenen ana bilgisayara / bağlantı noktasına bağlanır, başarı kodunu istemciye gönderirsiniz, sonra giden ve gelen akışları soketler üzerinden iletirsiniz.

HTTP ile giderseniz, bazı HTTP başlıklarını okuyup muhtemelen ayarlamanız / kaldırmanız gerekir, böylece bu biraz daha fazla iştir.

Doğru hatırlarsam SSL, HTTP ve Socks proxy'lerinde çalışır. Bir HTTP proxy için, yukarıda açıklandığı gibi socks4'e çok benzeyen CONNECT fiilini uygularsınız, ardından istemci proxy bağlantısını proxy sunucusundaki tcp akışı üzerinden açar.


2

Tarayıcı proxy'ye bağlanır, böylece proxy'nin web sunucusundan aldığı veriler, tarayıcının proxy'ye başlattığı bağlantıyla gönderilir.


2

Değer için ne, burada HttpListener ve HttpClient dayalı bir C # örnek asenkron uygulaması (Android cihazlarda Chrome'u IIS Express'e bağlamak için kullanıyorum, bulduğum tek yol bu ...).

HTTPS desteğine ihtiyacınız varsa, daha fazla kod gerektirmez, sadece sertifika yapılandırması gerekir: HTTPS desteğiyle Httplistener

// define http://localhost:5000 and http://127.0.0.1:5000/ to be proxies for http://localhost:53068
using (var server = new ProxyServer("http://localhost:53068", "http://localhost:5000/", "http://127.0.0.1:5000/"))
{
    server.Start();
    Console.WriteLine("Press ESC to stop server.");
    while (true)
    {
        var key = Console.ReadKey(true);
        if (key.Key == ConsoleKey.Escape)
            break;
    }
    server.Stop();
}

....

public class ProxyServer : IDisposable
{
    private readonly HttpListener _listener;
    private readonly int _targetPort;
    private readonly string _targetHost;
    private static readonly HttpClient _client = new HttpClient();

    public ProxyServer(string targetUrl, params string[] prefixes)
        : this(new Uri(targetUrl), prefixes)
    {
    }

    public ProxyServer(Uri targetUrl, params string[] prefixes)
    {
        if (targetUrl == null)
            throw new ArgumentNullException(nameof(targetUrl));

        if (prefixes == null)
            throw new ArgumentNullException(nameof(prefixes));

        if (prefixes.Length == 0)
            throw new ArgumentException(null, nameof(prefixes));

        RewriteTargetInText = true;
        RewriteHost = true;
        RewriteReferer = true;
        TargetUrl = targetUrl;
        _targetHost = targetUrl.Host;
        _targetPort = targetUrl.Port;
        Prefixes = prefixes;

        _listener = new HttpListener();
        foreach (var prefix in prefixes)
        {
            _listener.Prefixes.Add(prefix);
        }
    }

    public Uri TargetUrl { get; }
    public string[] Prefixes { get; }
    public bool RewriteTargetInText { get; set; }
    public bool RewriteHost { get; set; }
    public bool RewriteReferer { get; set; } // this can have performance impact...

    public void Start()
    {
        _listener.Start();
        _listener.BeginGetContext(ProcessRequest, null);
    }

    private async void ProcessRequest(IAsyncResult result)
    {
        if (!_listener.IsListening)
            return;

        var ctx = _listener.EndGetContext(result);
        _listener.BeginGetContext(ProcessRequest, null);
        await ProcessRequest(ctx).ConfigureAwait(false);
    }

    protected virtual async Task ProcessRequest(HttpListenerContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        var url = TargetUrl.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
        using (var msg = new HttpRequestMessage(new HttpMethod(context.Request.HttpMethod), url + context.Request.RawUrl))
        {
            msg.Version = context.Request.ProtocolVersion;

            if (context.Request.HasEntityBody)
            {
                msg.Content = new StreamContent(context.Request.InputStream); // disposed with msg
            }

            string host = null;
            foreach (string headerName in context.Request.Headers)
            {
                var headerValue = context.Request.Headers[headerName];
                if (headerName == "Content-Length" && headerValue == "0") // useless plus don't send if we have no entity body
                    continue;

                bool contentHeader = false;
                switch (headerName)
                {
                    // some headers go to content...
                    case "Allow":
                    case "Content-Disposition":
                    case "Content-Encoding":
                    case "Content-Language":
                    case "Content-Length":
                    case "Content-Location":
                    case "Content-MD5":
                    case "Content-Range":
                    case "Content-Type":
                    case "Expires":
                    case "Last-Modified":
                        contentHeader = true;
                        break;

                    case "Referer":
                        if (RewriteReferer && Uri.TryCreate(headerValue, UriKind.Absolute, out var referer)) // if relative, don't handle
                        {
                            var builder = new UriBuilder(referer);
                            builder.Host = TargetUrl.Host;
                            builder.Port = TargetUrl.Port;
                            headerValue = builder.ToString();
                        }
                        break;

                    case "Host":
                        host = headerValue;
                        if (RewriteHost)
                        {
                            headerValue = TargetUrl.Host + ":" + TargetUrl.Port;
                        }
                        break;
                }

                if (contentHeader)
                {
                    msg.Content.Headers.Add(headerName, headerValue);
                }
                else
                {
                    msg.Headers.Add(headerName, headerValue);
                }
            }

            using (var response = await _client.SendAsync(msg).ConfigureAwait(false))
            {
                using (var os = context.Response.OutputStream)
                {
                    context.Response.ProtocolVersion = response.Version;
                    context.Response.StatusCode = (int)response.StatusCode;
                    context.Response.StatusDescription = response.ReasonPhrase;

                    foreach (var header in response.Headers)
                    {
                        context.Response.Headers.Add(header.Key, string.Join(", ", header.Value));
                    }

                    foreach (var header in response.Content.Headers)
                    {
                        if (header.Key == "Content-Length") // this will be set automatically at dispose time
                            continue;

                        context.Response.Headers.Add(header.Key, string.Join(", ", header.Value));
                    }

                    var ct = context.Response.ContentType;
                    if (RewriteTargetInText && host != null && ct != null &&
                        (ct.IndexOf("text/html", StringComparison.OrdinalIgnoreCase) >= 0 ||
                        ct.IndexOf("application/json", StringComparison.OrdinalIgnoreCase) >= 0))
                    {
                        using (var ms = new MemoryStream())
                        {
                            using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                            {
                                await stream.CopyToAsync(ms).ConfigureAwait(false);
                                var enc = context.Response.ContentEncoding ?? Encoding.UTF8;
                                var html = enc.GetString(ms.ToArray());
                                if (TryReplace(html, "//" + _targetHost + ":" + _targetPort + "/", "//" + host + "/", out var replaced))
                                {
                                    var bytes = enc.GetBytes(replaced);
                                    using (var ms2 = new MemoryStream(bytes))
                                    {
                                        ms2.Position = 0;
                                        await ms2.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
                                    }
                                }
                                else
                                {
                                    ms.Position = 0;
                                    await ms.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
                                }
                            }
                        }
                    }
                    else
                    {
                        using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                        {
                            await stream.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
                        }
                    }
                }
            }
        }
    }

    public void Stop() => _listener.Stop();
    public override string ToString() => string.Join(", ", Prefixes) + " => " + TargetUrl;
    public void Dispose() => ((IDisposable)_listener)?.Dispose();

    // out-of-the-box replace doesn't tell if something *was* replaced or not
    private static bool TryReplace(string input, string oldValue, string newValue, out string result)
    {
        if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(oldValue))
        {
            result = input;
            return false;
        }

        var oldLen = oldValue.Length;
        var sb = new StringBuilder(input.Length);
        bool changed = false;
        var offset = 0;
        for (int i = 0; i < input.Length; i++)
        {
            var c = input[i];

            if (offset > 0)
            {
                if (c == oldValue[offset])
                {
                    offset++;
                    if (oldLen == offset)
                    {
                        changed = true;
                        sb.Append(newValue);
                        offset = 0;
                    }
                    continue;
                }

                for (int j = 0; j < offset; j++)
                {
                    sb.Append(input[i - offset + j]);
                }

                sb.Append(c);
                offset = 0;
            }
            else
            {
                if (c == oldValue[0])
                {
                    if (oldLen == 1)
                    {
                        changed = true;
                        sb.Append(newValue);
                    }
                    else
                    {
                        offset = 1;
                    }
                    continue;
                }

                sb.Append(c);
            }
        }

        if (changed)
        {
            result = sb.ToString();
            return true;
        }

        result = input;
        return false;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.