C # में एक साधारण प्रॉक्सी कैसे बनाएं?


143

मैं Privoxy डाउनलोड किया है कुछ सप्ताह पहले और मनोरंजन के लिए मुझे पता है कि यह कैसे का एक सरल संस्करण किया जा सकता है उत्सुक था।

मैं समझता हूं कि प्रॉक्सी को अनुरोध भेजने के लिए मुझे ब्राउज़र (क्लाइंट) को कॉन्फ़िगर करने की आवश्यकता है। प्रॉक्सी वेब को अनुरोध भेजती है (मान लीजिए कि यह एक HTTP प्रॉक्सी है)। प्रॉक्सी को जवाब मिल जाएगा ... लेकिन प्रॉक्सी ब्राउज़र (क्लाइंट) को अनुरोध वापस कैसे भेज सकता है?

मैं सी # के लिए वेब और HTTP प्रॉक्सी पर खोज की है, लेकिन कुछ है जो मुझे समझ कैसे यह दृश्य के पीछे सही ढंग से काम करता है नहीं मिली है। (मेरा मानना ​​है कि मैं एक रिवर्स प्रॉक्सी नहीं चाहता, लेकिन मुझे यकीन नहीं है)।

क्या आप में से किसी के पास कुछ अन्वेषण या कुछ जानकारी है जो मुझे इस छोटे से प्रोजेक्ट को जारी रखने देगी?

अपडेट करें

यह वही है जो मैं समझता हूं (नीचे ग्राफिक देखें)।

चरण 1 मैं क्लाइंट (ब्राउज़र) को कॉन्फ़िगर करने के लिए सभी अनुरोधों को प्रॉक्सी सुनने के लिए 127.0.0.1 पोर्ट पर भेजने के लिए कॉन्फ़िगर करता हूं। इस तरह, अनुरोध सीधे इंटरनेट पर नहीं भेजा जाएगा, लेकिन प्रॉक्सी द्वारा संसाधित किया जाएगा।

Step2 प्रॉक्सी एक नया कनेक्शन देखता है, HTTP हेडर पढ़ता है और वह अनुरोध देखता है जिसे उसे निष्पादित करना होगा। वह अनुरोध निष्पादित करता है।

चरण 3 प्रॉक्सी अनुरोध से उत्तर प्राप्त करती है। अब उसे वेब से क्लाइंट को जवाब भेजना होगा लेकिन कैसे ???

वैकल्पिक शब्द

उपयोगी लिंक

मेंटलिस प्रॉक्सी : मुझे यह प्रोजेक्ट मिला है जो एक प्रॉक्सी है (लेकिन अधिक यह है कि मैं चाहूंगा)। मैं स्रोत की जांच कर सकता हूं, लेकिन मैं वास्तव में कुछ अवधारणा को समझना चाहता हूं।

ASP प्रॉक्सी : मैं यहाँ पर कुछ जानकारी प्राप्त करने में सक्षम हो सकता हूँ।

अनुरोध परावर्तक : यह एक सरल उदाहरण है।

यहाँ एक सरल हब प्रॉक्सी के साथ एक गिट हब रिपॉजिटरी है


मैं 2015 में 2008 के एक स्क्रीनशॉट की जरूरत नहीं है क्षमा करें।
पैट्रिक डेसजार्डिन्स

वास्तव में, यह पता चला है कि archive.org यह है । आपको परेशान करने के लिए माफी चाहता हूं।
Ilmari कारोनेन

जवाबों:


35

आप HttpListenerआने वाले अनुरोधों को सुनने के लिए वर्ग के साथ एक बना सकते हैं और अनुरोधों HttpWebRequestको रिले करने के लिए वर्ग।


मैं कहां रिले करूं? मुझे कैसे पता कर सकते हैं जहां वापस जानकारी भेजने के लिए? ब्राउज़र ने कहा कि 127.0.0.1:9999 ग्राहकों को 9999 पर ग्राहक अनुरोध प्राप्त करता है और इसे वेब पर भेजता है। एक जवाब ... से क्या ग्राहक कर प्राप्त करें? किस पते पर भेजें?
पैट्रिक डेसजार्डिन्स

2
यदि आप HttpListener का उपयोग कर रहे हैं, तो आप बस HttpListener.GetConxtxt () पर प्रतिक्रिया लिखें। Response.OutputStream पते की परवाह करने की जरूरत नहीं है।
ओरेगॉनहोस्ट

दिलचस्प है, मैं इस तरह से जाँच करूँगा।
पैट्रिक डेसजार्डिन्स

8
मैं इसके लिए HttpListener का उपयोग नहीं होता। इसके बजाय, ASP.NET ऐप बनाएं और इसे IIS के भीतर होस्ट करें। HttpListener का उपयोग करते समय, आप आईआईएस द्वारा प्रदान की प्रक्रिया मॉडल को दे रहे हैं। इसका मतलब है कि आप प्रक्रिया प्रबंधन (स्टार्टअप, विफलता का पता लगाने, रीसाइक्लिंग), थ्रेड पूल प्रबंधन, आदि जैसी चीजों को खो देते हैं।
मौरिसियो शेफ़र

2
यही कारण है, अगर आप कई ग्राहक कंप्यूटर के लिए इसका इस्तेमाल करने के ... एक खिलौना प्रॉक्सी HttpListener के लिए इरादा ठीक है ...
मौरिसियो शेफ़र

93

मैं HttpListener या इस तरह से कुछ का उपयोग नहीं करेगा, इस तरह से आप इतने सारे मुद्दों पर आएँगे।

सबसे महत्वपूर्ण बात यह है कि यह एक बड़ा दर्द होगा:

  • प्रॉक्सी कीप-अलाइव्स
  • SSL काम नहीं करेगा (एक सही तरीके से, आपको पॉपअप मिलेगा)
  • नेट पुस्तकालयों सख्ती से RFC, जो विफल कुछ अनुरोध का कारण बनता है इस प्रकार है (भले ही आईई, एफएफ और दुनिया में किसी भी अन्य ब्राउज़र काम करेंगे।)

आपको क्या करने की आवश्यकता है:

  • एक TCP पोर्ट सुनो
  • ब्राउज़र अनुरोध को पार्स
  • टीसीपी स्तर में होस्ट को उस होस्ट से कनेक्ट करें
  • जब तक आप कस्टम हेडर आदि नहीं जोड़ना चाहते, तब तक सब कुछ आगे-पीछे करें।

मैंने अलग-अलग आवश्यकताओं के साथ .NET में 2 अलग-अलग HTTP प्रॉक्सी लिखी हैं और मैं आपको बता सकता हूं कि यह करने का सबसे अच्छा तरीका है।

Mentalis यह कर, लेकिन उनके कोड "प्रतिनिधि स्पेगेटी", गोटो से भी बदतर है :)


1
क्या वर्ग (ते) आप TCP कनेक्शन के लिए उपयोग किया?
कैमरून

8
@cameron TCPListener और SslStream।
डॉ। बुराई

2
क्या आप कृपया यह बता सकते हैं कि एचटीटीपीएस काम क्यों नहीं करेगा?
रेस्टुटा

10
SSL के लिए @Restuta काम करने के लिए आपको वास्तव में टीसीपी स्तर पर इसे छूने के बिना कनेक्शन को अग्रेषित करना चाहिए और HttpListener ऐसा नहीं कर सकता। आप पढ़ सकते हैं कि कैसे एसएसएल काम करता है और आप इसे लक्ष्य सर्वर से प्रमाणित करने की आवश्यकता होती है देखेंगे। तो क्लाइंट google.com से कनेक्ट करने का प्रयास करेगा, लेकिन वास्तव में आपके Httplistener को कनेक्ट करेगा जो कि google.com नहीं है और इसे एक प्रमाणित बेमेल त्रुटि मिलेगी और चूंकि आपका श्रोता हस्ताक्षरित प्रमाण पत्र का उपयोग नहीं करेगा, तो आपको गलत प्रमाणपत्र मिल जाएगा आदि। यह कंप्यूटर के लिए एक CA स्थापित करके है कि ग्राहक हालांकि उपयोग करेगा। यह काफी गंदा घोल है।
डॉ। बुराई

1
@ dr.evil: +++ 1 अद्भुत सुझावों के लिए धन्यवाद, लेकिन मैं (ब्राउज़र) ग्राहक के लिए डेटा वापस भेजने के लिए कैसे उत्सुक हूँ, मान लीजिए कि मैं TcpClient है कि कैसे मैं ग्राहक के जवाब वापस भेजना चाहिए?
कृपाण

26

मैंने हाल ही में C # .net में TcpListener और TcpClient का उपयोग करते हुए एक हल्का वजन प्रॉक्सी लिखा है ।

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

यह सुरक्षित HTTP सही तरीका, विश्वास जड़ प्रॉक्सी द्वारा प्रयोग किया जाता प्रमाण पत्र के लिए क्लाइंट मशीन की जरूरत का समर्थन करता है। WebSockets रिले का भी समर्थन करता है। HTTP 1.1 की सभी विशेषताओं को पाइपलाइनिंग के अलावा समर्थन किया गया है। वैसे भी अधिकांश आधुनिक ब्राउज़रों द्वारा पाइपलाइनिंग का उपयोग नहीं किया जाता है। साथ ही विंडोज़ प्रमाणीकरण (सादा, डाइजेस्ट) का समर्थन करता है।

आप परियोजना को संदर्भित करके अपने आवेदन को हुक कर सकते हैं और फिर सभी ट्रैफ़िक को देख और संशोधित कर सकते हैं। (अनुरोध और प्रतिक्रिया)।

जहां तक ​​प्रदर्शन की बात है, मैंने इसे अपनी मशीन पर परीक्षण किया है और बिना किसी देरी के काम करता है।


और अभी भी, 2020 में बनाए रखा साझा करने के लिए :) धन्यवाद
मार्क एडमसन

19

प्रॉक्सी निम्नलिखित तरीके से काम कर सकता है।

Step1, क्लाइंट को प्रॉक्सीहॉट का उपयोग करने के लिए कॉन्फ़िगर करें: प्रॉक्सीपोर्ट।

प्रॉक्सी एक टीसीपी सर्वर है जो प्रॉक्सीहॉट: प्रॉक्सीपॉर्ट पर सुन रहा है। ब्राउज़र प्रॉक्सी के साथ कनेक्शन खोलता है और Http अनुरोध भेजता है। प्रॉक्सी इस अनुरोध को पार्स करता है और "होस्ट" शीर्षक का पता लगाने की कोशिश करता है। यह शीर्षलेख प्रॉक्सी को बताएगा कि कनेक्शन कहां खोला जाए।

चरण 2: प्रॉक्सी "होस्ट" हेडर में निर्दिष्ट पते से कनेक्शन खोलता है। फिर यह उस रिमोट सर्वर से HTTP रिक्वेस्ट भेजता है। प्रतिक्रिया देता है।

चरण 3: के बाद प्रतिक्रिया दूरस्थ HTTP सर्वर से पढ़ा जाता है, प्रॉक्सी ब्राउज़र के साथ एक पहले खोला TCP कनेक्शन के माध्यम से प्रतिक्रिया भेजता है।

योजनाबद्ध रूप से यह इस तरह दिखेगा:

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

यदि आप ट्रैफ़िक को रोकना चाहते हैं, तो आप प्रॉक्सी बनाने के लिए फ़िडलर कोर का उपयोग कर सकते हैं ...

http://fiddler.wikidot.com/fiddlercore

यह क्या करता है देखने के लिए UI के साथ पहले फ़िडलर चलाएं, यह एक प्रॉक्सी है जो आपको http / https ट्रैफ़िक को डीबग करने की अनुमति देता है। यह c # में लिखा गया है और इसमें एक कोर है जिसे आप अपने स्वयं के अनुप्रयोगों में बना सकते हैं।

ध्यान रखें कि FiddlerCore व्यावसायिक अनुप्रयोगों के लिए स्वतंत्र नहीं है।



5

अगर आप HTTPListener का उपयोग करते हैं, तो आपको कई समस्याओं का सामना करना पड़ेगा, आपको अनुरोधों को पार्स करना होगा और हेडर से जुड़ना होगा ...

  1. ब्राउज़र अनुरोधों को सुनने के लिए tcp श्रोता का उपयोग करें
  2. कनेक्ट करने के लिए अनुरोध का केवल पहला लाइन को पार्स और होस्ट डोमेन और बंदरगाह मिल
  3. ब्राउज़र अनुरोध की पहली पंक्ति में पाए गए होस्ट को सटीक कच्चा अनुरोध भेजें
  4. लक्ष्य साइट से डेटा प्राप्त करें (मुझे इस अनुभाग में समस्या है)
  5. होस्ट से प्राप्त सटीक डेटा को ब्राउज़र में भेजें

आप देखते हैं कि आपको यह भी पता करने की आवश्यकता नहीं है कि ब्राउज़र अनुरोध में क्या है और इसे पार्स करें, केवल पहली पंक्ति से लक्ष्य साइट का पता प्राप्त करें पहली पंक्ति आमतौर पर इस GET http://google.com HTTP1.1 या CONNECT facebook.com को पसंद करती है : 443 (यह ssl अनुरोधों के लिए है)


4

Socks4 लागू करने के लिए एक बहुत ही सरल प्रोटोकॉल है। आप प्रारंभिक कनेक्शन के लिए सुनते हैं, क्लाइंट से अनुरोध किए गए होस्ट / पोर्ट से कनेक्ट करते हैं, क्लाइंट को सफलता कोड भेजते हैं और फिर सॉकेट्स में आउटगोइंग और इनकमिंग स्ट्रीम को फॉरवर्ड करते हैं।

यदि आप HTTP के साथ जाते हैं तो आपको कुछ HTTP हेडर को पढ़ना और संभवतः सेट / रिमूव करना होगा ताकि थोड़ा और काम हो सके।

अगर मुझे सही से याद है, तो SSL HTTP और सॉक्स प्रॉक्सी में काम करेगा। HTTP प्रॉक्सी के लिए आप CONNECT वर्ब को कार्यान्वित करते हैं, जो ऊपर वर्णित अनुसार मोजे 4 की तरह काम करता है, फिर क्लाइंट प्रॉक्सी कनेक्शन को प्रॉक्सी tcp स्ट्रीम में खोलता है।


2

ब्राउज़र प्रॉक्सी से जुड़ा होता है, इसलिए वेब सर्वर से प्रॉक्सी को जो डेटा मिलता है, उसे उसी कनेक्शन के माध्यम से भेजा जाता है, जो ब्राउज़र ने प्रॉक्सी को शुरू किया था।


1

इसके लायक क्या है, यहाँ HttpListener और HttpClient के आधार पर C # नमूना async कार्यान्वयन है (मैं इसका उपयोग IIS उपकरणों में Chrome को IIS एक्सप्रेस से कनेक्ट करने में सक्षम होने के लिए करता हूं, यही एकमात्र तरीका है जो मैंने पाया ...)।

और अगर आपको HTTPS समर्थन की आवश्यकता है, तो उसे अधिक कोड की आवश्यकता नहीं होनी चाहिए, बस प्रमाणपत्र कॉन्फ़िगरेशन: HTTPS समर्थन के साथ 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;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.