diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs index 6a4d9b24d6..05c1eb1878 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs @@ -82,15 +82,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { SocketInput.RemoteIntakeFin = true; _socket.ReadStop(); - - if (errorDone && error != null) - { - Log.LogError("Connection.OnRead", error); - } - else - { - Log.ConnectionReadFin(_connectionId); - } + Log.ConnectionReadFin(_connectionId); } SocketInput.SetCompleted(errorDone ? error : null); @@ -122,19 +114,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _connectionState = ConnectionState.Shutdown; Log.ConnectionWriteFin(_connectionId); - Thread.Post( - _this => - { - var shutdown = new UvShutdownReq(_this.Log); - shutdown.Init(_this.Thread.Loop); - shutdown.Shutdown(_this._socket, (req, status, state2) => - { - var __this = (Connection)state2; - __this.Log.ConnectionWroteFin(__this._connectionId, status); - req.Dispose(); - }, _this); - }, - this); + SocketOutput.End(endType); break; case ProduceEndType.ConnectionKeepAlive: if (_connectionState != ConnectionState.Open) @@ -152,13 +132,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _connectionState = ConnectionState.Disconnected; Log.ConnectionDisconnect(_connectionId); - Thread.Post( - _this => - { - Log.ConnectionStop(_this._connectionId); - _this._socket.Dispose(); - }, - this); + SocketOutput.End(endType); break; } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index 86b22b8bac..5bbb1db8a1 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -113,23 +113,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { await SocketInput; } - else - { - var x = 5; - } } - while (!terminated && !TakeMessageHeader2(SocketInput)) + while (!terminated && !TakeMessageHeaders(SocketInput)) { terminated = SocketInput.RemoteIntakeFin; if (!terminated) { await SocketInput; } - else - { - var x = 5; - } } if (!terminated) @@ -171,6 +163,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http // Connection Terminated! ConnectionControl.End(ProduceEndType.SocketShutdownSend); + + // Wait for client to disconnect, or to receive unexpected data + await SocketInput; + ConnectionControl.End(ProduceEndType.SocketDisconnect); } @@ -407,13 +403,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http WriteChunkedResponseSuffix(); } - if (!_keepAlive) - { - ConnectionControl.End(ProduceEndType.SocketShutdownSend); - } - - //NOTE: must finish reading request body - ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect); + ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketShutdownSend); } private Tuple, IDisposable> CreateResponseHeader( @@ -515,36 +505,51 @@ namespace Microsoft.AspNet.Server.Kestrel.Http private bool TakeStartLine(SocketInput input) { - var begin = input.GetIterator(); - if (begin.IsDefault) return false; + var scan = input.GetIterator(); - var end = begin.IndexOf(' '); - var method = begin.GetString(end); - - char chFound; - begin = end.Add(1); - end = begin.IndexOfAny(' ', '?', out chFound); - var requestUri = begin.GetString(end); - - begin = end; - end = chFound == '?' ? begin.IndexOf(' ') : begin; - var queryString = begin.GetString(end); - - begin = end.Add(1); - end = begin.IndexOf('\r'); - var httpVersion = begin.GetString(end); - - end = end.Add(1); - if (end.Peek() != '\n') + var begin = scan; + if (scan.Seek(' ') == -1) { return false; } + var method = begin.GetString(scan); + + scan.Take(); + begin = scan; + var chFound = scan.Seek(' ', '?'); + if (chFound == -1) + { + return false; + } + var requestUri = begin.GetString(scan); + + var queryString = ""; + if (chFound == '?') + { + begin = scan; + if (scan.Seek(' ') != ' ') + { + return false; + } + queryString = begin.GetString(scan); + } + + scan.Take(); + begin = scan; + if (scan.Seek('\r') == -1) + { + return false; + } + var httpVersion = begin.GetString(scan); + + scan.Take(); + if (scan.Take() != '\n') return false; Method = method; RequestUri = requestUri; QueryString = queryString; HttpVersion = httpVersion; - input.JumpTo(end.Add(1)); + input.JumpTo(scan); return true; } @@ -553,150 +558,98 @@ namespace Microsoft.AspNet.Server.Kestrel.Http return Encoding.UTF8.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex); } - private bool TakeMessageHeader2(SocketInput baton) + private bool TakeMessageHeaders(SocketInput input) { - char chFirst; - char chSecond; - var scan = baton.GetIterator(); - while (!scan.IsDefault) + int chFirst; + int chSecond; + var scan = input.GetIterator(); + var consumed = scan; + try { - var beginName = scan; - scan = scan.IndexOfAny(':', '\r', out chFirst); - - var endName = scan; - chSecond = scan.MoveNext(); - - if (chFirst == '\r' && chSecond == '\n') + while (!scan.IsEnd) { - baton.JumpTo(scan.Add(1)); - return true; - } - if (chFirst == char.MinValue) - { - return false; - } + var beginName = scan; + scan.Seek(':', '\r'); + var endName = scan; - while ( - chSecond == ' ' || - chSecond == '\t' || - chSecond == '\r' || - chSecond == '\n') - { - chSecond = scan.MoveNext(); - } + chFirst = scan.Take(); + var beginValue = scan; + chSecond = scan.Take(); - var beginValue = scan; - var wrapping = false; - while (!scan.IsDefault) - { - var endValue = scan = scan.IndexOf('\r'); - chFirst = scan.MoveNext(); - if (chFirst != '\n') + if (chFirst == -1 || chSecond == -1) { - continue; + return false; } - chSecond = scan.MoveNext(); - if (chSecond == ' ' || chSecond == '\t') + if (chFirst == '\r') { - wrapping = true; - continue; - } - var name = beginName.GetArraySegment(endName); - var value = beginValue.GetString(endValue); - if (wrapping) - { - value = value.Replace("\r\n", " "); + if (chSecond == '\n') + { + consumed = scan; + return true; + } + throw new Exception("Malformed request"); } - _requestHeaders.Append(name.Array, name.Offset, name.Count, value); - break; + while ( + chSecond == ' ' || + chSecond == '\t' || + chSecond == '\r' || + chSecond == '\n') + { + beginValue = scan; + chSecond = scan.Take(); + } + scan = beginValue; + + var wrapping = false; + while (!scan.IsEnd) + { + if (scan.Seek('\r') == -1) + { + // no "\r" in sight, burn used bytes and go back to await more data + return false; + } + + var endValue = scan; + chFirst = scan.Take(); // expecting: /r + chSecond = scan.Take(); // expecting: /n + + if (chSecond == '\r') + { + // special case, "\r\r". move to the 2nd "\r" and try again + scan = endValue; + scan.Take(); + continue; + } + + var chThird = scan.Peek(); + if (chThird == ' ' || chThird == '\t') + { + // special case, "\r\n " or "\r\n\t". + // this is considered wrapping"linear whitespace" and is actually part of the header value + // continue past this for the next + wrapping = true; + continue; + } + + var name = beginName.GetArraySegment(endName); + var value = beginValue.GetString(endValue); + if (wrapping) + { + value = value.Replace("\r\n", " "); + } + + _requestHeaders.Append(name.Array, name.Offset, name.Count, value); + consumed = scan; + break; + } } - } - - return false; - } - - private bool TakeMessageHeader(SocketInput baton, out bool endOfHeaders) - { - var remaining = baton.Buffer; - endOfHeaders = false; - if (remaining.Count < 2) - { return false; } - var ch0 = remaining.Array[remaining.Offset]; - var ch1 = remaining.Array[remaining.Offset + 1]; - if (ch0 == '\r' && ch1 == '\n') + finally { - endOfHeaders = true; - baton.Skip(2); - return true; + input.JumpTo(consumed); } - - if (remaining.Count < 3) - { - return false; - } - var wrappedHeaders = false; - var colonIndex = -1; - var valueStartIndex = -1; - var valueEndIndex = -1; - for (var index = 0; index != remaining.Count - 2; ++index) - { - var ch2 = remaining.Array[remaining.Offset + index + 2]; - if (ch0 == '\r' && - ch1 == '\n' && - ch2 != ' ' && - ch2 != '\t') - { - var value = ""; - if (valueEndIndex != -1) - { - value = _ascii.GetString( - remaining.Array, remaining.Offset + valueStartIndex, valueEndIndex - valueStartIndex); - } - if (wrappedHeaders) - { - value = value.Replace("\r\n", " "); - } - AddRequestHeader(remaining.Array, remaining.Offset, colonIndex, value); - baton.Skip(index + 2); - return true; - } - if (colonIndex == -1 && ch0 == ':') - { - colonIndex = index; - } - else if (colonIndex != -1 && - ch0 != ' ' && - ch0 != '\t' && - ch0 != '\r' && - ch0 != '\n') - { - if (valueStartIndex == -1) - { - valueStartIndex = index; - } - valueEndIndex = index + 1; - } - else if (!wrappedHeaders && - ch0 == '\r' && - ch1 == '\n' && - (ch2 == ' ' || - ch2 == '\t')) - { - wrappedHeaders = true; - } - - ch0 = ch1; - ch1 = ch2; - } - return false; - } - - private void AddRequestHeader(byte[] keyBytes, int keyOffset, int keyLength, string value) - { - _requestHeaders.Append(keyBytes, keyOffset, keyLength, value); } public bool StatusCanHaveBody(int statusCode) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/ISocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/ISocketOutput.cs index 4edffa3055..00c567d571 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/ISocketOutput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/ISocketOutput.cs @@ -14,5 +14,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { void Write(ArraySegment buffer, bool immediate = true); Task WriteAsync(ArraySegment buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken)); + void End(ProduceEndType endType); } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs index 0ad2ea8645..9256bdb314 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Diagnostics; -using System.Runtime.InteropServices; namespace Microsoft.AspNet.Server.Kestrel.Http { @@ -15,6 +13,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http private ConcurrentStack _blocks = new ConcurrentStack(); private ConcurrentStack _slabs = new ConcurrentStack(); + private bool disposedValue = false; // To detect redundant calls public MemoryPoolBlock2 Lease(int minimumSize) { @@ -27,7 +26,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http slab: null); } - for (;;) + while (true) { MemoryPoolBlock2 block; if (_blocks.TryPop(out block)) @@ -66,7 +65,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs index 8bb913ed6e..7d2f8d27d1 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs @@ -135,246 +135,284 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public bool IsDefault => _block == null; + public bool IsEnd + { + get + { + if (_block == null) + { + return true; + } + else if (_index < _block.End) + { + return false; + } + else + { + for (var block = _block.Next; block != null; block = block.Next) + { + if (block.Start < block.End) + { + return true; + } + } + return true; + } + } + } + public MemoryPoolBlock2 Block => _block; public int Index => _index; - public bool HasAtLeast(int count) + public int Take() { - var scan = _block; - var index = _index; - while (scan != null) + if (_block == null) { - if (count <= scan.End - index) - { - return true; - } - count -= scan.End - index; - scan = scan.Next; - index = scan?.Start ?? 0; + return -1; + } + else if (_index < _block.End) + { + return _block.Array[_index++]; } - return false; - } - public Iterator Add(int count) - { var block = _block; var index = _index; - while (block != null) + while (true) { - var tailCount = block.End - index; - if (count < tailCount) + if (index < block.End) { - return new Iterator(block, index + count); + _block = block; + _index = index + 1; + return block.Array[index]; } - count -= tailCount; - block = block.Next; - index = block?.Start ?? 0; - } - return new Iterator(block, index + count); - } - - public Iterator CopyTo(byte[] array, int offset, int count, out int actual) - { - var block = _block; - var index = _index; - var remaining = count; - while (block != null && remaining != 0) - { - var copyLength = Math.Min(remaining, block.End - index); - Buffer.BlockCopy(block.Array, index, array, offset, copyLength); - index += copyLength; - offset += copyLength; - remaining -= copyLength; - if (index == block.End) + else if (block.Next == null) + { + return -1; + } + else { block = block.Next; - index = block?.Start ?? 0; + index = block.Start; } } - actual = count - remaining; - return new Iterator(block, index); - } - - public char MoveNext() - { - var block = _block; - var index = _index; - while (block != null && index == block.End) - { - block = block.Next; - index = block?.Start ?? 0; - } - if (block != null) - { - ++index; - } - while (block != null && index == block.End) - { - block = block.Next; - index = block?.Start ?? 0; - } - _block = block; - _index = index; - return block != null ? (char)block.Array[index] : char.MinValue; } public int Peek() { - while (_block != null) + if (_block == null) { - if (_index < _block.End) - { - return _block.Data.Array[_index]; - } - _block = _block.Next; - _index = _block.Start; + return -1; + } + else if (_index < _block.End) + { + return _block.Array[_index]; + } + else if (_block.Next == null) + { + return -1; + } + + var block = _block.Next; + var index = block.Start; + while (true) + { + if (index < block.End) + { + return block.Array[index]; + } + else if (block.Next == null) + { + return -1; + } + else + { + block = block.Next; + index = block.Start; + } } - return -1; } - public Iterator IndexOf(char char0) + public int Seek(int char0) { + if (IsDefault) + { + return -1; + } + var byte0 = (byte)char0; var vectorStride = Vector.Count; var ch0Vector = new Vector(byte0); - var scanBlock = _block; - var scanArray = scanBlock?.Array; - var scanIndex = _index; - while (scanBlock != null) + var block = _block; + var index = _index; + var array = block.Array; + while (true) { - var tailCount = scanBlock.End - scanIndex; - if (tailCount == 0) + while (block.End == index) { - scanBlock = scanBlock.Next; - scanArray = scanBlock?.Array; - scanIndex = scanBlock?.Start ?? 0; - continue; + if (block.Next == null) + { + _block = block; + _index = index; + return -1; + } + block = block.Next; + index = block.Start; + array = block.Array; } - if (tailCount >= vectorStride) + while (block.End != index) { - var data = new Vector(scanBlock.Array, scanIndex); - var ch0Equals = Vector.Equals(data, ch0Vector); - var ch0Count = Vector.Dot(ch0Equals, _dotCount); + var following = block.End - index; + if (following >= vectorStride) + { + var data = new Vector(array, index); + var ch0Equals = Vector.Equals(data, ch0Vector); + var ch0Count = Vector.Dot(ch0Equals, _dotCount); - if (ch0Count == 0) - { - scanIndex += vectorStride; - continue; + if (ch0Count == 0) + { + index += vectorStride; + continue; + } + else if (ch0Count == 1) + { + _block = block; + _index = index + Vector.Dot(ch0Equals, _dotIndex); + return char0; + } + else + { + following = vectorStride; + } } - else if (ch0Count == 1) + for (; following != 0; following--, index++) { - return new Iterator(scanBlock, scanIndex + Vector.Dot(ch0Equals, _dotIndex)); - } - else - { - tailCount = vectorStride; - } - } - for (; tailCount != 0; tailCount--, scanIndex++) - { - var ch = scanBlock.Array[scanIndex]; - if (ch == byte0) - { - return new Iterator(scanBlock, scanIndex); + if (block.Array[index] == byte0) + { + _block = block; + _index = index; + return char0; + } } } } - return new Iterator(null, 0); } - public Iterator IndexOfAny(char char0, char char1, out char chFound) + public int Seek(int char0, int char1) { + if (IsDefault) + { + return -1; + } + var byte0 = (byte)char0; var byte1 = (byte)char1; var vectorStride = Vector.Count; var ch0Vector = new Vector(byte0); var ch1Vector = new Vector(byte1); - var scanBlock = _block; - var scanArray = scanBlock?.Array; - var scanIndex = _index; - while (scanBlock != null) + var block = _block; + var index = _index; + var array = block.Array; + while (true) { - var tailCount = scanBlock.End - scanIndex; - if (tailCount == 0) + while (block.End == index) { - scanBlock = scanBlock.Next; - scanArray = scanBlock?.Array; - scanIndex = scanBlock?.Start ?? 0; - continue; - } - if (tailCount >= vectorStride) - { - var data = new Vector(scanBlock.Array, scanIndex); - var ch0Equals = Vector.Equals(data, ch0Vector); - var ch0Count = Vector.Dot(ch0Equals, _dotCount); - var ch1Equals = Vector.Equals(data, ch1Vector); - var ch1Count = Vector.Dot(ch1Equals, _dotCount); - - if (ch0Count == 0 && ch1Count == 0) + if (block.Next == null) { - scanIndex += vectorStride; - continue; + _block = block; + _index = index; + return -1; } - else if (ch0Count < 2 && ch1Count < 2) + block = block.Next; + index = block.Start; + array = block.Array; + } + while (block.End != index) + { + var following = block.End - index; + if (following >= vectorStride) { - var ch0Index = ch0Count == 1 ? Vector.Dot(ch0Equals, _dotIndex) : byte.MaxValue; - var ch1Index = ch1Count == 1 ? Vector.Dot(ch1Equals, _dotIndex) : byte.MaxValue; - if (ch0Index < ch1Index) + var data = new Vector(array, index); + var ch0Equals = Vector.Equals(data, ch0Vector); + var ch0Count = Vector.Dot(ch0Equals, _dotCount); + var ch1Equals = Vector.Equals(data, ch1Vector); + var ch1Count = Vector.Dot(ch1Equals, _dotCount); + + if (ch0Count == 0 && ch1Count == 0) { - chFound = char0; - return new Iterator(scanBlock, scanIndex + ch0Index); + index += vectorStride; + continue; + } + else if (ch0Count < 2 && ch1Count < 2) + { + var ch0Index = ch0Count == 1 ? Vector.Dot(ch0Equals, _dotIndex) : byte.MaxValue; + var ch1Index = ch1Count == 1 ? Vector.Dot(ch1Equals, _dotIndex) : byte.MaxValue; + if (ch0Index < ch1Index) + { + _block = block; + _index = index + ch0Index; + return char0; + } + else + { + _block = block; + _index = index + ch1Index; + return char1; + } } else { - chFound = char1; - return new Iterator(scanBlock, scanIndex + ch1Index); + following = vectorStride; } } - else + for (; following != 0; following--, index++) { - tailCount = vectorStride; - } - } - for (; tailCount != 0; tailCount--, scanIndex++) - { - var chIndex = scanBlock.Array[scanIndex]; - if (chIndex == byte0) - { - chFound = char0; - return new Iterator(scanBlock, scanIndex); - } - else if (chIndex == byte1) - { - chFound = char1; - return new Iterator(scanBlock, scanIndex); + var byteIndex = block.Array[index]; + if (byteIndex == byte0) + { + _block = block; + _index = index; + return char0; + } + else if (byteIndex == byte1) + { + _block = block; + _index = index; + return char1; + } } } } - chFound = char.MinValue; - return new Iterator(null, 0); } public int GetLength(Iterator end) { - var length = 0; + if (IsDefault || end.IsDefault) + { + return -1; + } + var block = _block; var index = _index; - for (;;) + var length = 0; + while (true) { if (block == end._block) { return length + end._index - index; } - if (block == null) + else if (block.Next == null) { - throw new Exception("end was not after iterator"); + throw new Exception("end did not follow iterator"); + } + else + { + length += block.End - index; + block = block.Next; + index = block.Start; } - length += block.End - index; - block = block.Next; - index = block?.Start ?? 0; } } @@ -386,53 +424,74 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } if (end._block == _block) { - return Encoding.ASCII.GetString(_block.Array, _index, end._index - _index); - } - if (end._block == _block.Next && end._index == end._block.Start) - { - return Encoding.ASCII.GetString(_block.Array, _index, _block.End - _index); + return Encoding.UTF8.GetString(_block.Array, _index, end._index - _index); } - var length = GetLength(end); - var result = new char[length]; - var offset = 0; var decoder = Encoding.ASCII.GetDecoder(); + var length = GetLength(end); + var charLength = length * 2; + var chars = new char[charLength]; + var charIndex = 0; + var block = _block; var index = _index; - while (length != 0) + var remaining = length; + while (true) { - if (block == null) - { - throw new Exception("Unexpected end of data"); - } - - var count = Math.Min(block.End - index, length); - int bytesUsed; - int textAdded; + int charsUsed; bool completed; - decoder.Convert( - block.Array, - index, - count, - result, - offset, - length, - count == length, - out bytesUsed, - out textAdded, - out completed); - - Debug.Assert(bytesUsed == count); - Debug.Assert(textAdded == count); - offset += count; - length -= count; - - block = block.Next; - index = block?.Start ?? 0; + var following = block.End - index; + if (remaining <= following) + { + decoder.Convert( + block.Array, + index, + remaining, + chars, + charIndex, + charLength - charIndex, + true, + out bytesUsed, + out charsUsed, + out completed); + return new string(chars, 0, charIndex + charsUsed); + } + else if (block.Next == null) + { + decoder.Convert( + block.Array, + index, + following, + chars, + charIndex, + charLength - charIndex, + true, + out bytesUsed, + out charsUsed, + out completed); + return new string(chars, 0, charIndex + charsUsed); + } + else + { + decoder.Convert( + block.Array, + index, + following, + chars, + charIndex, + charLength - charIndex, + false, + out bytesUsed, + out charsUsed, + out completed); + charIndex += charsUsed; + remaining -= following; + block = block.Next; + index = block.Start; + } } - return new string(result); } public ArraySegment GetArraySegment(Iterator end) @@ -445,34 +504,47 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { return new ArraySegment(_block.Array, _index, end._index - _index); } - if (end._block == _block.Next && end._index == end._block.Start) - { - return new ArraySegment(_block.Array, _index, _block.End - _index); - } var length = GetLength(end); - var result = new ArraySegment(new byte[length]); - var offset = result.Offset; + var array = new byte[length]; + CopyTo(array, 0, length, out length); + return new ArraySegment(array, 0, length); + } + + public Iterator CopyTo(byte[] array, int offset, int count, out int actual) + { + if (IsDefault) + { + actual = 0; + return this; + } var block = _block; var index = _index; - while (length != 0) + var remaining = count; + while (true) { - if (block == null) + var following = block.End - index; + if (remaining <= following) { - throw new Exception("Unexpected end of data"); + actual = count; + Buffer.BlockCopy(block.Array, index, array, offset, remaining); + return new Iterator(block, index + remaining); + } + else if (block.Next == null) + { + actual = count - remaining + following; + Buffer.BlockCopy(block.Array, index, array, offset, following); + return new Iterator(block, index + following); + } + else + { + Buffer.BlockCopy(block.Array, index, array, offset, following); + remaining -= following; + block = block.Next; + index = block.Start; } - - var count = Math.Min(block.End - index, length); - Buffer.BlockCopy(block.Array, index, result.Array, offset, count); - offset += count; - length -= count; - - block = block.Next; - index = block?.Start ?? 0; } - - return result; } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs index 561d26d4df..88b20c78b4 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs @@ -11,6 +11,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public byte[] Array; public IntPtr ArrayPtr; public bool IsActive; + private bool disposedValue = false; // To detect redundant calls public static MemoryPoolSlab2 Create(int length) { @@ -26,7 +27,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs index 2e78402899..257df299aa 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs @@ -86,14 +86,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public override async Task ReadAsyncImplementation(ArraySegment buffer, CancellationToken cancellationToken) { var input = _context.SocketInput; + while (true) + { + await input; - await input; + var begin = input.GetIterator(); + int actual; + var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual); + input.JumpTo(end); - var begin = input.GetIterator(); - int actual; - var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual); - input.JumpTo(end); - return actual; + if (actual != 0) + { + return actual; + } + if (input.RemoteIntakeFin) + { + return 0; + } + } } } @@ -109,23 +119,35 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _contentLength = contentLength; _inputLength = _contentLength; } - + public override async Task ReadAsyncImplementation(ArraySegment buffer, CancellationToken cancellationToken) { var input = _context.SocketInput; - var limit = Math.Min(buffer.Count, _inputLength); - if (limit != 0) + while (true) { - await input; - } + var limit = Math.Min(buffer.Count, _inputLength); + if (limit == 0) + { + return 0; + } - var begin = input.GetIterator(); - int actual; - var end = begin.CopyTo(buffer.Array, buffer.Offset, limit, out actual); - _inputLength -= actual; - input.JumpTo(end); - return actual; + await input; + + var begin = input.GetIterator(); + int actual; + var end = begin.CopyTo(buffer.Array, buffer.Offset, limit, out actual); + _inputLength -= actual; + input.JumpTo(end); + if (actual != 0) + { + return actual; + } + if (input.RemoteIntakeFin) + { + throw new Exception("Unexpected end of request content"); + } + } } } @@ -186,21 +208,23 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { _mode = Mode.ChunkSuffix; } - - return actual; + if (actual != 0) + { + return actual; + } } while (_mode == Mode.ChunkSuffix) { - var begin = input.GetIterator(); - var ch1 = begin.Peek(); - var ch2 = begin.MoveNext(); - if (ch1 == char.MinValue || ch2 == char.MinValue) + var scan = input.GetIterator(); + var ch1 = scan.Take(); + var ch2 = scan.Take(); + if (ch1 == -1 || ch2 == -1) { await input; } else if (ch1 == '\r' && ch2 == '\n') { - input.JumpTo(begin.Add(1)); + input.JumpTo(scan); _mode = Mode.ChunkPrefix; } else @@ -215,13 +239,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http private static bool TakeChunkedLine(SocketInput baton, ref int chunkSizeOut) { - var remaining = baton.GetIterator(); - var ch0 = remaining.Peek(); + var scan = baton.GetIterator(); + var ch0 = scan.Take(); var chunkSize = 0; var mode = 0; - while(ch0 != -1) + while (ch0 != -1) { - var ch1 = remaining.MoveNext(); + var ch1 = scan.Take(); + if (ch1 == -1) + { + return false; + } if (mode == 0) { @@ -263,7 +291,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } else if (ch0 == '\r' && ch1 == '\n') { - baton.JumpTo(remaining.Add(1)); + baton.JumpTo(scan); chunkSizeOut = chunkSize; return true; } @@ -276,7 +304,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { if (ch0 == '\r' && ch1 == '\n') { - baton.JumpTo(remaining.Add(1)); + baton.JumpTo(scan); chunkSizeOut = chunkSize; return true; } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs index 27f42c8a5e..1ff2530bc4 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs @@ -206,19 +206,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http lock (_syncHeadAndTail) { // TODO: leave _pinned intact + // TODO: return when empty returnStart = _head; returnEnd = iterator.Block; _head = iterator.Block; - if (_head == null) + _head.Start = iterator.Index; + if (iterator.IsEnd) { - _tail = null; SetNotCompleted(); } - else - { - _head.Start = iterator.Index; - } } while (returnStart != returnEnd) { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs index b6dcd0967f..7e35251e38 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs @@ -42,14 +42,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _callbacksPending = new Queue(); } - public void Write(ArraySegment buffer, Action callback, object state, bool immediate = true) + public void Write( + ArraySegment buffer, + Action callback, + object state, + bool immediate = true, + bool socketShutdownSend = false, + bool socketDisconnect = false) { //TODO: need buffering that works - var copy = new byte[buffer.Count]; - Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count); - buffer = new ArraySegment(copy); - - _log.ConnectionWrite(_connectionId, buffer.Count); + if (buffer.Array != null) + { + var copy = new byte[buffer.Count]; + Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count); + buffer = new ArraySegment(copy); + _log.ConnectionWrite(_connectionId, buffer.Count); + } bool triggerCallbackNow = false; @@ -60,8 +68,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _nextWriteContext = new WriteContext(this); } - _nextWriteContext.Buffers.Enqueue(buffer); - + if (buffer.Array != null) + { + _nextWriteContext.Buffers.Enqueue(buffer); + } + if (socketShutdownSend) + { + _nextWriteContext.SocketShutdownSend = true; + } + if (socketDisconnect) + { + _nextWriteContext.SocketDisconnect = true; + } // Complete the write task immediately if all previous write tasks have been completed, // the buffers haven't grown too large, and the last write to the socket succeeded. triggerCallbackNow = _lastWriteError == null && @@ -121,22 +139,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http try { - var buffers = new ArraySegment[writingContext.Buffers.Count]; - - var i = 0; - foreach (var buffer in writingContext.Buffers) - { - buffers[i++] = buffer; - } - - var writeReq = new UvWriteReq(_log); - writeReq.Init(_thread.Loop); - - writeReq.Write(_socket, new ArraySegment>(buffers), (r, status, error, state) => - { - var writtenContext = (WriteContext)state; - writtenContext.Self.OnWriteCompleted(writtenContext.Buffers, r, status, error); - }, writingContext); + writingContext.Execute(); } catch { @@ -152,7 +155,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } // This is called on the libuv event loop - private void OnWriteCompleted(Queue> writtenBuffers, UvWriteReq req, int status, Exception error) + private void OnWriteCompleted(Queue> writtenBuffers, int status, Exception error) { _log.ConnectionWriteCallback(_connectionId, status); @@ -194,8 +197,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http Trace.Assert(_numBytesPreCompleted >= 0); Trace.Assert(_numBytesPreCompleted <= _maxBytesPreCompleted); } - - req.Dispose(); } private void TriggerCallback(CallbackContext context) @@ -237,6 +238,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Http return tcs.Task; } + void ISocketOutput.End(ProduceEndType endType) + { + switch (endType) + { + case ProduceEndType.SocketShutdownSend: + Write(default(ArraySegment), (error, state) => { }, null, + immediate: true, + socketShutdownSend: true, + socketDisconnect: false); + break; + case ProduceEndType.SocketDisconnect: + Write(default(ArraySegment), (error, state) => { }, null, + immediate: true, + socketShutdownSend: false, + socketDisconnect: true); + break; + } + } + private class CallbackContext { public Exception Error; @@ -248,13 +268,89 @@ namespace Microsoft.AspNet.Server.Kestrel.Http private class WriteContext { public SocketOutput Self; + public Queue> Buffers; + public bool SocketShutdownSend; + public bool SocketDisconnect; + + public int WriteStatus; + public Exception WriteError; + + public int ShutdownSendStatus; public WriteContext(SocketOutput self) { Self = self; Buffers = new Queue>(); } + + public void Execute() + { + if (Buffers.Count == 0 || Self._socket.IsClosed) + { + StageTwo(); + return; + } + + var buffers = new ArraySegment[Buffers.Count]; + + var i = 0; + foreach (var buffer in Buffers) + { + buffers[i++] = buffer; + } + + var writeReq = new UvWriteReq(Self._log); + writeReq.Init(Self._thread.Loop); + writeReq.Write(Self._socket, new ArraySegment>(buffers), (_writeReq, status, error, state) => + { + _writeReq.Dispose(); + var _this = (WriteContext)state; + _this.WriteStatus = status; + _this.WriteError = error; + StageTwo(); + }, this); + } + + public void StageTwo() + { + if (SocketShutdownSend == false || Self._socket.IsClosed) + { + StageThree(); + return; + } + + var shutdownReq = new UvShutdownReq(Self._log); + shutdownReq.Init(Self._thread.Loop); + shutdownReq.Shutdown(Self._socket, (_shutdownReq, status, state) => + { + _shutdownReq.Dispose(); + var _this = (WriteContext)state; + _this.ShutdownSendStatus = status; + + Self._log.ConnectionWroteFin(Self._connectionId, status); + + StageThree(); + }, this); + } + + public void StageThree() + { + if (SocketDisconnect == false || Self._socket.IsClosed) + { + Complete(); + return; + } + + Self._socket.Dispose(); + Self._log.ConnectionStop(Self._connectionId); + Complete(); + } + + public void Complete() + { + Self.OnWriteCompleted(Buffers, WriteStatus, WriteError); + } } } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs index 0a49cb3f85..881c295c6a 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs @@ -12,6 +12,7 @@ using Microsoft.AspNet.Server.Kestrel.Http; using Microsoft.Dnx.Runtime; using Microsoft.Dnx.Runtime.Infrastructure; using Xunit; +using System.Linq; namespace Microsoft.AspNet.Server.KestrelTests { @@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Server.KestrelTests private async Task App(Frame frame) { frame.ResponseHeaders.Clear(); - for (; ;) + while (true) { var buffer = new byte[8192]; var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length); @@ -60,17 +61,17 @@ namespace Microsoft.AspNet.Server.KestrelTests private async Task AppChunked(Frame frame) { + foreach (var h in frame.RequestHeaders) + { + Console.WriteLine($"{h.Key}: {h.Value}"); + } + Console.WriteLine($""); + frame.ResponseHeaders.Clear(); var data = new MemoryStream(); - for (; ;) + while(true) { - var buffer = new byte[8192]; - var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length); - if (count == 0) - { - break; - } - data.Write(buffer, 0, count); + await frame.RequestBody.CopyToAsync(data); } var bytes = data.ToArray(); frame.ResponseHeaders["Content-Length"] = new[] { bytes.Length.ToString() }; @@ -115,7 +116,7 @@ namespace Microsoft.AspNet.Server.KestrelTests socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); socket.Shutdown(SocketShutdown.Send); var buffer = new byte[8192]; - for (; ;) + for (;;) { var length = socket.Receive(buffer); if (length == 0) { break; } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolBlock2Tests.cs b/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolBlock2Tests.cs index c5f91b33ac..a9c6e5a24f 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolBlock2Tests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolBlock2Tests.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Server.KestrelTests public class MemoryPoolBlock2Tests { [Fact] - public void IndexOfAnyWorks() + public void SeekWorks() { using (var pool = new MemoryPool2()) { @@ -22,7 +22,16 @@ namespace Microsoft.AspNet.Server.KestrelTests var iterator = block.GetIterator(); foreach (var ch in Enumerable.Range(0, 256).Select(x => (char)x)) { - var hit = iterator.IndexOf(ch); + var hit = iterator; + hit.Seek(ch); + Assert.Equal(ch, iterator.GetLength(hit)); + + hit = iterator; + hit.Seek(ch, byte.MaxValue); + Assert.Equal(ch, iterator.GetLength(hit)); + + hit = iterator; + hit.Seek(byte.MaxValue, ch); Assert.Equal(ch, iterator.GetLength(hit)); } } @@ -72,5 +81,49 @@ namespace Microsoft.AspNet.Server.KestrelTests } } } + + [Fact] + public void AddDoesNotAdvanceAtEndOfCurrentBlock() + { + using (var pool = new MemoryPool2()) + { + var block1 = pool.Lease(256); + var block2 = block1.Next = pool.Lease(256); + + block1.End += 100; + block2.End += 200; + + var iter0 = block1.GetIterator(); + var iter100 = iter0.Add(100); + + var iter200a = iter0.Add(200); + var iter200b = iter100.Add(100); + + var iter300a = iter0.Add(300); + var iter300b = iter100.Add(200); + var iter300c = iter200a.Add(100); + + var iter300a2 = iter300a.Add(1); + var iter300b2 = iter300b.Add(1); + var iter300c2 = iter300c.Add(1); + + AssertIterator(iter0, block1, block1.Start); + AssertIterator(iter100, block1, block1.End); + AssertIterator(iter200a, block2, block2.Start+100); + AssertIterator(iter200b, block2, block2.Start + 100); + AssertIterator(iter300a, block2, block2.End); + AssertIterator(iter300b, block2, block2.End); + AssertIterator(iter300c, block2, block2.End); + AssertIterator(iter300a2, block2, block2.End); + AssertIterator(iter300b2, block2, block2.End); + AssertIterator(iter300c2, block2, block2.End); + } + } + + private void AssertIterator(MemoryPoolBlock2.Iterator iter, MemoryPoolBlock2 block, int index) + { + Assert.Same(block, iter.Block); + Assert.Equal(index, iter.Index); + } } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolExtensions.cs b/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolExtensions.cs new file mode 100644 index 0000000000..3578dda184 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNet.Server.Kestrel.Http; + +namespace Microsoft.AspNet.Server.KestrelTests +{ + public static class MemoryPoolExtensions + { + public static MemoryPoolBlock2.Iterator Add(this MemoryPoolBlock2.Iterator iterator, int count) + { + int actual; + return iterator.CopyTo(new byte[count], 0, count, out actual); + } + } +} diff --git a/tools/Microsoft.StandardsPolice/StandardsPoliceCompileModule.cs b/tools/Microsoft.StandardsPolice/StandardsPoliceCompileModule.cs index 5de3961d58..5e3e5dfa06 100644 --- a/tools/Microsoft.StandardsPolice/StandardsPoliceCompileModule.cs +++ b/tools/Microsoft.StandardsPolice/StandardsPoliceCompileModule.cs @@ -71,8 +71,8 @@ namespace Microsoft.StandardsPolice acceptableOrder = true; } if (!acceptableOrder && - priorUsingDirective.Name.ToString().StartsWith("System.") && - !usingDirective.Name.ToString().StartsWith("System.")) + priorUsingDirective.Name.ToString().StartsWith("System") && + !usingDirective.Name.ToString().StartsWith("System")) { acceptableOrder = true; }