diff options
Diffstat (limited to 'lib/Net.Http/src')
-rw-r--r-- | lib/Net.Http/src/Core/TransportReader.cs | 180 | ||||
-rw-r--r-- | lib/Net.Http/src/Helpers/HelperTypes.cs | 6 | ||||
-rw-r--r-- | lib/Net.Http/src/Helpers/HttpHelpers.cs | 108 |
3 files changed, 185 insertions, 109 deletions
diff --git a/lib/Net.Http/src/Core/TransportReader.cs b/lib/Net.Http/src/Core/TransportReader.cs index 8d605d1..a512331 100644 --- a/lib/Net.Http/src/Core/TransportReader.cs +++ b/lib/Net.Http/src/Core/TransportReader.cs @@ -41,14 +41,7 @@ namespace VNLib.Net.Http.Core /// </summary> internal readonly struct TransportReader : IVnTextReader { - /* - * To make this structure read-only we can store the - * mutable values in a private segment of the internal - * buffer. 8 bytes are reserved at the beining and an - * additional word is added for padding incase small/wild - * under/over run occurs. - */ - const int PrivateBufferOffset = 4 * sizeof(int); + private readonly static int BufferPosStructSize = Unsafe.SizeOf<BufferPosition>(); ///<inheritdoc/> public readonly Encoding Encoding { get; } @@ -58,26 +51,10 @@ namespace VNLib.Net.Http.Core ///<inheritdoc/> public readonly Stream BaseStream { get; } - - /* - * Store the window start/end in the begging of the - * data buffer. Then use a constant offset to get the - * start of the buffer - */ - private readonly int BufWindowStart - { - get => MemoryMarshal.Read<int>(Buffer.GetBinSpan()); - set => MemoryMarshal.Write(Buffer.GetBinSpan(), ref value); - } - - private readonly int BufWindowEnd - { - get => MemoryMarshal.Read<int>(Buffer.GetBinSpan()[sizeof(int)..]); - set => MemoryMarshal.Write(Buffer.GetBinSpan()[sizeof(int)..], ref value); - } + private readonly IHttpHeaderParseBuffer Buffer; - private readonly int MAxBufferSize; + private readonly uint MaxBufferSize; /// <summary> /// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream @@ -92,78 +69,177 @@ namespace VNLib.Net.Http.Core BaseStream = transport; LineTermination = lineTermination; Buffer = buffer; - MAxBufferSize = buffer.BinSize - PrivateBufferOffset; + MaxBufferSize = (uint)(buffer.BinSize - BufferPosStructSize); + + //Assign an zeroed position + BufferPosition position = default; + SetPosition(ref position); - //Initialize the buffer window - SafeZeroPrivateSegments(Buffer); + AssertZeroPosition(); + } + + [Conditional("DEBUG")] + private void AssertZeroPosition() + { + BufferPosition position = default; + GetPosition(ref position); + Debug.Assert(position.WindowStart == 0); + Debug.Assert(position.WindowEnd == 0); + } - Debug.Assert(BufWindowEnd == 0 && BufWindowStart == 0); + /// <summary> + /// Reads the current position from the buffer segment + /// </summary> + /// <param name="position">A reference to the varable to write the position to</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly void GetPosition(ref BufferPosition position) + { + //Get the beining of the segment and read the position + Span<byte> span = Buffer.GetBinSpan(); + position = MemoryMarshal.Read<BufferPosition>(span); } /// <summary> - /// Clears the initial window start/end values with the - /// extra padding + /// Updates the current position in the buffer segment /// </summary> - /// <param name="buffer">The buffer segment to initialize</param> - private static void SafeZeroPrivateSegments(IHttpHeaderParseBuffer buffer) + /// <param name="position"></param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly void SetPosition(ref BufferPosition position) { - ref byte start = ref MemoryMarshal.GetReference(buffer.GetBinSpan()); - Unsafe.InitBlock(ref start, 0, PrivateBufferOffset); + //Store the position at the beining of the segment + Span<byte> span = Buffer.GetBinSpan(); + MemoryMarshal.Write(span, ref position); } /// <summary> /// Gets the data segment of the buffer after the private segment /// </summary> /// <returns></returns> - private readonly Span<byte> GetDataSegment() => Buffer.GetBinSpan()[PrivateBufferOffset..]; + private readonly Span<byte> GetDataSegment() + { + //Get the beining of the segment + Span<byte> span = Buffer.GetBinSpan(); + //Return the segment after the private segment + return span[BufferPosStructSize..]; + } ///<inheritdoc/> - public readonly int Available => BufWindowEnd - BufWindowStart; + public readonly int Available + { + get + { + //Read position and return the window size + BufferPosition position = default; + GetPosition(ref position); + return (int)position.GetWindowSize(); + } + } ///<inheritdoc/> - public readonly Span<byte> BufferedDataWindow => GetDataSegment()[BufWindowStart..BufWindowEnd]; - + public readonly Span<byte> BufferedDataWindow + { + get + { + //Read current position and return the window + BufferPosition position = default; + GetPosition(ref position); + return GetDataSegment()[(int)position.WindowStart..(int)position.WindowEnd]; + } + } ///<inheritdoc/> - public readonly void Advance(int count) => BufWindowStart += count; + public readonly void Advance(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive"); + } + + //read the current position + BufferPosition position = default; + GetPosition(ref position); + + //Advance the window start by the count and set the position + position.AdvanceStart(count); + SetPosition(ref position); + } ///<inheritdoc/> public readonly void FillBuffer() { + //Read the current position + BufferPosition bufferPosition = default; + GetPosition(ref bufferPosition); + //Get a buffer from the end of the current window to the end of the buffer - Span<byte> bufferWindow = GetDataSegment()[BufWindowEnd..]; + Span<byte> bufferWindow = GetDataSegment()[(int)bufferPosition.WindowEnd..]; //Read from stream int read = BaseStream.Read(bufferWindow); + Debug.Assert(read > -1, "Read should never be negative"); //Update the end of the buffer window to the end of the read data - BufWindowEnd += read; + bufferPosition.AdvanceEnd(read); + SetPosition(ref bufferPosition); } ///<inheritdoc/> public readonly ERRNO CompactBufferWindow() { + //Read the current position + BufferPosition bufferPosition = default; + GetPosition(ref bufferPosition); + //No data to compact if window is not shifted away from start - if (BufWindowStart > 0) + if (bufferPosition.WindowStart > 0) { //Get span over engire buffer Span<byte> buffer = GetDataSegment(); //Get used data segment within window - Span<byte> usedData = buffer[BufWindowStart..BufWindowEnd]; + Span<byte> usedData = buffer[(int)bufferPosition.WindowStart..(int)bufferPosition.WindowEnd]; //Copy remaining to the begining of the buffer usedData.CopyTo(buffer); - - //Buffer window start is 0 - BufWindowStart = 0; - - //Buffer window end is now the remaining size - BufWindowEnd = usedData.Length; + + /* + * Now that data has been shifted, update the position to + * the new window and write the new position to the buffer + */ + bufferPosition.Set(0, usedData.Length); + SetPosition(ref bufferPosition); } //Return the number of bytes of available space from the end of the current window - return MAxBufferSize - BufWindowEnd; + return (nint)(MaxBufferSize - bufferPosition.WindowEnd); + } + + [StructLayout(LayoutKind.Sequential)] + private record struct BufferPosition + { + public uint WindowStart; + public uint WindowEnd; + + /// <summary> + /// Sets the the buffer window position + /// </summary> + /// <param name="start">Window start</param> + /// <param name="end">Window end</param> + public void Set(int start, int end) + { + //Verify that the start and end are not negative + Debug.Assert(start >= 0, "Negative internal value passed to http buffer window start"); + Debug.Assert(end >= 0, "Negative internal value passed to http buffer window end"); + + WindowStart = (uint)start; + WindowEnd = (uint)end; + } + + public readonly uint GetWindowSize() => WindowEnd - WindowStart; + + public void AdvanceEnd(int count) => WindowEnd += (uint)count; + + public void AdvanceStart(int count) => WindowStart += (uint)count; } } } diff --git a/lib/Net.Http/src/Helpers/HelperTypes.cs b/lib/Net.Http/src/Helpers/HelperTypes.cs index 7e7e068..ce13874 100644 --- a/lib/Net.Http/src/Helpers/HelperTypes.cs +++ b/lib/Net.Http/src/Helpers/HelperTypes.cs @@ -87,7 +87,11 @@ namespace VNLib.Net.Http /// <summary> /// Http UNLOCK request method /// </summary> - UNLOCK = 0x1000 + UNLOCK = 0x1000, + /// <summary> + /// Http LIST request method + /// </summary> + LIST = 0x2000 } /// <summary> diff --git a/lib/Net.Http/src/Helpers/HttpHelpers.cs b/lib/Net.Http/src/Helpers/HttpHelpers.cs index d5c471f..198396f 100644 --- a/lib/Net.Http/src/Helpers/HttpHelpers.cs +++ b/lib/Net.Http/src/Helpers/HttpHelpers.cs @@ -29,7 +29,6 @@ using System.Net.Sockets; using System.Collections.Generic; using System.Text.RegularExpressions; -using VNLib.Net.Http.Core; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; @@ -66,7 +65,7 @@ namespace VNLib.Net.Http * an HttpMethod enum value, */ - private static readonly IReadOnlyDictionary<int, HttpMethod> MethodHashLookup; + private static readonly IReadOnlyDictionary<int, HttpMethod> MethodHashLookup = HashHttpMethods(); /* * Provides a constant lookup table from an MIME http request header string to a .NET @@ -128,8 +127,8 @@ namespace VNLib.Net.Http * during request parsing) * */ - private static readonly IReadOnlyDictionary<int, HttpRequestHeader> RequestHeaderHashLookup; - + private static readonly IReadOnlyDictionary<int, HttpRequestHeader> RequestHeaderHashLookup = HashRequestHeaders(); + /* * Provides a constant lookup table for http version hashcodes to an http * version enum value @@ -143,68 +142,63 @@ namespace VNLib.Net.Http }; - //Pre-compiled strings for all status codes for http 1, 1.1, and 2 - private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_STAUTS_CODES; - private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_1_STATUS_CODES; - private static readonly IReadOnlyDictionary<HttpStatusCode, string> V2_STAUTS_CODES; + //Pre-compiled strings for all status codes for http 0.9 1, 1.1 + private static readonly IReadOnlyDictionary<HttpStatusCode, string> V0_9_STATUS_CODES = GetStatusCodes("0.9"); + private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_STAUTS_CODES = GetStatusCodes("1.0"); + private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_1_STATUS_CODES = GetStatusCodes("1.1"); + private static readonly IReadOnlyDictionary<HttpStatusCode, string> V2_STATUS_CODES = GetStatusCodes("2.0"); - static HttpHelpers() + private static IReadOnlyDictionary<HttpStatusCode, string> GetStatusCodes(string version) { + //Setup status code dict + Dictionary<HttpStatusCode, string> statusCodes = new(); + //Get all status codes + foreach (HttpStatusCode code in Enum.GetValues<HttpStatusCode>()) { - //Setup status code dict - Dictionary<HttpStatusCode, string> v1status = new(); - Dictionary<HttpStatusCode, string> v11status = new(); - Dictionary<HttpStatusCode, string> v2status = new(); - //Get all status codes - foreach (HttpStatusCode code in Enum.GetValues<HttpStatusCode>()) - { - //Use a regex to write the status code value as a string - v1status[code] = $"HTTP/1.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}"; - v11status[code] = $"HTTP/1.1 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}"; - v2status[code] = $"HTTP/2.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}"; - } - //Store as readonly - V1_STAUTS_CODES = v1status; - V1_1_STATUS_CODES = v11status; - V2_STAUTS_CODES = v2status; + //Use a regex to write the status code value as a string + statusCodes[code] = $"HTTP/{version} {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}"; } + return statusCodes; + } + + private static IReadOnlyDictionary<int, HttpMethod> HashHttpMethods() + { + /* + * Http methods are hashed at runtime using the HttpMethod enum + * values, purley for compatability and automation + */ + Dictionary<int, HttpMethod> methods = new(); + //Add all HTTP methods + foreach (HttpMethod method in Enum.GetValues<HttpMethod>()) { - /* - * Http methods are hashed at runtime using the HttpMethod enum - * values, purley for compatability and automation - */ - Dictionary<int, HttpMethod> methods = new(); - //Add all HTTP methods - foreach (HttpMethod method in Enum.GetValues<HttpMethod>()) + //Exclude the not supported method + if (method == HttpMethod.None) { - //Exclude the not supported method - if (method == HttpMethod.None) - { - continue; - } - //Store method string's hashcode for faster lookups - methods[string.GetHashCode(method.ToString(), StringComparison.OrdinalIgnoreCase)] = method; + continue; } - MethodHashLookup = methods; + //Store method string's hashcode for faster lookups + methods[string.GetHashCode(method.ToString(), StringComparison.OrdinalIgnoreCase)] = method; } - { - /* - * Pre-compute common headers - */ - Dictionary<int, HttpRequestHeader> requestHeaderHashes = new(); + return methods; + } - //Add all HTTP methods - foreach (string headerValue in RequestHeaderLookup.Keys) - { - //Compute the hashcode for the header value - int hashCode = string.GetHashCode(headerValue, StringComparison.OrdinalIgnoreCase); - //Store the http header enum value with the hash-code of the string of said header - requestHeaderHashes[hashCode] = RequestHeaderLookup[headerValue]; - } + private static IReadOnlyDictionary<int, HttpRequestHeader> HashRequestHeaders() + { + /* + * Pre-compute common headers + */ + Dictionary<int, HttpRequestHeader> requestHeaderHashes = new(); - RequestHeaderHashLookup = requestHeaderHashes; + //Add all HTTP methods + foreach (string headerValue in RequestHeaderLookup.Keys) + { + //Compute the hashcode for the header value + int hashCode = string.GetHashCode(headerValue, StringComparison.OrdinalIgnoreCase); + //Store the http header enum value with the hash-code of the string of said header + requestHeaderHashes[hashCode] = RequestHeaderLookup[headerValue]; } - } + return requestHeaderHashes; + } /// <summary> @@ -358,8 +352,10 @@ namespace VNLib.Net.Http { return version switch { + HttpVersion.Http09 => V0_9_STATUS_CODES[code], HttpVersion.Http1 => V1_STAUTS_CODES[code], - HttpVersion.Http2 => V2_STAUTS_CODES[code], + HttpVersion.Http2 => V2_STATUS_CODES[code], + //Default to HTTP/1.1 _ => V1_1_STATUS_CODES[code], }; } |