jgauffin / Griffin.WebServer

A web server built on top of Griffin.Framework
107 stars 42 forks source link

Blocking requests #17

Open jgauffin opened 9 years ago

jgauffin commented 9 years ago

Source: http://stackoverflow.com/questions/27139443/how-do-i-close-a-connection-on-a-request-to-the-griffin-webserver

We're using Griffin.WebServer as a replacement for the .Net HttpClient because the httpclient requires elevated privileges. After exhausting our options on that subject, we decided on Griffin.WebServer. Sorry this is not tagged with Griffin.WebServer - I do not have rep to create that tag.

This simple webserver responds to requests, and works great - once. Then the connection stays open, and a subsequent request will not process until I have closed the connection forcibly (using TCPView and right-click - Close Connection), or it times out.

Here is the client I am testing with:

    static void Main(string[] args)
    {
        try
        {
            var request = new
            {
                JsonData1 = 50538,
                JsonData2 = 2,
                JsonData3 = 1
            };

            var handler = new HttpClientHandler();
            handler.UseDefaultCredentials = true;
            using (var client = new HttpClient(handler))
            {
                client.BaseAddress = new Uri("http://ALocalMachine:8800/listener/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.ConnectionClose = true;
                Task<HttpResponseMessage> postTask = client.PostAsJsonAsync("RunTest/", request);

                postTask.Wait();

                if (!postTask.Result.IsSuccessStatusCode)
                {
                    throw new Exception("Unable to start test run - " + postTask.Result.ReasonPhrase);
                }

                var responseTask = postTask.Result.Content.ReadAsStringAsync();
                responseTask.Wait();
                dynamic response = JsonConvert.DeserializeObject(responseTask.Result);

                var results = new
                {
                    Error = "",
                    Success = true
                };
            }

            // this request fails with connection forcebly closed
            var request = new
            {
                JsonData1 = 50539,
                JsonData2 = 3,
                JsonData3 = 4
            };

            var handler = new HttpClientHandler();
            handler.UseDefaultCredentials = true;
            using (var client = new HttpClient(handler))
            {
                client.BaseAddress = new Uri("http://ALocalMachine:8800/listener/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.ConnectionClose = true;
                Task<HttpResponseMessage> postTask = client.PostAsJsonAsync("RunTest/", request);

                postTask.Wait();

                if (!postTask.Result.IsSuccessStatusCode)
                {
                    throw new Exception("Unsuccessful - " + postTask.Result.ReasonPhrase);
                }

                var responseTask = postTask.Result.Content.ReadAsStringAsync();
                responseTask.Wait();
                dynamic response = JsonConvert.DeserializeObject(responseTask.Result);

                var results = new
                {
                    Error = "",
                    Success = true
                };
            }

            Console.WriteLine("successful");
        }
        catch (Exception ex)
        {
            Console.WriteLine("failed with error: " + ex.ToString());
        }

        Console.ReadLine();
    }

Below is the Griffin IWorkerModule.

public class MyModule : IWorkerModule
{
    public class ListenerNotifyEventArgs : EventArgs
    {
        public IHttpContext Context { get; set; }
    }

    public delegate void ListenerNotifyEventHandler(object sender, ListenerNotifyEventArgs e);

    public event ListenerNotifyEventHandler OnListenerNotify;

    public void BeginRequest(IHttpContext context)
    {
    }

    public void EndRequest(IHttpContext context)
    {
    }

    public void HandleRequestAsync(IHttpContext context, Action<IAsyncModuleResult> callback)
    {
        // Since this module only supports sync
        callback(new AsyncModuleResult(context, HandleRequest(context)));
    }

    public ModuleResult HandleRequest(IHttpContext context)
    {
        if (OnListenerNotify != null)
            OnListenerNotify(null, new ListenerNotifyEventArgs(){Context = context});

        return ModuleResult.Stop;
    }
}

Here is my listener callback. Adding the:

    context.Response.KeepAlive = false;

didn't make any difference...

    private void ListenerCallback(object sender, MyModule.ListenerNotifyEventArgs eventArgs)
    {
        var context = eventArgs.Context;

        try
        {
            var requestDetails = context.Request.Uri.AbsolutePath.Substring("/TestifyAgent/".Length);

            if (requestDetails.ToLower().StartsWith("runtest"))
            {
                var data_text = new StreamReader(context.Request.Body, context.Request.ContentEncoding).ReadToEnd();

                dynamic requestVals = JsonConvert.DeserializeObject(data_text);
                int jsonData1 = Convert.ToInt32(requestVals.JsonData1);
                int jsonData2 = Convert.ToInt32(requestVals.JsonData2);
                int jsonData3 = Convert.ToInt32(requestVals.JsonData3);

                StartProcess(jsonData1, jsonData2, jsonData3);

                var returnData = new
                {
                    Result = "Success",
                    Environment.MachineName,
                    runRequest.TestCaseRunId
                };
                var returnJsonData = JsonConvert.SerializeObject(returnData);
                var returnUtfData = Encoding.UTF8.GetBytes(returnJsonData);

                context.Response.StatusCode = (int) HttpStatusCode.OK;
                context.Response.StatusDescription = "OK";
                context.Response.ContentType = "text/html";
                context.Response.Body = new MemoryStream();
                context.Response.Body.Write(returnUtfData, 0, returnUtfData.Length);
                context.Response.Body.Flush();
                context.Response.Body.Position = 0;
                context.Response.KeepAlive = false;
                return;
            }
            else
            {
                var buffer = Encoding.UTF8.GetBytes("<html><head></head><body><h1>500 - Server Error</h1></body></html>");
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
                context.Response.StatusDescription = "Bad Request";
                context.Response.ContentType = "text/html";
                context.Response.Body = new MemoryStream();
                context.Response.Body.Write(buffer, 0, buffer.Length);
                context.Response.Body.Flush();
                context.Response.Body.Position = 0;
                context.Response.KeepAlive = false;

                Notify("Invalid Request method:" + context.Request.Method + " Url: " + context.Request.Uri.AbsoluteUri);
                return;
            }
        }
        catch (Exception ex)
        {
            TrySendErrorOrWriteEventLog(ex);

            try
            {
                var buffer =
                    Encoding.UTF8.GetBytes("<html><head></head><body><h1>500 - Server Error</h1>Error processing request:" +
                    context.Request.Method + " Error: " + ex.Message + "</body></html>");
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
                context.Response.StatusDescription = "Internal Server Error";
                context.Response.ContentType = "text/html";
                context.Response.Body.Write(buffer, 0, buffer.Length);
                context.Response.Body.Flush();
                context.Response.Body.Position = 0;
            }
            catch { }
            return;
        }
    }

EDIT: I will leave this open in case someone needs it (and it gets answered - jgauffin?), however, I found a simple solution for creating a listener that did not require elevated privileges at this CodeProject page. It is a very straightforward solution and with one change it works very well. The one issue I had with it is that it won't shut down. It needs this added to the listener loop:

public abstract class SimpleHttpServer {

    protected int port;
    private TcpListener _listener;

    // this lets us call in with Listener.Server.Close(); when we want to shutdown
    public TcpListener Listener { get { return _listener; } }
    public bool IsActive { get; set; }

    public SimpleHttpServer(int port) 
    {
        this.port = port;
    }

    public void listen()
    {
        IsActive = true;
        _listener = new TcpListener(port);
        _listener.Start();
        while (IsActive) 
        {
            try
            {
                TcpClient s = _listener.AcceptTcpClient();
                SimpleHttpProcessor processor = new SimpleHttpProcessor(s, this);
                Thread thread = new Thread(new ThreadStart(processor.process));
                thread.Start();
                Thread.Sleep(1);
            }
            catch (Exception ex)
            {   // shutdown requested?
                if (ex.Message.Contains("A blocking operation was interrupted by a call to WSACancelBlockingCall"))
                    return;
            }
        }
    }

    public abstract void handleGETRequest(SimpleHttpProcessor p);
    public abstract void handlePOSTRequest(SimpleHttpProcessor p, StreamReader inputData);
}

Then in your shutdown include something like this:

        simpleHttpServer.Listener.Server.Close();
        if (!thread.Join(10000)) // try to wait for it...
            thread.Abort();
        simpleHttpServer = null;

Hope this is helpful to another in the same search as I was. This took way longer than it should have.