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

发布于 2024-07-08 10:27:21 字数 1183 浏览 9 评论 0原文

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

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

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

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

更新

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

第 1 步 我将客户端(浏览器)配置为将所有请求发送到代理侦听端口上的 127.0.0.1。 这样,请求不会直接发送到互联网,而是由代理处理。

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

第三步 代理收到请求的答复。 现在他必须将答案从网络发送给客户,但是如何发送???

alt text

有用链接

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

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

请求Reflector :这是一个简单的例子。

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

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.

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)?

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).

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 1 I 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.

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

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

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.

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

Request reflector : This is a simple example.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

木槿暧夏七纪年 2024-07-15 10:27:21

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

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

  • 代理保持活动
  • SSL 将无法工作(以正确的方式,您将收到弹出窗口)
  • .NET 库严格遵循 RFC,这会导致某些请求失败(即使 IE、 FF 和世界上任何其他浏览器都可以工作。)

您需要做的是:

  • 侦听 TCP 端口
  • 解析浏览器请求
  • 提取主机以 TCP 级别连接到该主机
  • 来回转发所有内容,除非您想添加自定义标头等。

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

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

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

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.)

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.

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.

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

白鸥掠海 2024-07-15 10:27:21

我最近使用 TcpListenerTcpClient.

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

支持安全 HTTP正确的方法是,客户端计算机需要信任代理使用的根证书。 还支持 WebSockets 中继。 支持 HTTP 1.1 的所有功能(管道传输除外)。 无论如何,大多数现代浏览器都不使用管道技术。 还支持 Windows 身份验证(普通、摘要)。

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

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

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

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).

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.

魂ガ小子 2024-07-15 10:27:21

You can build one with the HttpListener class to listen for incoming requests and the HttpWebRequest class to relay the requests.

深海里的那抹蓝 2024-07-15 10:27:21

代理可以按以下方式工作。

步骤1,配置客户端使用proxyHost:proxyPort。

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

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

步骤 3:从远程 HTTP 服务器读取响应后,Proxy 通过先前打开的与浏览器的 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

Proxy can work in the following way.

Step1, configure client to use 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.

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

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

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
快乐很简单 2024-07-15 10:27:21

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

http:// fiddler.wikidot.com/fiddlercore

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

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

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

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.

Keep in mind FiddlerCore is not free for commercial applications.

旧竹 2024-07-15 10:27:21

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

  1. 使用tcp监听器监听浏览器请求
  2. 仅解析请求的第一行并获取主机域和连接端口
  3. 将确切的原始请求发送到浏览器请求第一行上找到的主机
  4. 从目标站点接收数据(我在本节中遇到问题)
  5. 将从主机接收到的确切数据发送到

您认为不需要的 浏览器甚至要知道浏览器请求中的内容并对其进行解析,只需从第一行获取目标站点地址
第一行通常喜欢这样
获取 http://google.com HTTP1.1
或者
CONNECT facebook.com:443(用于 ssl 请求)

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 ...

  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

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.com HTTP1.1
or
CONNECT facebook.com:443 (this is for ssl requests)

挖鼻大婶 2024-07-15 10:27:21

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

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.

短叹 2024-07-15 10:27:21

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

如果您使用 HTTP,则必须读取并可能设置/删除一些 HTTP 标头,因此工作量会增加一些。

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

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.

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

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.

十秒萌定你 2024-07-15 10:27:21

无论如何,这里是一个基于 HttpListenerHttpClient(我用它能够将Android设备中的Chrome连接到IIS Express,这是我发现的唯一方法......)。

如果您需要 HTTPS 支持,则不需要更多代码,只需证书配置: 具有 HTTPS 支持的 Httplistener< /a>

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

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

And If you need HTTPS support, it shouldn't require more code, just certificate configuration: 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;
    }
}
骄兵必败 2024-07-15 10:27:21

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文