# HG changeset patch # User Ivo Smits # Date 1391464411 -3600 # Node ID 50d4aed66c675fe002b572664000e938e4a16ac3 # Parent 6aca18ee4ec6aea194506d8b6f2931a9482ed146 Improved HTTP classes diff -r 6aca18ee4ec6 -r 50d4aed66c67 Net/HTTP.cs --- a/Net/HTTP.cs Sat Nov 02 16:01:09 2013 +0100 +++ b/Net/HTTP.cs Mon Feb 03 22:53:31 2014 +0100 @@ -70,6 +70,14 @@ } } + public enum HTTPResponseStreamMode { + None = -1, + Direct = 0, + Buffered = 1, + Chunked = 2, + Hybrid = 3, + } + public class HTTPContext { public HTTPServer Server { get; private set; } public EndPoint LocalEndPoint { get; private set; } @@ -88,7 +96,9 @@ private PrebufferingStream Reader; private List RequestHeaders; private HTTPConnectionState State = HTTPConnectionState.Starting; - + private KeyValuePair[] QueryParameters = null, PostParameters = null; + private HTTPOutputStream ResponseStream = null; + private HTTPInputStream RequestStream = null; private enum HTTPConnectionState { Starting = 0, @@ -96,7 +106,289 @@ ProcessingRequest = 2, SendingHeaders = 3, SendingContent = 4, - Closed = 5, + Completed = 5, + Closed = 6, + } + + private class HTTPOutputStream : Stream { + public HTTPResponseStreamMode Mode { get; private set; } + public HTTPContext Context { get; private set; } + private Stream OutputStream = null; + private MemoryStream Buffer = null; + private long BytesWritten = 0; + private long MaxLength; + + public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode) : this(context, mode, -1) { } + public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode, long length) { + this.Context = context; + this.Mode = mode; + this.MaxLength = length; + switch (Mode) { + case HTTPResponseStreamMode.Direct: + if (MaxLength != -1) Context.SendHeader("Content-Length", MaxLength.ToString()); + OutputStream = Context.BeginResponseData(); + break; + case HTTPResponseStreamMode.Chunked: + Context.SendHeader("Transfer-Encoding", "chunked"); + OutputStream = Context.BeginResponseData(); + break; + case HTTPResponseStreamMode.Buffered: + case HTTPResponseStreamMode.Hybrid: + if (Context.State != HTTPConnectionState.ProcessingRequest && Context.State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException("The response stream can not be created in the current state"); + break; + default: throw new InvalidOperationException("Response stream mode is not supported"); + } + } + + private void WriteBuffered(byte[] buffer, int offset, int count) { + if (Buffer == null) Buffer = new MemoryStream(); + Buffer.Write(buffer, offset, count); + } + private void WriteChunked(byte[] buffer, int offset, int count) { + Byte[] lb = Encoding.ASCII.GetBytes(count.ToString("X") + "\r\n"); + OutputStream.Write(lb, 0, lb.Length); + if (count != 0) OutputStream.Write(buffer, offset, count); + OutputStream.Write(new Byte[] { (Byte)'\r', (Byte)'\n' }, 0, 2); + } + private void HybridSwitchToChunked() { + MemoryStream oldbuffer = Buffer; + Buffer = null; + Context.SendHeader("Transfer-Encoding", "chunked"); + OutputStream = Context.BeginResponseData(); + Mode = HTTPResponseStreamMode.Chunked; + oldbuffer.WriteTo(this); + } + + public override void Write(byte[] buffer, int offset, int count) { + if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); + switch (Mode) { + case HTTPResponseStreamMode.Direct: + if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); + OutputStream.Write(buffer, offset, count); + BytesWritten += count; + break; + case HTTPResponseStreamMode.Buffered: + WriteBuffered(buffer, offset, count); + break; + case HTTPResponseStreamMode.Chunked: + if (count != 0) WriteChunked(buffer, offset, count); + BytesWritten += count; + break; + case HTTPResponseStreamMode.Hybrid: + if (count > 1024 || (Buffer != null && Buffer.Length + count > 1024)) { + HybridSwitchToChunked(); + if (count != 0) WriteChunked(buffer, offset, count); + } else { + WriteBuffered(buffer, offset, count); + } + break; + } + } + class CompletedAsyncResult : AsyncResultBase { + public CompletedAsyncResult(AsyncCallback callback, Object state) : base(callback, state) { + SetCompleted(true, null); + } + } + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { + if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); + switch (Mode) { + case HTTPResponseStreamMode.Direct: + if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); + BytesWritten += count; + return OutputStream.BeginWrite(buffer, offset, count, callback, state); + case HTTPResponseStreamMode.Buffered: + case HTTPResponseStreamMode.Chunked: + case HTTPResponseStreamMode.Hybrid: + Write(buffer, offset, count); + return new CompletedAsyncResult(callback, state); + default: return null; + } + } + public override void EndWrite(IAsyncResult asyncResult) { + switch (Mode) { + case HTTPResponseStreamMode.Direct: + OutputStream.EndWrite(asyncResult); + break; + } + } + public override void SetLength(long value) { + switch (Mode) { + case HTTPResponseStreamMode.Buffered: + case HTTPResponseStreamMode.Hybrid: + if (value != 0) throw new InvalidOperationException("The length can only be set to zero using this method"); + Buffer = null; + break; + default: throw new InvalidOperationException("The operation is not supported in the current mode"); + } + } + public override long Length { + get { return MaxLength == -1 ? Position : MaxLength; } + } + public override long Position { + get { return (Buffer == null) ? BytesWritten : BytesWritten + Buffer.Length; } + set { throw new NotSupportedException(); } + } + public override void Flush() { + switch (Mode) { + case HTTPResponseStreamMode.Hybrid: + HybridSwitchToChunked(); + break; + } + } + public override bool CanWrite { + get { return Context.State != HTTPConnectionState.Completed && Context.State != HTTPConnectionState.Closed && (OutputStream == null || OutputStream.CanWrite); } + } + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + if (disposing) { + switch (Mode) { + case HTTPResponseStreamMode.Direct: + /*if (MaxLength != -1 && MaxLength > BytesWritten) { + long left = MaxLength - BytesWritten; + Byte[] dummy = new Byte[Math.Min(left, 1024)]; + for (; left > 0; left -= dummy.Length) OutputStream.Write(dummy, 0, (int)Math.Min(left, dummy.Length)); + }*/ + break; + case HTTPResponseStreamMode.Chunked: + WriteChunked(null, 0, 0); + Mode = HTTPResponseStreamMode.None; + break; + case HTTPResponseStreamMode.Buffered: + case HTTPResponseStreamMode.Hybrid: + long length = (Buffer == null) ? 0 : Buffer.Length; + Context.SendHeader("Content-Length", length.ToString()); + OutputStream = Context.BeginResponseData(); + if (Buffer != null) Buffer.WriteTo(OutputStream); + Buffer = null; + Mode = HTTPResponseStreamMode.None; + break; + } + Context.EndResponseData(); + if (MaxLength != -1 && MaxLength > BytesWritten) Context.Close(); + } + } + public override bool CanTimeout { + get { return OutputStream == null ? true : OutputStream.CanTimeout; } + } + public override int WriteTimeout { + get { return OutputStream == null ? 0 : OutputStream.WriteTimeout; } + set { if (OutputStream != null) OutputStream.WriteTimeout = value; } + } + + public override bool CanRead { get { return false; } } + public override bool CanSeek { get { return false; } } + + public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + } + private class HTTPInputStream : Stream { + public HTTPResponseStreamMode Mode { get; private set; } + private Stream InputStream = null; + private long BytesRead = 0; + private long BytesLeft = 0; + public HTTPInputStream(HTTPContext context) { + String TransferEncoding = context.GetRequestHeader("Transfer-Encoding"); + String ContentLength = context.GetRequestHeader("Content-Length"); + InputStream = context.Reader; + if (TransferEncoding != null && TransferEncoding.StartsWith("chunked", StringComparison.InvariantCultureIgnoreCase)) { + Mode = HTTPResponseStreamMode.Chunked; + } else if (ContentLength != null) { + Mode = HTTPResponseStreamMode.Direct; + if (!long.TryParse(ContentLength, out BytesLeft)) BytesLeft = 0; + } else { + Mode = HTTPResponseStreamMode.None; + } + } + private int ReadDirect(Byte[] buffer, int offset, int count) { + if (count > BytesLeft) count = (int)BytesLeft; + if (count == 0) return 0; + int read = InputStream.Read(buffer, offset, count); + if (read >= 0) { + BytesRead += read; + BytesLeft -= read; + } + return read; + } + public override int Read(byte[] buffer, int offset, int count) { + if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); + switch (Mode) { + case HTTPResponseStreamMode.None: + return 0; + case HTTPResponseStreamMode.Direct: + return ReadDirect(buffer, offset, count); + case HTTPResponseStreamMode.Chunked: + if (BytesLeft == 0) { + String length = ReadLine(InputStream); + if (!long.TryParse(length, out BytesLeft)) BytesLeft = 0; + if (BytesLeft == 0) { + while (true) { + String line = ReadLine(InputStream); + if (line == null || line.Length == 0) break; + } + Mode = HTTPResponseStreamMode.None; + return 0; + } + } + return ReadDirect(buffer, offset, count); + default: + return 0; + } + } + class CompletedAsyncResult : AsyncResultBase { + public int Count { get; private set; } + public CompletedAsyncResult(AsyncCallback callback, Object state, int count) + : base(callback, state) { + this.Count = count; + SetCompleted(true, null); + } + } + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { + if (BytesLeft > 0) { + if (count > BytesLeft) count = (int)BytesLeft; + if (count == 0) return new CompletedAsyncResult(callback, state, 0); + return InputStream.BeginRead(buffer, offset, count, callback, state); + } else { + int ret = Read(buffer, offset, count); + return new CompletedAsyncResult(callback, state, ret); + } + } + public override int EndRead(IAsyncResult asyncResult) { + CompletedAsyncResult car = asyncResult as CompletedAsyncResult; + if (car != null) { + return car.Count; + } else { + return InputStream.EndRead(asyncResult); + } + } + public override long Length { + get { return BytesRead + BytesLeft; } + } + public override long Position { + get { return BytesRead; } + set { throw new NotSupportedException(); } + } + public override bool CanRead { + get { return (BytesLeft > 0 || Mode == HTTPResponseStreamMode.Chunked) && InputStream.CanRead; } + } + public override bool CanTimeout { + get { return InputStream.CanTimeout; } + } + public override int ReadTimeout { + get { return InputStream.ReadTimeout; } + set { InputStream.ReadTimeout = value; } + } + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + Byte[] dummy = new Byte[1024]; + while (Read(dummy, 0, dummy.Length) != 0) ; + } + + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override void Flush() { } + public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public override void SetLength(long value) { throw new NotSupportedException(); } + public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } public HTTPContext(HTTPServer server, TCPStream stream) : this(server, stream, stream.Socket) { } @@ -121,10 +413,10 @@ Reader.BeginPrebuffering(PrebufferCallback, null); } - private String ReadLine() { + private static String ReadLine(Stream stream) { StringBuilder s = new StringBuilder(); while (true) { - int b = Reader.ReadByte(); + int b = stream.ReadByte(); if (b == -1) { if (s.Length == 0) return null; break; @@ -137,6 +429,9 @@ } return s.ToString(); } + private String ReadLine() { + return ReadLine(Reader); + } private void PrebufferCallback(IAsyncResult ar) { State = HTTPConnectionState.ReceivingRequest; @@ -160,7 +455,7 @@ switch (request[2]) { case "HTTP/1.0": HTTPVersion = 10; break; case "HTTP/1.1": HTTPVersion = 11; break; - default: goto SendError400AndClose; + default: goto SendError505AndClose; } request = RequestAddress.Split(new Char[] { '?' }); RequestPath = Uri.UnescapeDataString(request[0]); @@ -178,6 +473,7 @@ if (content == null) goto SendError500AndClose; State = HTTPConnectionState.ProcessingRequest; content.ServeRequest(this); + Close(); } catch (Exception ex) { Console.Error.WriteLine(ex); switch (State) { @@ -197,6 +493,10 @@ State = HTTPConnectionState.ProcessingRequest; SendErrorAndClose(500); return; + SendError505AndClose: + State = HTTPConnectionState.ProcessingRequest; + SendErrorAndClose(400); + return; } public String GetRequestHeader(String name) { @@ -215,26 +515,82 @@ return items; } - public void SendErrorAndClose(int state) { - try { - SendStatus(state); - GetResponseStream(); - } catch (Exception ex) { - Console.Error.WriteLine(ex); + private static String UnescapeUrlDataString(String text) { + return Uri.UnescapeDataString(text.Replace('+', ' ')); + } + private static KeyValuePair[] DecodeUrlEncodedFields(String data) { + List> list = new List>(); + foreach (String arg in data.Split('&')) { + String[] parts = arg.Split(new Char[] { '=' }, 2); + String key = UnescapeUrlDataString(parts[0]); + String value = (parts.Length > 1) ? UnescapeUrlDataString(parts[1]) : String.Empty; + list.Add(new KeyValuePair(key, value)); } - Close(); + return list.ToArray(); + } + + public String GetQueryParameter(String name) { + foreach (KeyValuePair kvp in GetQueryParameters()) if (kvp.Key == name) return kvp.Value; + return null; + } + public String[] GetQueryParameters(String name) { + List list = new List(); + foreach (KeyValuePair kvp in GetQueryParameters()) if (kvp.Key == name) list.Add(kvp.Value); + return list.ToArray(); + } + public KeyValuePair[] GetQueryParameters() { + if (QueryParameters == null) QueryParameters = DecodeUrlEncodedFields(RequestQuery); + return QueryParameters; + } + + public String GetPostParameter(String name) { + foreach (KeyValuePair kvp in GetPostParameters()) if (kvp.Key == name) return kvp.Value; + return null; + } + public String[] GetPostParameters(String name) { + List list = new List(); + foreach (KeyValuePair kvp in GetPostParameters()) if (kvp.Key == name) list.Add(kvp.Value); + return list.ToArray(); + } + public KeyValuePair[] GetPostParameters() { + if (PostParameters == null) { + if (RequestMethod == "POST" && GetRequestHeader("Content-Type") == "application/x-www-form-urlencoded") { + String data; + using (StreamReader reader = new StreamReader(OpenRequestStream(), Encoding.UTF8)) data = reader.ReadToEnd(); + PostParameters = DecodeUrlEncodedFields(data); + } else { + PostParameters = new KeyValuePair[0]; + } + } + return PostParameters; + } + + public Stream OpenRequestStream() { + if (RequestStream == null) RequestStream = new HTTPInputStream(this); + return RequestStream; + } + + private static String GetMessageForStatus(int code) { + switch (code) { + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Access denied"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 500: return "Internal Server Error"; + case 505: return "HTTP Version Not Supported"; + default: return "Unknown Status"; + } } public void SendStatus(int code) { - String message; - switch (code) { - case 101: message = "Switching Protocols"; break; - case 200: message = "OK"; break; - case 400: message = "Bad Request"; break; - case 404: message = "Not Found"; break; - case 500: message = "Internal Server Error"; break; - default: message = "Unknown Status"; break; - } + String message = GetMessageForStatus(code); SendStatus(code, message); } public void SendStatus(int code, String message) { @@ -244,7 +600,7 @@ switch (HTTPVersion) { case 10: sb.Append("1.0"); break; case 11: sb.Append("1.1"); break; - default: throw new ArgumentException("The HTTP version is not supported", "HTTPVersion"); + default: sb.Append("1.0"); break; } sb.Append(" "); sb.Append(code); @@ -257,7 +613,7 @@ SendHeader("Cache-Control", "no-store, no-cache, must-revalidate"); SendHeader("Cache-Control", "post-check=0, pre-check=0"); SendHeader("Pragma", "no-cache"); - SendHeader("Server", "UCIS Webserver"); + SendHeader("Server", "UCIS Embedded Webserver"); SendHeader("Connection", "Close"); } } @@ -266,17 +622,63 @@ if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException(); Writer.WriteLine(name + ": " + value); } - public Stream GetResponseStream() { + public void SendErrorResponse(int state) { + String message = GetMessageForStatus(state); + try { + SendStatus(state, message); + SendHeader("Content-Type", "text/plain"); + WriteResponseData(Encoding.ASCII.GetBytes(String.Format("Error {0}: {1}", state, message))); + } catch (Exception ex) { + Console.Error.WriteLine(ex); + } + } + public Stream OpenResponseStream(HTTPResponseStreamMode mode) { + if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); + return ResponseStream = new HTTPOutputStream(this, mode); + } + public Stream OpenResponseStream(long length) { + if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); + if (length < 0) throw new ArgumentException("Response length can not be negative", "length"); + return ResponseStream = new HTTPOutputStream(this, HTTPResponseStreamMode.Direct, length); + } + public void WriteResponseData(Byte[] buffer) { + WriteResponseData(buffer, 0, buffer.Length); + } + public void WriteResponseData(Byte[] buffer, int offset, int count) { + if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); + SendHeader("Content-Length", count.ToString()); + Stream stream = BeginResponseData(); + stream.Write(buffer, offset, count); + EndResponseData(); + } + private Stream BeginResponseData() { if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); if (State == HTTPConnectionState.SendingHeaders) { Writer.WriteLine(); State = HTTPConnectionState.SendingContent; } - if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); + if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException("The response stream can not be opened in the current state"); + return Reader; + } + private void EndResponseData() { + if (State == HTTPConnectionState.Completed || State == HTTPConnectionState.Closed) return; + OpenRequestStream().Close(); + if (State != HTTPConnectionState.SendingContent) WriteResponseData(new Byte[0]); + State = HTTPConnectionState.Completed; + } + + public Stream GetDirectStream() { + if (State == HTTPConnectionState.Closed) throw new InvalidOperationException("The context has been closed"); + BeginResponseData(); + State = HTTPConnectionState.Closed; return Reader; } - public void Close() { + private void SendErrorAndClose(int code) { + SendErrorResponse(code); + Close(); + } + private void Close() { if (State == HTTPConnectionState.Closed) return; Reader.Close(); State = HTTPConnectionState.Closed; @@ -315,7 +717,7 @@ if (c.Value != null) { c.Value.ServeRequest(context); } else { - context.SendErrorAndClose(404); + context.SendErrorResponse(404); } } } @@ -338,16 +740,13 @@ public void ServeRequest(HTTPContext context) { ArraySegment content = ContentBuffer; if (content.Array == null) { - context.SendErrorAndClose(404); + context.SendErrorResponse(404); return; } String contentType = ContentType; context.SendStatus(200); if (contentType != null) context.SendHeader("Content-Type", contentType); - context.SendHeader("Content-Length", content.Count.ToString()); - Stream response = context.GetResponseStream(); - response.Write(content.Array, content.Offset, content.Count); - response.Close(); + context.WriteResponseData(content.Array, content.Offset, content.Count); } } public class HTTPFileProvider : IHTTPContentProvider { @@ -363,9 +762,8 @@ using (FileStream fs = File.OpenRead(FileName)) { context.SendStatus(200); context.SendHeader("Content-Type", ContentType); - context.SendHeader("Content-Length", fs.Length.ToString()); long left = fs.Length; - Stream response = context.GetResponseStream(); + Stream response = context.OpenResponseStream(fs.Length); byte[] buffer = new byte[1024 * 10]; while (fs.CanRead) { int len = fs.Read(buffer, 0, buffer.Length); @@ -376,7 +774,7 @@ response.Close(); } } else { - context.SendErrorAndClose(404); + context.SendErrorResponse(404); } } } @@ -387,7 +785,7 @@ } public void ServeRequest(HTTPContext context) { if (!File.Exists(TarFileName)) { - context.SendErrorAndClose(404); + context.SendErrorResponse(404); return; } String reqname1 = context.RequestPath; @@ -399,7 +797,6 @@ if (!file.IsFile) continue; if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue; context.SendStatus(200); - context.SendHeader("Content-Length", file.Size.ToString()); String ctype = null; switch (Path.GetExtension(file.Name).ToLowerInvariant()) { case ".txt": ctype = "text/plain"; break; @@ -414,7 +811,7 @@ case ".ico": ctype = "image/x-icon"; break; } if (ctype != null) context.SendHeader("Content-Type", ctype); - using (Stream response = context.GetResponseStream(), source = file.GetStream()) { + using (Stream response = context.OpenResponseStream(file.Size), source = file.GetStream()) { byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)]; while (source.CanRead) { int len = source.Read(buffer, 0, buffer.Length); @@ -424,7 +821,7 @@ } return; } - context.SendErrorAndClose(404); + context.SendErrorResponse(404); } } } diff -r 6aca18ee4ec6 -r 50d4aed66c67 Net/WebSocketPacketStream.cs --- a/Net/WebSocketPacketStream.cs Sat Nov 02 16:01:09 2013 +0100 +++ b/Net/WebSocketPacketStream.cs Mon Feb 03 22:53:31 2014 +0100 @@ -40,7 +40,7 @@ context.SendHeader("Upgrade", "websocket"); context.SendHeader("Sec-WebSocket-Accept", hashedKey); context.SendHeader("Sec-WebSocket-Protocol", binaryProtocol ? "binary" : "base64"); - baseStream = context.GetResponseStream(); + baseStream = context.GetDirectStream(); } else if (SecWebSocketKey1 != null && SecWebSocketKey2 != null) { wsProtocol = 100; Byte[] key = new Byte[4 + 4 + 8]; @@ -53,7 +53,7 @@ context.SendHeader("Sec-WebSocket-Protocol", binaryProtocol ? "binary" : "base64"); context.SendHeader("Sec-WebSocket-Origin", context.GetRequestHeader("Origin")); context.SendHeader("Sec-WebSocket-Location", "ws://" + context.GetRequestHeader("Host") + context.RequestPath); - baseStream = context.GetResponseStream(); + baseStream = context.GetDirectStream(); ReadAllBytes(key, 8, 8); using (MD5 md5 = new MD5CryptoServiceProvider()) key = md5.ComputeHash(key); baseStream.Write(key, 0, key.Length); @@ -63,7 +63,7 @@ if (closed) baseStream.Close(); } catch (Exception) { closed = true; - context.Close(); + if (baseStream != null) baseStream.Close(); } finally { negotiationDone = true; negotiationEvent.Set(); @@ -71,7 +71,7 @@ return; Failure: closed = true; - context.SendErrorAndClose(400); + context.SendErrorResponse(400); return; }