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. }
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); } }