Ejemplo n.º 1
0
        public async Task Handle()
        {
            var q = new Queue <ProxyHTTPEncoder>();

            var qchanged = new SemaphoreSlim(0);

            // TODO,BUG: Do these need some kind of volatile marking? How to mark them as such?
            bool runner_exit  = false;
            bool runner_abort = false;

            // This task watches the `q` queue and starts and removes
            // proxy objects representing the HTTP encoder. Each request
            // gets its own proxy object and all methods on the proxy either
            // block or buffer until the proxy object becomes ready.
#pragma warning disable 4014
            var runner_task = Task.Run(async() =>
            {
                while (true)
                {
                    ProxyHTTPEncoder phe;

                    await qchanged.WaitAsync();

                    // Only lock long enough to get the first item.
                    lock (q)
                    {
                        if (runner_abort)
                        {
                            return;
                        }

                        if (runner_exit && q.Count == 0)
                        {
                            return;
                        }

                        phe = q.Peek();
                    }

                    if (phe == null)
                    {
                        // The exit signal.
                        break;
                    }

                    // Signal this object that it is ready to do work.
                    phe.ready.Set();

                    // Wait until it is done.
                    await phe.done.WaitAsync();

                    await phe.Death();

                    // Remove it, and signal the next to go.
                    q.Dequeue();
                }
            });
#pragma warning restore 4014

            bool close_connection = false;

            while (!close_connection)
            {
                //Debug.WriteLine("###### Handling the next request. ######");
                // Need a way for this to block (await) until the body has been completely
                // read. This logic could be implemented inside the decoder.
                List <String> line_header;
                try
                {
                    line_header = await decoder.ReadHeader();
                }
                catch (InvalidOperationException)
                {
                    // This happens if the remote closes their transmit channel, yet, we must be careful not to
                    // exit since the remote receive channel may still be open.
                    break;
                }

                //Debug.WriteLine("Header to dictionary.");

                if (line_header == null)
                {
                    //Debug.WriteLine("Connection has been lost.");
                    break;
                }

                var header = LineHeaderToDictionary(line_header);

                Stream body;

                //Debug.WriteLine("Got header.");

                Task body_reading_task;

                if (header.ContainsKey("content-length"))
                {
                    //Debug.WriteLine("Got content-length.");
                    // Content length specified body follows.
                    long content_length = (long)Convert.ToUInt32(header["content-length"]);

                    (body, body_reading_task) = await decoder.ReadBody(HTTPDecoderBodyType.ContentLength(content_length));
                }
                else if (header.ContainsKey("transfer-encoding"))
                {
                    //Debug.WriteLine("Got chunked.");
                    // Chunked encoded body follows.
                    (body, body_reading_task) = await decoder.ReadBody(HTTPDecoderBodyType.ChunkedEncoding());
                }
                else
                {
                    //Debug.WriteLine("Got no body.");
                    // No body follows. (Not using await to allow pipelining.)
                    (body, body_reading_task) = await decoder.ReadBody(HTTPDecoderBodyType.NoBody());
                }

                if (header.ContainsKey("connection") && !header["connection"].ToLower().Equals("close"))
                {
                    close_connection = false;
                }
                else
                {
                    close_connection = true;
                }

                //Debug.WriteLine($"close_connection={close_connection}");

                var phe = new ProxyHTTPEncoder(encoder, close_connection);

                q.Enqueue(phe);

                qchanged.Release();

                //Debug.WriteLine("Allowing handling of request.");

                // A couple of scenarios are possible here.
                // (1) The request can complete and exit before the data it writes is actually sent.
                // (2) The request can complete after the response has been sent.
                // (3) If we do not await then we can service other requests (pipeline).
                //
                // To keep things more deterministic and less performance oriented
                // you can see that we await the request handler to complete before
                // moving onward.
                var handle_req_task = HandleRequest(header, body, (IProxyHTTPEncoder)phe);

                var next_task = await handle_req_task;

                // Clever way to handle exceptions since otherwise things continue onward like
                // everything is normal. This can leave the runner stuck waiting at the `phe.done`
                // signal.
                if (handle_req_task.Exception != null)
                {
                    // We might should think of a way to salvage the connection.. but if the headers have
                    // been sent and we are in the middle of a content-length response then I am not sure
                    // how to gracefully let the client know that things have gone wrong without shutting
                    // the connection down. It is possible to detect if its a chunked-encoding and then use
                    // a trailer header, maybe? Pretty much like a CGI script?
                    //Debug.WriteLine("exception in request handler; shutting down connection");
                    // Ensure the proxy is marked as done. So the runner will throw it away, and also
                    // drop the connection because we are not sure how to proceed now.
                    runner_abort = true;
                    // The runner is likely waiting on the `done` signal. Set the signal to ensure it
                    // gets past that point and then sees the `runner_abort`.
                    phe.done.Set();
                    // Come out of this loop and wait on runner to complete.
                    break;
                }

                //Debug.WriteLine("Handler released control.");

                await next_task;

                // Ensure that the proxy is finished up.
                await phe.Death();

                // We must wait until both the HandleRequest returns and
                // that the task, if any, which is reading the body also
                // completes.
                //
                // This should not usually happen but it is possible and has happened. For some reason,
                // the handler exits, yet the body is still being read, therefore, we really also need
                // to sort of dump the data here or take a Task that is returned by HandleRequest so
                // we know when all possible handlers are dead.
                if (body_reading_task != null)
                {
                    // BUG, TODO: unsufficient... will deadlock if nothing is reading the `body` as the buffer may fill
                    //       for the body
                    // Do not block while waiting.
                    //Debug.WriteLine("waiting for body_reading_task to complete");
                    await body_reading_task;

                    if (body_reading_task.Exception != null)
                    {
                        //Debug.WriteLine("exception in body reader; shutting down connection");
                        //Debug.WriteLine(body_reading_task.Exception.ToString());
                        runner_abort = true;
                        phe.done.Set();
                    }
                }
            }

            //Debug.WriteLine("httpclient handler trying to exit; once runner has exited");
            // Signal the runner that it is time to exit.
            runner_exit = true;
            // Ensure the runner can continue.
            qchanged.Release();
            await runner_task;

            //Debug.WriteLine("done waiting on runner; now exiting handler");

            // The stream is (expected to be) closed once this method is exited.
        }
Ejemplo n.º 2
0
        public async Task <(Stream, Task)> ReadBody(HTTPDecoderBodyType body_type)
        {
            var  os = new DoubleEndedStream();
            Task spawned_task;

            const long max_buffer = 1024 * 1024 * 32;

            switch (body_type.type)
            {
            case HTTPDecoderBodyType.MyType.NoBody:
                os.Dispose();
                return(os as Stream, null);

            case HTTPDecoderBodyType.MyType.ChunkedEncoding:
#pragma warning disable 4014
                spawned_task = Task.Run(async() =>
                {
                    do
                    {
                        var line_bytes = await s_helper.ReadLine();
                        var line       = Encoding.UTF8.GetString(line_bytes).TrimEnd();

                        //Debug.WriteLine($"HTTPDecoder.ChunkedDecoder: line={line}");

                        if (line.Length == 0)
                        {
                            continue;
                        }

                        int chunk_size = Convert.ToInt32(line, 16);

                        if (chunk_size == 0)
                        {
                            os.Dispose();
                            await s_helper.ReadLine();
                            break;
                        }

                        // Wait without polling for the buffer to decrease from reading from it.
                        while (os.GetUsed() + chunk_size > max_buffer)
                        {
                            await os.WaitForReadAsync();
                        }

                        var chunk = await s_helper.ReadSpecificSize(chunk_size);

                        await os.WriteAsync(chunk, 0, chunk.Length);
                    } while (true);
                });
#pragma warning restore 4014
                return(os, spawned_task);

            case HTTPDecoderBodyType.MyType.ContentLength:
#pragma warning disable 4014
                spawned_task = Task.Run(async() =>
                {
                    //Debug.WriteLine("reading content-length body");

                    long got = 0;
                    int amount;
                    const int chunksize = 4096;

                    do
                    {
                        if (body_type.size - got > 0x7fffffff)
                        {
                            amount = chunksize;
                        }
                        else
                        {
                            amount = Math.Min(chunksize, (int)(body_type.size - got));
                        }

                        // Wait without polling for the buffer to decrease from reading from it.
                        while (os.GetUsed() + amount > max_buffer)
                        {
                            await os.WaitForReadAsync();
                        }

                        var chunk = await s_helper.ReadSpecificSize(amount);

                        got += chunk.Length;

                        await os.WriteAsync(chunk, 0, chunk.Length);
                    } while (got < body_type.size);

                    os.Dispose();
                });
#pragma warning restore 4014
                return(os, spawned_task);

            default:
                os.Dispose();
                return(os, null);
            }
        }