Bobris / Nowin

Owin Web Server in pure .Net
MIT License
537 stars 78 forks source link

owin.CallCancelled and websocket.CallCancelled not cancelled on client tcp connection closed or dropped #39

Closed comphilip closed 9 years ago

comphilip commented 9 years ago

Reproduce

  1. Client issue a request to server
  2. Server code register callback to owin.CallCancelled CancellationToken
  3. Server suspend, not finish current response
  4. Client close TCP connection

    Expected Result

owin.CallCancelled should be cancelled and server code registered callback should be called.

Now

owin.CallCancelled not cancelled on client TCP connection closed or dropped

Bobris commented 9 years ago

Can you (do you want to) try to write test case?

comphilip commented 9 years ago

Hi Bobris,

From TCPView tool, I found some thing interesting. My HttpWebRequest connect to Nowin server via IPv6. I debug my code and sure the HttpWebRequest.Close was called. While still the connection is not closed, it seems HttpWebRequest persist the connection (HTTP/1.1) even though adding Connection: close header.

I am still looking at it. Maybe it is HttpWebRequst behavior and has nothing with Nowin.

Bobris commented 9 years ago

It should be possible to recreate it using just plain Socket - create Http/1.1 request by hand should not be too difficult.

comphilip commented 9 years ago

Hi Boris,

You are right. It is my code problem. Thanks.

comphilip commented 9 years ago

Hi Bobris, I find it is exactly a bug of Nowin.

Server Code

using Microsoft.Owin.Hosting;
using Owin;
using System;
using System.Threading.Tasks;

namespace NowinServer {
    class Program {
        static void Main(string[] args) {
            var options = new StartOptions {
                ServerFactory = "Nowin",
                Port = 8080
            };

            using (WebApp.Start<Startup>(options)) {
                Console.WriteLine("Running a http server on port 8080");
                Console.ReadKey();
            }
        }
    }

    public class Startup {
        public void Configuration(IAppBuilder app) {
            app.Use(async (context, task) => {
                // wait 1 minute
                Console.WriteLine("Start to wait 1 minute...");

                using (context.Request.CallCancelled.Register(() => {
                    // if CallCancelled cancelled, following message should shown in console
                    Console.WriteLine("Request CallCancelled cancelled.");
                })) {

                    //simulate long time operation, use context.Request.CallCancelled to stop processing if client disconnected.
                    try {
                        for (int i = 0; i < 60; ++i) {
                            await Task.Delay(TimeSpan.FromSeconds(1), context.Request.CallCancelled);
                        }
                        Console.WriteLine("Long operation completed.");
                    } catch (TaskCanceledException) {
                        Console.WriteLine("Long operation cancelled.");
                    }

                    if (context.Request.Path.Value == "/") {
                        context.Response.ContentType = "text/plain";
                        await context.Response.WriteAsync("Hello World!");
                    }

                    context.Response.StatusCode = 404;
                }
            });
        }
    }
}

Client Code

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace TestClient {
    class Program {
        static void Main(string[] args) {
            using (TcpClient client = new TcpClient()) {
                Console.WriteLine("Connecting to Nowin Server");
                client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080));
                using (var connStream = client.GetStream()) {
                    Console.WriteLine("Send http request");
                    var request = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\n"
                                + "Host: localhost:8080\r\n"
                                + "\r\n");
                    connStream.Write(request, 0, request.Length);
                    connStream.Flush();
                }
            }
            Console.WriteLine("Client closed.");
        }
    }
}
Bobris commented 9 years ago

You are right this feature was missing, thanks for repro. Unfortunately I was not able to implemented it during one night (it is pretty complex to do it thread safe without too much locking), so need to continue on it later today...

Bobris commented 9 years ago

This fix took me over 10 hours. It is big change and although all tests pass, please test it carefully.