如何在 C# 中创建一个简单的代理?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/226784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:58:05  来源:igfitidea点击:

How to create a simple proxy in C#?

c#.net.net-2.0proxy

提问by Patrick Desjardins

I have downloaded Privoxy few weeks ago and for the fun I was curious to know how a simple version of it can be done.

几周前我下载了 Privoxy,为了好玩,我很想知道它的简单版本是如何完成的。

I understand that I need to configure the browser (client) to send request to the proxy. The proxy send the request to the web (let say it's a http proxy). The proxy will receive the answer... but how can the proxy send back the request to the browser (client)?

我知道我需要配置浏览器(客户端)以向代理发送请求。代理将请求发送到网络(假设它是一个 http 代理)。代理将收到答案......但是代理如何将请求发送回浏览器(客户端)?

I have search on the web for C# and http proxy but haven't found something that let me understand how it works behind the scene correctly. (I believe I do not want a reverse proxy but I am not sure).

我在网上搜索了 C# 和 http 代理,但没有找到让我理解它在幕后如何正确工作的东西。(我相信我不想要反向代理,但我不确定)。

Does any of you have some explication or some information that will let me continue this small project?

你们有没有人有一些解释或一些信息可以让我继续这个小项目?

Update

更新

This is what I understand (see graphic below).

这是我的理解(见下图)。

Step 1I configure the client (browser) for all request to be send to 127.0.0.1 at the port the Proxy listen. This way, request will be not sent to the Internet directly but will be processed by the proxy.

步骤 1我配置客户端(浏览器),以便在代理侦听的端口处将所有请求发送到 127.0.0.1。这样,请求不会直接发送到 Internet,而是由代理处理。

Step2The proxy see a new connection, read the HTTP header and see the request he must executes. He executes the request.

Step2代理看到一个新的连接,读取 HTTP 头并看到他必须执行的请求。他执行请求。

Step3The proxy receive an answer from the request. Now he must send the answer from the web to the client but how???

Step3代理从请求中收到应答。现在他必须将答案从网络发送给客户端,但如何发送???

alt text

替代文字

Useful link

有用的链接

Mentalis Proxy: I have found this project that is a proxy (but more that I would like). I might check the source but I really wanted something basic to understand more the concept.

Mentalis 代理:我发现这个项目是一个代理(但更多的是我想要的)。我可能会检查来源,但我真的想要一些基本的东西来了解更多的概念。

ASP Proxy: I might be able to get some information over here too.

ASP 代理:我也可以从这里获取一些信息。

Request reflector: This is a simple example.

请求反射器:这是一个简单的例子。

Here is a Git Hub Repository with a Simple Http Proxy.

这是一个带有简单 Http 代理Git Hub 存储库

采纳答案by Mark Cidade

You can build one with the HttpListenerclass to listen for incoming requests and the HttpWebRequestclass to relay the requests.

您可以使用HttpListener该类构建一个来侦听传入请求,并使用HttpWebRequest该类来中继请求。

回答by Stephen Caldwell

The browser is connected to the proxy so the data that the proxy gets from the web server is just sent via the same connection that the browser initiated to the proxy.

浏览器连接到代理,因此代理从 Web 服务器获取的数据只是通过浏览器向代理发起的同一连接发送。

回答by Vadym Stetsiak

Proxy can work in the following way.

代理可以通过以下方式工作。

Step1, configure client to use proxyHost:proxyPort.

Step1、配置客户端使用proxyHost:proxyPort。

Proxy is a TCP server that is listening on proxyHost:proxyPort. Browser opens connection with Proxy and sends Http request. Proxy parses this request and tries to detect "Host" header. This header will tell Proxy where to open connection.

Proxy 是一个 TCP 服务器,它正在侦听 proxyHost:proxyPort。浏览器打开与代理的连接并发送 Http 请求。代理解析此请求并尝试检测“主机”标头。此标头将告诉 Proxy 在哪里打开连接。

Step 2: Proxy opens connection to the address specified in the "Host" header. Then it sends HTTP request to that remote server. Reads response.

第 2 步:代理打开与“主机”标头中指定地址的连接。然后它将 HTTP 请求发送到该远程服务器。读取响应。

Step 3: After response is read from remote HTTP server, Proxy sends the response through an earlier opened TCP connection with browser.

第 3 步:从远程 HTTP 服务器读取响应后,代理通过先前与浏览器打开的 TCP 连接发送响应。

Schematically it will look like this:

从原理上讲,它看起来像这样:

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

回答by dr. evil

I wouldn't use HttpListener or something like that, in that way you'll come across so many issues.

我不会使用 HttpListener 或类似的东西,那样你会遇到很多问题。

Most importantly it'll be a huge pain to support:

最重要的是,支持将是一个巨大的痛苦:

  • Proxy Keep-Alives
  • SSL won't work (in a correct way, you'll get popups)
  • .NET libraries strictly follows RFCs which causes some requests to fail (even though IE, FF and any other browser in the world will work.)
  • 代理保活
  • SSL 不起作用(以正确的方式,您会看到弹出窗口)
  • .NET 库严格遵循 RFC,这会导致某些请求失败(即使 IE、FF 和世界上的任何其他浏览器都可以工作。)

What you need to do is:

你需要做的是:

  • Listen a TCP port
  • Parse the browser request
  • Extract Host connect to that host in TCP level
  • Forward everything back and forth unless you want to add custom headers etc.
  • 侦听 TCP 端口
  • 解析浏览器请求
  • 在 TCP 级别提取主机连接到该主机
  • 除非您想添加自定义标题等,否则来回转发所有内容。

I wrote 2 different HTTP proxies in .NET with different requirements and I can tell you that this is the best way to do it.

我在 .NET 中编写了 2 个具有不同要求的不同 HTTP 代理,我可以告诉您这是最好的方法。

Mentalis doing this, but their code is "delegate spaghetti", worse than GoTo :)

Mentalis 这样做,但他们的代码是“委托意大利面”,比 GoTo 更糟糕:)

回答by Alireza Rinan

Agree to dr evil if you use HTTPListener you will have many problems, you have to parse requests and will be engaged to headers and ...

如果您使用 HTTPListener,请同意邪恶博士,您将遇到很多问题,您必须解析请求并将参与标题和...

  1. Use tcp listener to listen to browser requests
  2. parse only the first line of the request and get the host domain and port to connect
  3. send the exact raw request to the found host on the first line of browser request
  4. receive the data from the target site(I have problem in this section)
  5. send the exact data received from the host to the browser
  1. 使用tcp listener监听浏览器请求
  2. 仅解析请求的第一行并获取要连接的主机域和端口
  3. 在浏览器请求的第一行将确切的原始请求发送到找到的主机
  4. 从目标站点接收数据(我在本节中有问题)
  5. 将从主机接收到的确切数据发送到浏览器

you see you dont need to even know what is in the browser request and parse it, only get the target site address from the first line first line usually likes this GET http://google.comHTTP1.1 or CONNECT facebook.com:443 (this is for ssl requests)

你看你甚至不需要知道浏览器请求中的内容并解析它,只需从第一行获取目标站点地址 第一行通常像这样 GET http://google.comHTTP1.1 或 CONNECT facebook.com: 443(这是用于 ssl 请求)

回答by C.M.

Socks4 is a very simple protocol to implement. You listen for the initial connection, connect to the host/port that was requested by the client, send the success code to the client then forward the outgoing and incoming streams across sockets.

Socks4 是一个实现起来非常简单的协议。您侦听初始连接,连接到客户端请求的主机/端口,将成功代码发送到客户端,然后通过套接字转发传出和传入流。

If you go with HTTP you'll have to read and possibly set/remove some HTTP headers so that's a little more work.

如果您使用 HTTP,您将不得不阅读并可能设置/删除一些 HTTP 标头,这样就需要做更多的工作。

If I remember correctly, SSL will work across HTTP and Socks proxies. For a HTTP proxy you implement the CONNECT verb, which works much like the socks4 as described above, then the client opens the SSL connection across the proxied tcp stream.

如果我没记错的话,SSL 可以跨 HTTP 和 Socks 代理工作。对于 HTTP 代理,您实现 CONNECT 动词,其工作方式与上述的 socks4 非常相似,然后客户端通过代理的 tcp 流打开 SSL 连接。

回答by Dean North

If you are just looking to intercept the traffic, you could use the fiddler core to create a proxy...

如果您只是想拦截流量,则可以使用 fiddler 核心来创建代理...

http://fiddler.wikidot.com/fiddlercore

http://fiddler.wikidot.com/fiddlercore

run fiddler first with the UI to see what it does, it is a proxy that allows you to debug the http/https traffic. It is written in c# and has a core which you can build into your own applications.

首先使用 UI 运行 fiddler 以查看它的作用,它是一个允许您调试 http/https 流量的代理。它是用 c# 编写的,并且有一个核心,您可以将其构建到您自己的应用程序中。

Keep in mind FiddlerCore is not free for commercial applications.

请记住,对于商业应用程序,FiddlerCore 不是免费的。

回答by justcoding121

I have recently written a light weight proxy in c# .net using TcpListenerand TcpClient.

我最近使用TcpListenerTcpClient在 c# .net 中编写了一个轻量级代理。

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

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

It supports secure HTTP the correct way, client machine needs to trust root certificate used by the proxy. Also supports WebSockets relay. All features of HTTP 1.1 are supported except pipelining. Pipelining is not used by most modern browsers anyway. Also supports windows authentication (plain, digest).

它以正确的方式支持安全 HTTP,客户端机器需要信任代理使用的根证书。还支持 WebSockets 中继。除流水线外,支持 HTTP 1.1 的所有功能。无论如何,大多数现代浏览器都不使用流水线。还支持 Windows 身份验证(普通、摘要)。

You can hook up your application by referencing the project and then see and modify all traffic. (Request and response).

您可以通过引用项目来连接您的应用程序,然后查看和修改所有流量。(请求和响应)。

As far as performance, I have tested it on my machine and works without any noticeable delay.

至于性能,我已经在我的机器上对其进行了测试,并且没有任何明显的延迟。

回答by Jochen van Wylick

Things have become really easy with OWIN and WebAPI. In my search for a C# Proxy server, I also came across this post http://blog.kloud.com.au/2013/11/24/do-it-yourself-web-api-proxy/. This will be the road I'm taking.

使用 OWIN 和 WebAPI,事情变得非常简单。在我搜索 C# 代理服务器时,我还看到了这篇文章http://blog.kloud.com.au/2013/11/24/do-it-yourself-web-api-proxy/。这将是我要走的路。

回答by Simon Mourier

For what it's worth, here is a C# sample async implementation based on HttpListenerand HttpClient(I use it to be able to connect Chrome in Android devices to IIS Express, that's the only way I found...).

值得一提的是,这里有一个基于HttpListenerHttpClient的 C# 示例异步实现(我使用它来将 Android 设备中的 Chrome 连接到 IIS Express,这是我发现的唯一方法......)。

And If you need HTTPS support, it shouldn't require more code, just certificate configuration: Httplistener with HTTPS support

如果您需要 HTTPS 支持,则不需要更多代码,只需证书配置:Httplistener with HTTPS support

// 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;
    }
}