diff --git a/src/Entity/HttpHeaderCollection.cs b/src/Entity/HttpHeaderCollection.cs
new file mode 100644
index 0000000..9005ebf
--- /dev/null
+++ b/src/Entity/HttpHeaderCollection.cs
@@ -0,0 +1,478 @@
+// The Sisk Framework source code
+// Copyright (c) 2023 PROJECT PRINCIPIUM
+//
+// The code below is licensed under the MIT license as
+// of the date of its publication, available at
+//
+// File name: HttpHeaderCollection.cs
+// Repository: https://github.com/sisk-http/core
+
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Sisk.Core.Http;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Header = Sisk.Core.Internal.HttpKnownHeaderNames;
+
+namespace Sisk.Core.Entity;
+
+///
+/// Represents an collection of HTTP headers with their name and values.
+///
+///
+/// public class HttpHeaderCollection : NameValueCollection
+///
+///
+/// Class
+///
+public class HttpHeaderCollection : NameValueCollection
+{
+ ///
+ /// Create an new instance of the class.
+ ///
+ public HttpHeaderCollection()
+ {
+ }
+
+ ///
+ /// Creates an new instance of the with the specified
+ /// headers.
+ ///
+ /// The header collection.
+ public HttpHeaderCollection(NameValueCollection headers) : base(headers)
+ {
+ }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept header.
+ ///
+ ///
+ /// public string? Accept { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Accept { get => base[Header.Accept]; set => base[Header.Accept] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept-Charset header.
+ ///
+ ///
+ /// public string? AcceptCharset { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AcceptCharset { get => base[Header.AcceptCharset]; set => base[Header.AcceptCharset] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept-Encoding header.
+ ///
+ ///
+ /// public string? AcceptEncoding { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AcceptEncoding { get => base[Header.AcceptEncoding]; set => base[Header.AcceptEncoding] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept-Language header.
+ ///
+ ///
+ /// public string? AcceptLanguage { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AcceptLanguage { get => base[Header.AcceptLanguage]; set => base[Header.AcceptLanguage] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept-Patch header.
+ ///
+ ///
+ /// public string? AcceptPatch { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AcceptPatch { get => base[Header.AcceptPatch]; set => base[Header.AcceptPatch] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Accept-Ranges header.
+ ///
+ ///
+ /// public string? AcceptRanges { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AcceptRanges { get => base[Header.AcceptRanges]; set => base[Header.AcceptRanges] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Allow-Credentials header.
+ ///
+ ///
+ /// Note: this header can be overwritten by the current configuration.
+ ///
+ ///
+ /// public string? AccessControlAllowCredentials { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlAllowCredentials { get => base[Header.AccessControlAllowCredentials]; set => base[Header.AccessControlAllowCredentials] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Allow-Headers header.
+ ///
+ ///
+ /// Note: this header can be overwritten by the current configuration.
+ ///
+ ///
+ /// public string? AccessControlAllowHeaders { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlAllowHeaders { get => base[Header.AccessControlAllowHeaders]; set => base[Header.AccessControlAllowHeaders] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Allow-Methods header.
+ ///
+ ///
+ /// public string? AccessControlAllowMethods { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlAllowMethods { get => base[Header.AccessControlAllowMethods]; set => base[Header.AccessControlAllowMethods] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Allow-Origin header.
+ ///
+ ///
+ /// Note: this header can be overwritten by the current configuration.
+ ///
+ ///
+ /// public string? AccessControlAllowOrigin { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlAllowOrigin { get => base[Header.AccessControlAllowOrigin]; set => base[Header.AccessControlAllowOrigin] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Expose-Headers header.
+ ///
+ ///
+ /// Note: this header can be overwritten by the current configuration.
+ ///
+ ///
+ /// public string? AccessControlExposeHeaders { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlExposeHeaders { get => base[Header.AccessControlExposeHeaders]; set => base[Header.AccessControlExposeHeaders] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Access-Control-Max-Age header.
+ ///
+ ///
+ /// Note: this header can be overwritten by the current configuration.
+ ///
+ ///
+ /// public string? AccessControlMaxAge { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AccessControlMaxAge { get => base[Header.AccessControlMaxAge]; set => base[Header.AccessControlMaxAge] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Age header.
+ ///
+ ///
+ /// public string? Age { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Age { get => base[Header.Age]; set => base[Header.Age] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Allow header.
+ ///
+ ///
+ /// public string? Allow { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Allow { get => base[Header.Allow]; set => base[Header.Allow] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Alt-Svc header.
+ ///
+ ///
+ /// public string? AltSvc { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? AltSvc { get => base[Header.AltSvc]; set => base[Header.AltSvc] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Authorization header.
+ ///
+ ///
+ /// public string? Authorization { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Authorization { get => base[Header.Authorization]; set => base[Header.Authorization] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Cache-Control header.
+ ///
+ ///
+ /// public string? CacheControl { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? CacheControl { get => base[Header.CacheControl]; set => base[Header.CacheControl] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Content-Disposition header.
+ ///
+ ///
+ /// public string? ContentDisposition { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? ContentDisposition { get => base[Header.ContentDisposition]; set => base[Header.ContentDisposition] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Content-Encoding header.
+ ///
+ ///
+ /// public string? ContentEncoding { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? ContentEncoding { get => base[Header.ContentEncoding]; set => base[Header.ContentEncoding] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Content-Language header.
+ ///
+ ///
+ /// public string? ContentLanguage { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? ContentLanguage { get => base[Header.ContentLanguage]; set => base[Header.ContentLanguage] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Content-Range header.
+ ///
+ ///
+ /// public string? ContentRange { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? ContentRange { get => base[Header.ContentRange]; set => base[Header.ContentRange] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Content-Type header.
+ ///
+ ///
+ /// Note: setting the value of this header, the value present in the response's will be overwritten.
+ ///
+ ///
+ /// public string? ContentType { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? ContentType { get => base[Header.ContentType]; set => base[Header.ContentType] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Cookie header.
+ ///
+ ///
+ /// Tip: use property to getting cookies values from requests and
+ /// on to set cookies.
+ ///
+ ///
+ /// public string? Cookie { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Cookie { get => base[Header.Cookie]; set => base[Header.Cookie] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Expect header.
+ ///
+ ///
+ /// public string? Expect { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Expect { get => base[Header.Expect]; set => base[Header.Expect] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Expires header.
+ ///
+ ///
+ /// public string? Expires { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Expires { get => base[Header.Expires]; set => base[Header.Expires] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Host header.
+ ///
+ ///
+ /// public string? Host { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Host { get => base[Header.Host]; set => base[Header.Host] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Origin header.
+ ///
+ ///
+ /// public string? Origin { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Origin { get => base[Header.Origin]; set => base[Header.Origin] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Range header.
+ ///
+ ///
+ /// public string? Range { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Range { get => base[Header.Range]; set => base[Header.Range] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Referer header.
+ ///
+ ///
+ /// public string? Referer { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Referer { get => base[Header.Referer]; set => base[Header.Referer] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Retry-After header.
+ ///
+ ///
+ /// public string? RetryAfter { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? RetryAfter { get => base[Header.RetryAfter]; set => base[Header.RetryAfter] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Set-Cookie header.
+ ///
+ ///
+ /// Tip: use property to getting cookies values from requests and
+ /// on to set cookies.
+ ///
+ ///
+ /// public string? SetCookie { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? SetCookie { get => base[Header.SetCookie]; set => base[Header.SetCookie] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP User-Agent header.
+ ///
+ ///
+ /// public string? UserAgent { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? UserAgent { get => base[Header.UserAgent]; set => base[Header.UserAgent] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP Vary header.
+ ///
+ ///
+ /// public string? Vary { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? Vary { get => base[Header.Vary]; set => base[Header.Vary] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP WWW-Authenticate header.
+ ///
+ ///
+ /// public string? WWWAuthenticate { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? WWWAuthenticate { get => base[Header.WWWAuthenticate]; set => base[Header.WWWAuthenticate] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP X-Forwarded-For header.
+ ///
+ ///
+ /// Tip: enable the property to obtain the user client proxied IP throught .
+ ///
+ ///
+ /// public string? XForwardedFor { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? XForwardedFor { get => base[Header.XForwardedFor]; set => base[Header.XForwardedFor] = value; }
+
+ ///
+ /// Gets or sets the value of the HTTP X-Forwarded-Host header.
+ ///
+ ///
+ /// Tip: enable the property to obtain the client requested host throught .
+ ///
+ ///
+ /// public string? XForwardedHost { get; set; }
+ ///
+ ///
+ /// Property
+ ///
+ public string? XForwardedHost { get => base[Header.XForwardedHost]; set => base[Header.XForwardedHost] = value; }
+
+}
diff --git a/src/Ext/CircularBuffer.cs b/src/Ext/CircularBuffer.cs
index 126ef38..926c0bb 100644
--- a/src/Ext/CircularBuffer.cs
+++ b/src/Ext/CircularBuffer.cs
@@ -9,403 +9,70 @@
using System.Collections;
-
-///
-internal class CircularBuffer : IEnumerable
+namespace System;
+
+///
+/// Represents an thread-safe, fixed-capacity circular buffer that stores elements of .
+///
+/// The type of elements stored in the buffer.
+///
+public class CircularBuffer : IEnumerable
{
- private readonly T[] _buffer;
-
- ///
- /// The _start. Index of the first element in buffer.
- ///
- private int _start;
-
- ///
- /// The _end. Index after the last element in the buffer.
- ///
- private int _end;
+ readonly T[] items;
- ///
- /// The _size. Buffer size.
- ///
- private int _size;
+ int capacity = 0;
///
- /// Initializes a new instance of the class.
- ///
+ /// Creates an new instance of the with the specified
+ /// capacity.
///
- ///
- /// Buffer capacity. Must be positive.
- ///
+ /// The circular buffer capacity.
public CircularBuffer(int capacity)
- : this(capacity, new T[] { })
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- ///
- /// Buffer capacity. Must be positive.
- ///
- ///
- /// Items to fill buffer with. Items length must be less than capacity.
- /// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from
- /// any enumerable.
- ///
- public CircularBuffer(int capacity, T[] items)
- {
- if (capacity < 1)
- {
- throw new ArgumentException(
- "Circular buffer cannot have negative or zero capacity.", nameof(capacity));
- }
- if (items == null)
- {
- throw new ArgumentNullException(nameof(items));
- }
- if (items.Length > capacity)
- {
- throw new ArgumentException(
- "Too many items to fit circular buffer", nameof(items));
- }
-
- _buffer = new T[capacity];
-
- Array.Copy(items, _buffer, items.Length);
- _size = items.Length;
-
- _start = 0;
- _end = _size == capacity ? 0 : _size;
- }
-
- ///
- /// Maximum capacity of the buffer. Elements pushed into the buffer after
- /// maximum capacity is reached (IsFull = true), will remove an element.
- ///
- public int Capacity { get { return _buffer.Length; } }
-
- ///
- /// Boolean indicating if Circular is at full capacity.
- /// Adding more elements when the buffer is full will
- /// cause elements to be removed from the other end
- /// of the buffer.
- ///
- public bool IsFull
{
- get
- {
- return Size == Capacity;
- }
+ this.capacity = capacity;
+ items = new T[capacity];
}
///
- /// True if has no elements.
+ /// Adds an object of into the beginning of the circular buffer and pushes
+ /// old items to end.
///
- public bool IsEmpty
+ /// The item to add.
+ public void Add(T item)
{
- get
+ lock (items)
{
- return Size == 0;
- }
- }
-
- ///
- /// Current buffer size (the number of elements that the buffer has).
- ///
- public int Size { get { return _size; } }
-
- ///
- /// Element at the front of the buffer - this[0].
- ///
- /// The value of the element of type T at the front of the buffer.
- public T Front()
- {
- ThrowIfEmpty();
- return _buffer[_start];
- }
-
- ///
- /// Element at the back of the buffer - this[Size - 1].
- ///
- /// The value of the element of type T at the back of the buffer.
- public T Back()
- {
- ThrowIfEmpty();
- return _buffer[(_end != 0 ? _end : Capacity) - 1];
- }
-
- ///
- /// Index access to elements in buffer.
- /// Index does not loop around like when adding elements,
- /// valid interval is [0;Size[
- ///
- /// Index of element to access.
- /// Thrown when index is outside of [; Size[ interval.
- public T this[int index]
- {
- get
- {
- if (IsEmpty)
+ for (int i = capacity - 1; i > 0; i--)
{
- throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
+ items[i] = items[i - 1];
}
- if (index >= _size)
- {
- throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
- }
- int actualIndex = InternalIndex(index);
- return _buffer[actualIndex];
- }
- set
- {
- if (IsEmpty)
- {
- throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
- }
- if (index >= _size)
- {
- throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
- }
- int actualIndex = InternalIndex(index);
- _buffer[actualIndex] = value;
- }
- }
-
- ///
- /// Pushes a new element to the back of the buffer. Back()/this[Size-1]
- /// will now return this element.
- ///
- /// When the buffer is full, the element at Front()/this[0] will be
- /// popped to allow for this new element to fit.
- ///
- /// Item to push to the back of the buffer
- public void PushBack(T item)
- {
- if (IsFull)
- {
- _buffer[_end] = item;
- Increment(ref _end);
- _start = _end;
- }
- else
- {
- _buffer[_end] = item;
- Increment(ref _end);
- ++_size;
- }
- }
- ///
- /// Pushes a new element to the front of the buffer. Front()/this[0]
- /// will now return this element.
- ///
- /// When the buffer is full, the element at Back()/this[Size-1] will be
- /// popped to allow for this new element to fit.
- ///
- /// Item to push to the front of the buffer
- public void PushFront(T item)
- {
- if (IsFull)
- {
- Decrement(ref _start);
- _end = _start;
- _buffer[_start] = item;
- }
- else
- {
- Decrement(ref _start);
- _buffer[_start] = item;
- ++_size;
+ items[0] = item;
}
}
///
- /// Removes the element at the back of the buffer. Decreasing the
- /// Buffer size by 1.
- ///
- public void PopBack()
- {
- ThrowIfEmpty("Cannot take elements from an empty buffer.");
- Decrement(ref _end);
- _buffer[_end] = default(T)!;
- --_size;
- }
-
- ///
- /// Removes the element at the front of the buffer. Decreasing the
- /// Buffer size by 1.
+ /// Returns an array representation of this items in their defined
+ /// positions within the capacity.
///
- public void PopFront()
- {
- ThrowIfEmpty("Cannot take elements from an empty buffer.");
- _buffer[_start] = default(T)!;
- Increment(ref _start);
- --_size;
- }
-
- ///
- /// Clears the contents of the array. Size = 0, Capacity is unchanged.
- ///
- ///
- public void Clear()
- {
- // to clear we just reset everything.
- _start = 0;
- _end = 0;
- _size = 0;
- Array.Clear(_buffer, 0, _buffer.Length);
- }
-
- ///
- /// Copies the buffer contents to an array, according to the logical
- /// contents of the buffer (i.e. independent of the internal
- /// order/contents)
- ///
- /// A new array with a copy of the buffer contents.
- public T[] ToArray()
- {
- T[] newArray = new T[Size];
- int newArrayOffset = 0;
- var segments = ToArraySegments();
- foreach (ArraySegment segment in segments)
- {
- Array.Copy(segment.Array!, segment.Offset, newArray, newArrayOffset, segment.Count);
- newArrayOffset += segment.Count;
- }
- return newArray;
- }
+ public T[] ToArray() => items;
///
- /// Get the contents of the buffer as 2 ArraySegments.
- /// Respects the logical contents of the buffer, where
- /// each segment and items in each segment are ordered
- /// according to insertion.
- ///
- /// Fast: does not copy the array elements.
- /// Useful for methods like Send(IList<ArraySegment<Byte>>).
- ///
- /// Segments may be empty.
+ /// Gets the current capacity of this .
///
- /// An IList with 2 segments corresponding to the buffer content.
- public IList> ToArraySegments()
- {
- return new[] { ArrayOne(), ArrayTwo() };
- }
+ public int Capacity { get => capacity; }
- #region IEnumerable implementation
- ///
- /// Returns an enumerator that iterates through this buffer.
- ///
- /// An enumerator that can be used to iterate this collection.
+ ///
+ ///
public IEnumerator GetEnumerator()
{
- var segments = ToArraySegments();
- foreach (ArraySegment segment in segments)
- {
- for (int i = 0; i < segment.Count; i++)
- {
- yield return segment.Array![segment.Offset + i];
- }
- }
- }
- #endregion
- #region IEnumerable implementation
- IEnumerator IEnumerable.GetEnumerator()
- {
- return (IEnumerator)GetEnumerator();
- }
- #endregion
-
- private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
- {
- if (IsEmpty)
- {
- throw new InvalidOperationException(message);
- }
+ return ((IEnumerable)items).GetEnumerator();
}
- ///
- /// Increments the provided index variable by one, wrapping
- /// around if necessary.
- ///
- ///
- private void Increment(ref int index)
- {
- if (++index == Capacity)
- {
- index = 0;
- }
- }
-
- ///
- /// Decrements the provided index variable by one, wrapping
- /// around if necessary.
- ///
- ///
- private void Decrement(ref int index)
- {
- if (index == 0)
- {
- index = Capacity;
- }
- index--;
- }
-
- ///
- /// Converts the index in the argument to an index in _buffer
- ///
- ///
- /// The transformed index.
- ///
- ///
- /// External index.
- ///
- private int InternalIndex(int index)
- {
- return _start + (index < (Capacity - _start) ? index : index - Capacity);
- }
-
- // doing ArrayOne and ArrayTwo methods returning ArraySegment as seen here:
- // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d
- // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b
- // should help a lot with the code.
-
- #region Array items easy access.
- // The array is composed by at most two non-contiguous segments,
- // the next two methods allow easy access to those.
-
- private ArraySegment ArrayOne()
- {
- if (IsEmpty)
- {
- return new ArraySegment(new T[0]);
- }
- else if (_start < _end)
- {
- return new ArraySegment(_buffer, _start, _end - _start);
- }
- else
- {
- return new ArraySegment(_buffer, _start, _buffer.Length - _start);
- }
- }
-
- private ArraySegment ArrayTwo()
+ ///
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
{
- if (IsEmpty)
- {
- return new ArraySegment(new T[0]);
- }
- else if (_start < _end)
- {
- return new ArraySegment(_buffer, _end, 0);
- }
- else
- {
- return new ArraySegment(_buffer, 0, _end);
- }
+ return items.GetEnumerator();
}
- #endregion
}
\ No newline at end of file
diff --git a/src/Http/Handlers/HttpServerHandlerRepository.cs b/src/Http/Handlers/HttpServerHandlerRepository.cs
index 1ed7d73..4c3f596 100644
--- a/src/Http/Handlers/HttpServerHandlerRepository.cs
+++ b/src/Http/Handlers/HttpServerHandlerRepository.cs
@@ -13,8 +13,14 @@ namespace Sisk.Core.Http.Handlers;
internal class HttpServerHandlerRepository
{
+ private readonly HttpServer parent;
private readonly List handlers = new List();
+ public HttpServerHandlerRepository(HttpServer parent)
+ {
+ this.parent = parent;
+ }
+
public void RegisterHandler(HttpServerHandler handler)
{
handlers.Add(handler);
@@ -23,7 +29,18 @@ public void RegisterHandler(HttpServerHandler handler)
private void CallEvery(Action action)
{
foreach (HttpServerHandler handler in handlers)
- action(handler);
+ try
+ {
+ action(handler);
+ }
+ catch (Exception ex)
+ {
+ if (parent.ServerConfiguration.ThrowExceptions)
+ {
+ parent.ServerConfiguration.ErrorsLogsStream?.WriteException(ex);
+ }
+ else throw;
+ }
}
internal void ServerStarting(HttpServer val) => CallEvery(handler => handler.InvokeOnServerStarting(val));
diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs
index 7a58abb..caf0c39 100644
--- a/src/Http/HttpRequest.cs
+++ b/src/Http/HttpRequest.cs
@@ -53,7 +53,7 @@ public sealed class HttpRequest
private byte[]? contentBytes;
internal bool isStreaming;
private HttpRequestEventSource? activeEventSource;
- private NameValueCollection? headers = null;
+ private HttpHeaderCollection? headers = null;
private NameValueCollection? cookies = null;
private StringValueCollection? query = null;
private StringValueCollection? form = null;
@@ -84,7 +84,7 @@ void ReadRequestStreamContents()
{
if (contentBytes == null)
{
- using (var memoryStream = new MemoryStream())
+ using (var memoryStream = new MemoryStream((int)ContentLength))
{
listenerRequest.InputStream.CopyTo(memoryStream);
contentBytes = memoryStream.ToArray();
@@ -140,12 +140,12 @@ void ReadRequestStreamContents()
/// Gets the HTTP request headers.
///
///
- /// public NameValueCollection Headers { get; }
+ /// public HttpHeaderCollection Headers { get; }
///
///
/// Property
///
- public NameValueCollection Headers
+ public HttpHeaderCollection Headers
{
get
{
@@ -153,7 +153,7 @@ public NameValueCollection Headers
{
if (contextServerConfiguration.Flags.NormalizeHeadersEncodings)
{
- headers = new NameValueCollection();
+ headers = new HttpHeaderCollection();
Encoding entryCodepage = Encoding.GetEncoding("ISO-8859-1");
foreach (string headerName in listenerRequest.Headers)
{
@@ -166,7 +166,7 @@ public NameValueCollection Headers
}
else
{
- headers = listenerRequest.Headers;
+ headers = new HttpHeaderCollection(listenerRequest.Headers);
}
}
@@ -708,7 +708,7 @@ public HttpResponse Close()
/// cached in this object.
///
///
- /// public Stream GetInputStream()
+ /// public Stream GetRequestStream()
///
///
/// Method
diff --git a/src/Http/HttpResponse.cs b/src/Http/HttpResponse.cs
index 3546cf6..7532289 100644
--- a/src/Http/HttpResponse.cs
+++ b/src/Http/HttpResponse.cs
@@ -19,7 +19,7 @@ namespace Sisk.Core.Http
///
///
/// public class HttpResponse : CookieHelper
- ///
+ ///
///
/// Class
///
@@ -84,16 +84,15 @@ public static HttpResponse CreateRedirectResponse(RouteAction action)
}
///
- /// Gets or sets an custom HTTP status code and description for this HTTP response. If this property ins't null, it will overwrite
- /// the property in this class.
+ /// Gets or sets the HTTP status code and description for this HTTP response.
///
///
- /// public HttpStatusInformation? CustomStatus { get; set; }
+ /// public HttpStatusInformation StatusInformation { get; set; }
///
///
/// Property
///
- public HttpStatusInformation? CustomStatus { get; set; } = null;
+ public HttpStatusInformation StatusInformation { get; set; } = new HttpStatusInformation();
///
/// Gets or sets the HTTP response status code.
@@ -104,7 +103,7 @@ public static HttpResponse CreateRedirectResponse(RouteAction action)
///
/// Property
///
- public HttpStatusCode Status { get; set; } = HttpStatusCode.OK;
+ public HttpStatusCode Status { get => (HttpStatusCode)StatusInformation.StatusCode; set => StatusInformation = new HttpStatusInformation(value); }
///
/// Gets a instance of the HTTP response headers.
@@ -146,12 +145,6 @@ internal HttpResponse(byte internalStatus)
this.internalStatus = internalStatus;
}
- internal HttpResponse(HttpListenerResponse res)
- {
- Status = (HttpStatusCode)res.StatusCode;
- Headers.Add(res.Headers);
- }
-
///
/// Gets the raw HTTP response message.
///
@@ -166,16 +159,23 @@ internal HttpResponse(HttpListenerResponse res)
public string GetRawHttpResponse(bool includeBody = true)
{
StringBuilder sb = new StringBuilder();
- sb.AppendLine($"HTTP/1.1 {(int)Status}");
+ sb.AppendLine($"HTTP/1.1 {StatusInformation}");
foreach (string header in Headers)
{
sb.Append(header + ": ");
sb.Append(Headers[header]);
sb.Append('\n');
}
+ if (Content?.Headers is not null)
+ foreach (var header in Content.Headers)
+ {
+ sb.Append(header.Key + ": ");
+ sb.Append(string.Join(", ", header.Value));
+ sb.Append('\n');
+ }
sb.Append('\n');
- if (includeBody)
+ if (includeBody && Content is not StreamContent)
{
sb.Append(Content?.ReadAsStringAsync().Result);
}
@@ -283,8 +283,7 @@ public HttpResponse WithStatus(int status)
///
public HttpResponse WithStatus(HttpStatusInformation statusInformation)
{
- Status = default;
- CustomStatus = statusInformation;
+ StatusInformation = statusInformation;
return this;
}
@@ -340,8 +339,6 @@ public HttpResponse WithCookie(string name, string value, DateTime? expires = nu
///
public HttpResponse()
{
- Status = HttpStatusCode.OK;
- Content = null;
}
///
@@ -388,6 +385,18 @@ public HttpResponse(int status, HttpContent? content) : this((HttpStatusCode)sta
///
public HttpResponse(HttpContent? content) : this(HttpStatusCode.OK, content) { }
+ ///
+ /// Creates an new instanec with given string content and status code as 200 OK.
+ ///
+ /// The UTF-8 string content.
+ ///
+ /// public HttpResponse(string stringContent)
+ ///
+ ///
+ /// Constructor
+ ///
+ public HttpResponse(string stringContent) : this(HttpStatusCode.OK, new StringContent(stringContent)) { }
+
///
/// Creates an new instance with given status code and HTTP contents.
///
@@ -403,18 +412,6 @@ public HttpResponse(HttpStatusCode status, HttpContent? content)
Content = content;
}
- internal string? GetHeader(string headerName)
- {
- foreach (string header in Headers.Keys)
- {
- if (string.Compare(header, headerName, StringComparison.OrdinalIgnoreCase) == 0)
- {
- return Headers[header];
- }
- }
- return null;
- }
-
///
protected override void SetCookieHeader(String name, String value)
{
diff --git a/src/Http/HttpServer.cs b/src/Http/HttpServer.cs
index 1bcdc16..e06a473 100644
--- a/src/Http/HttpServer.cs
+++ b/src/Http/HttpServer.cs
@@ -57,7 +57,7 @@ public partial class HttpServer : IDisposable
internal HttpEventSourceCollection _eventCollection = new HttpEventSourceCollection();
internal HttpWebSocketConnectionCollection _wsCollection = new HttpWebSocketConnectionCollection();
internal List? listeningPrefixes;
- internal HttpServerHandlerRepository handler = new HttpServerHandlerRepository();
+ internal HttpServerHandlerRepository handler;
static HttpServer()
{
@@ -257,6 +257,7 @@ public HttpServer(HttpServerConfiguration configuration)
{
_listenerCallback = new AsyncCallback(ListenerCallback);
ServerConfiguration = configuration;
+ handler = new HttpServerHandlerRepository(this);
handler.RegisterHandler(new DefaultHttpServerHandler());
}
diff --git a/src/Http/HttpServerConfiguration.cs b/src/Http/HttpServerConfiguration.cs
index 3c93b63..ef5ed3c 100644
--- a/src/Http/HttpServerConfiguration.cs
+++ b/src/Http/HttpServerConfiguration.cs
@@ -57,11 +57,8 @@ public class HttpServerConfiguration : IDisposable
public CultureInfo? DefaultCultureInfo { get; set; }
///
- /// Gets or sets the object which the HTTP server will write HTTP server access messages to.
+ /// Gets or sets the object which the HTTP server will write HTTP server access messages to.
///
- ///
- /// This property defaults to Console.Out.
- ///
///
/// public TextWriter? AccessLogsStream { get; set; }
///
@@ -71,11 +68,8 @@ public class HttpServerConfiguration : IDisposable
public LogStream? AccessLogsStream { get; set; } = null;
///
- /// Gets or sets the object which the HTTP server will write HTTP server error transcriptions to.
+ /// Gets or sets the object which the HTTP server will write HTTP server error transcriptions to.
///
- ///
- /// This stream can be empty if ThrowExceptions is true.
- ///
///
/// public TextWriter? ErrorsLogsStream { get; set; }
///
diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs
index 1b96fe5..fd8fa1b 100644
--- a/src/Http/HttpServer__Core.cs
+++ b/src/Http/HttpServer__Core.cs
@@ -280,16 +280,8 @@ private async Task ProcessRequest(HttpListenerContext context)
goto finishSending;
}
- if (response.CustomStatus != null)
- {
- baseResponse.StatusCode = response.CustomStatus.Value.StatusCode;
- baseResponse.StatusDescription = response.CustomStatus.Value.Description;
- }
- else
- {
- baseResponse.StatusCode = (int)response.Status;
- }
-
+ baseResponse.StatusCode = response.StatusInformation.StatusCode;
+ baseResponse.StatusDescription = response.StatusInformation.Description;
baseResponse.KeepAlive = ServerConfiguration.KeepAlive;
#endregion
@@ -310,19 +302,39 @@ private async Task ProcessRequest(HttpListenerContext context)
baseResponse.Headers[incameHeader] = value;
}
- if (response.Content != null)
+ if (response.Content is not null)
{
+ Stream? contentStream = null;
// determines the content type
baseResponse.ContentType = resHeaders[HttpKnownHeaderNames.ContentType] ?? response.Content.Headers.ContentType?.ToString();
- // determines if the response should be sent as chunked or normal
- if (response.SendChunked || responseContentLength == null)
+ // determines if the response should be sent as chunked or normal
+ if (response.SendChunked)
{
baseResponse.SendChunked = true;
}
+ else if (responseContentLength is long contentLength)
+ {
+ baseResponse.ContentLength64 = contentLength;
+ }
+ else if (response.Content is StreamContent stmContent)
+ {
+ contentStream = await stmContent.ReadAsStreamAsync();
+ if (!contentStream.CanSeek)
+ {
+ throw new InvalidOperationException(SR.HttpResponse_Stream_ContentLenghtNotSet);
+ }
+ else
+ {
+ contentStream.Position = 0;
+ baseResponse.ContentLength64 = contentStream.Length;
+ }
+ }
else
{
- baseResponse.ContentLength64 = responseContentLength.Value;
+ // the content-length wasn't informed and the user didn't set the request to
+ // send as chunked. so it will throw an exception.
+ throw new InvalidOperationException(SR.HttpResponse_Stream_ContentLenghtNotSet);
}
// write the output buffer
@@ -336,23 +348,8 @@ response.Content is StreamContent ||
if (isPayloadStreamable)
{
- Stream contentStream = await response.Content.ReadAsStreamAsync();
-
- if (contentStream.CanSeek)
- {
- contentStream.Position = 0;
- if (!baseResponse.SendChunked)
- baseResponse.ContentLength64 = contentStream.Length;
- }
- else
- {
- if (baseResponse.ContentLength64 == 0)
- {
- // the content-length wasn't informed and the user didn't set the request to
- // send as chunked. so it will throw an exception.
- throw new InvalidOperationException(SR.HttpResponse_Stream_ContentLenghtNotSet);
- }
- }
+ if (contentStream is null)
+ contentStream = await response.Content.ReadAsStreamAsync();
await contentStream.CopyToAsync(baseResponse.OutputStream, flag.RequestStreamCopyBufferSize);
}
@@ -420,6 +417,7 @@ response.Content is StreamContent ||
{
if (!ServerConfiguration.ThrowExceptions)
{
+ baseResponse.StatusCode = 500;
executionResult.ServerException = ex;
executionResult.Status = HttpServerExecutionStatus.ExceptionThrown;
}
@@ -443,7 +441,7 @@ response.Content is StreamContent ||
catch (Exception)
{
baseResponse.Abort();
- }
+ }
}
if (OnConnectionClose != null)
diff --git a/src/Http/HttpStatusInformation.cs b/src/Http/HttpStatusInformation.cs
index c3c4521..2c1db21 100644
--- a/src/Http/HttpStatusInformation.cs
+++ b/src/Http/HttpStatusInformation.cs
@@ -8,6 +8,7 @@
// Repository: https://github.com/sisk-http/core
using Sisk.Core.Internal;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Runtime.CompilerServices;
@@ -17,15 +18,15 @@ namespace Sisk.Core.Http
/// Represents a structure that holds an HTTP response status information, with its code and description.
///
///
- /// public struct HttpStatusInformation
+ /// public struct HttpStatusInformation : IComparable, IEquatable{{HttpStatusInformation}}
///
///
/// Struct
///
- public struct HttpStatusInformation
+ public struct HttpStatusInformation : IComparable, IEquatable
{
- private int __statusCode = 100;
- private string __description = "Continue";
+ private int __statusCode;
+ private string __description;
///
/// Gets or sets the short description of the HTTP message.
@@ -53,6 +54,21 @@ public int StatusCode
}
}
+ ///
+ /// Creates an new with default parameters (200 OK) status.
+ ///
+ ///
+ /// public HttpStatusInformation()
+ ///
+ ///
+ /// Constructor
+ ///
+ public HttpStatusInformation()
+ {
+ __statusCode = 200;
+ __description = "OK";
+ }
+
///
/// Creates an new instance with given parameters.
///
@@ -66,8 +82,9 @@ public int StatusCode
///
public HttpStatusInformation(int statusCode)
{
- StatusCode = statusCode;
- Description = GetStatusCodeDescription(statusCode);
+ ValidateStatusCode(statusCode);
+ __statusCode = statusCode;
+ __description = GetStatusCodeDescription(statusCode);
}
///
@@ -82,8 +99,10 @@ public HttpStatusInformation(int statusCode)
///
public HttpStatusInformation(HttpStatusCode statusCode)
{
- StatusCode = (int)statusCode;
- Description = GetStatusCodeDescription(StatusCode);
+ int s = (int)statusCode;
+ ValidateStatusCode(s);
+ __statusCode = s;
+ __description = GetStatusCodeDescription(statusCode);
}
///
@@ -100,16 +119,17 @@ public HttpStatusInformation(HttpStatusCode statusCode)
///
public HttpStatusInformation(int statusCode, string description)
{
- Description = description ?? throw new ArgumentNullException(nameof(description));
- StatusCode = statusCode;
ValidateStatusCode(statusCode);
ValidateDescription(description);
+ __statusCode = statusCode;
+ __description = description;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateStatusCode(int st)
{
- if (st < 100 || st > 999) throw new ProtocolViolationException(SR.HttpStatusCode_IllegalStatusCode);
+ if (st < 100 || st > 999)
+ throw new ProtocolViolationException(SR.HttpStatusCode_IllegalStatusCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -133,5 +153,94 @@ public static string GetStatusCodeDescription(int statusCode)
ValidateStatusCode(statusCode);
return HttpStatusDescription.Get(statusCode);
}
+
+ ///
+ /// Gets the description of the HTTP status based on its description.
+ ///
+ /// The HTTP status code.
+ ///
+ /// public static string GetStatusCodeDescription(HttpStatusCode statusCode)
+ ///
+ ///
+ /// Static method
+ ///
+ public static string GetStatusCodeDescription(HttpStatusCode statusCode)
+ {
+ return GetStatusCodeDescription((int)statusCode);
+ }
+
+ ///
+ /// Gets an corresponding to this instance, or null if the HTTP status does not match any value.
+ ///
+ ///
+ /// An or null if the HTTP status matches no entry on it.
+ ///
+ ///
+ /// public HttpStatusCode? GetHttpStatusCode()
+ ///
+ ///
+ /// Static method
+ ///
+ public HttpStatusCode? GetHttpStatusCode()
+ {
+ HttpStatusCode s = (HttpStatusCode)__statusCode;
+ if (Enum.IsDefined(s))
+ {
+ return s;
+ }
+ return null;
+ }
+
+ ///
+ ///
+ public readonly bool Equals(HttpStatusInformation other)
+ {
+ return other.__statusCode.Equals(this.__statusCode) && other.__description.Equals(this.__description);
+ }
+
+ ///
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (obj is HttpStatusInformation other)
+ {
+ if (other.__statusCode == this.__statusCode) return 0;
+ if (other.__statusCode > this.__statusCode) return 1;
+ if (other.__statusCode < this.__statusCode) return -1;
+ }
+ return -1;
+ }
+
+ ///
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is HttpStatusInformation other)
+ {
+ return Equals(other);
+ }
+ return false;
+ }
+
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return this.__statusCode.GetHashCode() ^ this.__statusCode.GetHashCode();
+ }
+
+ ///
+ /// Gets an string representation of this HTTP Status Code.
+ ///
+ ///
+ /// public string ToString()
+ ///
+ ///
+ /// Method
+ ///
+ public override string ToString()
+ {
+ return $"{__statusCode} {__description}";
+ }
}
}
diff --git a/src/Http/LogStream.cs b/src/Http/LogStream.cs
index c2300a1..3353fc4 100644
--- a/src/Http/LogStream.cs
+++ b/src/Http/LogStream.cs
@@ -40,7 +40,7 @@ public class LogStream : IDisposable
///
/// Field
///
- public static LogStream ConsoleOutput = new LogStream(Console.Out);
+ public static readonly LogStream ConsoleOutput = new LogStream(Console.Out);
///
/// Gets the defined for this .
@@ -325,7 +325,7 @@ private void ProcessQueue()
if (_bufferingContent is not null)
{
- _bufferingContent.PushBack(exitBuffer.ToString());
+ _bufferingContent.Add(exitBuffer.ToString());
}
// writes log to outputs
@@ -366,7 +366,7 @@ public virtual void WriteException(Exception exp)
{
StringBuilder excpStr = new StringBuilder();
WriteExceptionInternal(excpStr, exp, 0);
- WriteLine(excpStr.ToString(), Array.Empty