justcoding121 / titanium-web-proxy

A cross-platform asynchronous HTTP(S) proxy server in C#.
MIT License
1.93k stars 611 forks source link

Requests are not being sent to upstream proxy (sometimes). Freezes on baseStream.WriteAsync. This is connection pooling problem. #826

Closed tonik-ru closed 3 years ago

tonik-ru commented 3 years ago

using a basic setup with upstream proxy and ssl decryption. and i see that chrome sends request to titanium (BeforeRequest), but it never hits upstream proxy. this is happening for some requests only. didnt find a pattern yet. does someone else have such issues?

Update: it actually freezes in HttpStream.WriteAsync await baseStream.WriteAsync(data, offset, count, cancellationToken);

it just hangs here forever. in call stack topmost function is TaskFactory.FromAsyncTrim

idk how make a code reproducible example. im using titanium as a proxy for chrome.

Update 2: this is connection pooling problem. if it disable it, everything works fine.

steps to reproduce:

  1. start proxy
  2. use it from chrome
  3. google something, navigate to google pages.
  4. close chrome
  5. check in "Tasks" for frozen tasks
sanket-pattekar commented 3 years ago

I am also facing the same issue. The proxy works for few connections and then hangs.

Any workaround for the same

justcoding121 commented 3 years ago

Are you using multiple upstream proxies? Can you paste a sample code that you use here.

tonik-ru commented 3 years ago

yes. also i have multiple endpoints, say 20. the idea is to allow client app to set proxy (upstream). client app will switch proxies very often. so we have 20 client apps, when app wants proxy, proxy manager acquires free proxy, sets ProxyConfig (from client) and returns this port to client. client will connect to that port. so each of 20 Titanium Endpoints will be constantly changing upstream proxies. and here how i set upstream proxy

        private async Task Ep_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
        {
            endpointsInUse.TryGetValue(e.ProxyEndPoint.Port, out var ep);
            if (ep != null)
            {
                if (ep.UpstreamProxy != null)
                {
                    e.CustomUpStreamProxy = new ExternalProxy()
                    {
                        HostName = ep.UpstreamProxy.Address,
                        Port = ep.UpstreamProxy.Port,
                        ProxyType = ExternalProxyType.Http,
                        UserName = ep.UpstreamProxy.Username,
                        Password = ep.UpstreamProxy.Password
                    };
                }
            }
        }

private async Task OnRequest(object sender, SessionEventArgs e)
        {
            endpointsInUse.TryGetValue(e.ProxyEndPoint.Port, out var ep);

            if (ep != null)
            {
                if (ep.UpstreamProxy != null)
                {
                    e.CustomUpStreamProxy = new ExternalProxy()
                    {
                        HostName = ep.UpstreamProxy.Address,
                        Port = ep.UpstreamProxy.Port,
                        ProxyType = ExternalProxyType.Http,
                        UserName = ep.UpstreamProxy.Username,
                        Password = ep.UpstreamProxy.Password
                    };
                }
            }
        }
justcoding121 commented 3 years ago

@tonik-ru This appears to be an issue with RetryPolicy.cs. We have some logic to retry the requests to the upstream server upon network failure. I've set the default retry attempt count default to zero as a workaround and published a beta package. Essentially, it means failures won't be retried by default. You can try the latest beta out and see if it works.

We still need to fix the retry logic, which is nice to have, but not required. The downside of this workaround is that when the proxy fails to make a connection with the server, clients will see that right away as a failure. CC: @honfika

justcoding121 commented 3 years ago

The original intent when I wrote retry logic was if the cache somehow got corrupted, it will try to get the next available connection in cache until all cached connections are exhausted. Finally, it will fall back to creating a fresh connection. I am not sure if the retry logic ever worked correctly. Maybe it didn't and it showed up in this specific scenario.

One can still revert back to logic by increasing proxyServer.NetworkFailureRetryAttempts to a value greater than zero.