unosquare / embedio

A tiny, cross-platform, module based web server for .NET
http://unosquare.github.io/embedio
Other
1.46k stars 176 forks source link

EmbedIO HttpListenerMode doesn't detect client disconnects #457

Open ketodiet opened 4 years ago

ketodiet commented 4 years ago

A quick overview: A number of client apps connect via a rev proxy to a service. The rev proxy is an ASP.NET app and the service is a c# windows service using EmbedIO to handle incoming requests. Client apps have two connections each: one for pushing updates and the one polling for changes. The issue described here is related to the polling.

The Bug: When using Microsoft HttpListenerMode when a client disconnects & the rev proxy removes the connection in response, the next write on the poll connection will fail (i.e. the service will fail to send any updates or heartbeat to the client via the rev proxy).

When using EmbedIO HttpListenerMode in the exact same scenario, the service writes keep succeeding with no errors reported at all. Even if the rev proxy process is terminated (i.e. stopping the IIS server hosting the rev proxy) the writes keep working with no exceptions thrown.

That feels like a bug to me.

rdeago commented 4 years ago

Hello @ketodiet, thanks for using EmbedIO!

The behavior you describe feels like a bug to me too.

Which type of module do you use for the polling? Looks like WebSocketModule from what you write, but I'd like to be sure.

ketodiet commented 4 years ago

Hi Riccardo, I'm not using websockets. The WebServer setup is:

_PublicAPI = new WebServer(o => o .WithUrlPrefix($"http://*:{port}/") .WithMode(HttpListenerMode.Microsoft) .WithSupportCompressedRequests(true)) .WithModule(new ActionModule(ctx => APIs.PublicAPI.Handle(ctx)));

Changing .WithMode from HttpListenerMode.Microsoft to HttpListenerMode.EmbedIO changes the behaviour as described in the bug.

In the case of HttpListenerMode.Microsoft I always get an System.Net.HttpListenerException exception when attempting to write on a closed connection.

rdeago commented 4 years ago

Hi @ketodiet, sorry for the delay. From what I could see from the source code, there are actually some flaws in connection management. I've begun an almost complete refactor of that part of EmbedIO, but in doing so I had to introduce a handful of breaking changes, so I'm afraid you'll have to wait for version 4 to see this issue (hopefully) fixed.

simontuffley commented 2 years ago

Hi Riccardo,

We're experiencing the exact same issue. Do you have any idea when we may see v4?

radioegor146 commented 2 years ago

@ketodiet, @simontuffley can you give me an example of code for continuous writes on HTTP connection?

OffTimers commented 2 years ago

I confirm that there is such disconnection behavior. the code to repeat the error is simple:

[Route(HttpVerbs.Get, "/test")]
    public async Task Test()
    {
        try {
            byte[] dataBuffer = new byte[512];
            using (var stream = HttpContext.OpenResponseStream()) {
                while (true) {
                     await stream.WriteAsync(dataBuffer, 0, dataBuffer.Length);
                     await Task.Delay(1000);
                }
            }
        } catch (Exception e) { Debug.WriteLine($"{e}"); }
    }

It is convenient to receive data using curl command line, as well as terminate connections using ctl-c.

At the same time, in the debugger during debugging, the occurring exceptions associated with the advice are visible, but they are not returned as exceptions to the code of the method from which it was called.

OffTimers commented 2 years ago

Small changes in the source codes of the library easily solve this problem. The file /src/packages/EmbedIO/Net/Internal/ResponseStream needs to be modified:

remove: internal class ResponseStream : Stream insert: public class ResponseStream : Stream

remove: private readonly bool _ignoreErrors;

insert and rename all _ignoreErrors to IgnoreErrors public bool IgnoreErrors { get; set; } = true;

next:

[Route(HttpVerbs.Get, "/test")]
    public async Task Test()
    {
        try {
            byte[] dataBuffer = new byte[512];
            using (var stream = HttpContext.OpenResponseStream()) {
                if (stream is EmbedIO.Net.Internal.ResponseStream s)
                    s.IgnoreErrors = false;
                while (true) {
                     await stream.WriteAsync(dataBuffer, 0, dataBuffer.Length);
                     await Task.Delay(1000);
                }
            }
        } catch (Exception e) { Debug.WriteLine($"{e}"); }
    }
klasyc commented 1 year ago

Another solution to this issue is globally enabling the write errors by configuring the WebServer's Listener object:

server.Listener.IgnoreWriteExceptions = false;
await server.RunAsync();

This method does not require touching the library internal class.