A_L
A_L

Reputation: 1146

HttpListener response is not sent before listener is stopped

I use the code below to get an oAuth code using a browser login, it works just fine ie. auth code is returned BUT there is some timing issue between responseOutput.WriteAsync() and http.stop().

When in debug (breakpoint on **** below), then the response is returned to the browser as expected.

If I the comment out the line http.stop() (**** below), then the response is returned to the browser as expected.

BUT If I run the code as usual then the browser shows "page cannot be found", so, it looks as though responseOutput.WriteAsync() is not actually completing (not begin 'awaited'). Do I need to do anything else to ensure that the response is completely sent before stopping the listener?

await GetAuthorizationCode();

public async Task GetAuthorizationCode() {
    
    // Creates an Listener
    string redirectUri = "http://127.0.0.1:12345"; 
    HttpListener http = new HttpListener();
    http.Prefixes.Add(redirectUri);
    http.Start();

    // Open auth page in browser
    string authUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?........"; 
    var authorizationRequest = authUri;
    Process.Start(authorizationRequest);

    // Wait for auth response.
    HttpListenerContext context = await http.GetContextAsync();

    var sCode = context.Request.QueryString.Get("code");

    //Send response to the browser.
    HttpListenerResponse response = context.Response;
    string responseString = string.Format("<html><head></head><body>Auth OK.</body></html>");
    var buffer = Encoding.UTF8.GetBytes(responseString);
    response.ContentLength64 = buffer.Length;

    using (Stream responseOutput = response.OutputStream)
    {
        await responseOutput.WriteAsync(buffer, 0, buffer.Length);
        responseOutput.Close();
    }

    //****** this line causes problems
    http.Stop();

    AuthorizationCode = sCode;
}

Upvotes: 1

Views: 1306

Answers (2)

Noman_1
Noman_1

Reputation: 530

From an old http handler I made some time ago, I have this code:

    protected static void WriteString(HttpListenerResponse response, HttpContentType contentType, Encoding encoding, string content)
    {
        byte[] outputBuffer = encoding.GetBytes(content);
        response.ContentType = contentType.Value + "; charset=" + encoding.BodyName;
        response.ContentLength64 = outputBuffer.Length;
        response.Close(outputBuffer, true);
    }

This code have been active on a program that usually stayed days if not weeks serving few thousand requests each day and never had any kind of issues regarding memory leaks.

From docs:

You can customize the response by setting various properties, such as StatusCode, StatusDescription, and Cookies. Use the HttpListenerResponse.OutputStream property to obtain a Stream instance to which response data can be written. Finally, send the response data to the client by calling the Close method.

https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistenerresponse?view=net-6.0

Oposed to my though, the HttpListener docs states to close the output stream:

https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-6.0

Which is quite confusing:

    System.IO.Stream output = response.OutputStream;
    output.Write(buffer,0,buffer.Length);
    // You must close the output stream.
    output.Close();
    listener.Stop();

I would suggest to try the follwing code:

    using (Stream responseOutput = response.OutputStream)
    {
        await responseOutput.WriteAsync(buffer, 0, buffer.Length);
        
//The following two lines are possibly unnecesary in an unsing statement
        //responseOutput.Flush(); //Ensure content placed on the right place
        //responseOutput.Close(); 
    }
    response.Close();
    http.Stop();

--- Update ---

I could run the following code under .Net6.0 without any issue and got the response content on the browser:

    class Program
    {
        static async Task Main(string[] args)
        {
            await RunUntilServeOneRequest();
        }

        private static async Task RunUntilServeOneRequest()
        {
            Console.WriteLine("Starting...");

            // Creates an Listener
            string listenUri = "http://127.0.0.1:12346/";
            HttpListener http = new HttpListener();
            http.Prefixes.Add(listenUri);
            http.Start();

            // Wait for request.
            Console.WriteLine("Awaiting request to " + listenUri);
            HttpListenerContext context = await http.GetContextAsync();

            //Send response to the browser.
            HttpListenerResponse response = context.Response;
            string responseString = string.Format($"<html><head></head><body>Hello world: {DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}</body></html>");
            byte[] buffer = Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;

            using (Stream responseOutput = response.OutputStream)
            {
                await responseOutput.WriteAsync(buffer, 0, buffer.Length);
                responseOutput.Close();
            }
            response.Close();
            Console.WriteLine("Request answered");

            http.Stop();
        }
    }

Upvotes: 2

A_L
A_L

Reputation: 1146

It seems that I have to set KeepAlive prior to http.stop()

response.KeepAlive = False

Somehow even with calling response.close and/or with a 'using' block around the response it still needed this setting.

Upvotes: 3

Related Questions