aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Net.Http/src')
-rw-r--r--lib/Net.Http/src/Core/ConnectionInfo.cs26
-rw-r--r--lib/Net.Http/src/Core/HttpCookie.cs121
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs18
-rw-r--r--lib/Net.Http/src/Core/IHttpContextInformation.cs3
-rw-r--r--lib/Net.Http/src/Core/PerfCounter/HttpPerfCounter.cs76
-rw-r--r--lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs32
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs7
-rw-r--r--lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs2
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs30
-rw-r--r--lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs6
-rw-r--r--lib/Net.Http/src/Core/Response/ResponseWriter.cs27
-rw-r--r--lib/Net.Http/src/Helpers/HttpControlMask.cs4
-rw-r--r--lib/Net.Http/src/Helpers/HttpHelpers.cs14
-rw-r--r--lib/Net.Http/src/HttpConfig.cs7
-rw-r--r--lib/Net.Http/src/HttpResponseCookie.cs226
-rw-r--r--lib/Net.Http/src/IConnectionInfo.cs11
-rw-r--r--lib/Net.Http/src/VNLib.Net.Http.csproj7
17 files changed, 415 insertions, 202 deletions
diff --git a/lib/Net.Http/src/Core/ConnectionInfo.cs b/lib/Net.Http/src/Core/ConnectionInfo.cs
index bcc5fe7..4a46971 100644
--- a/lib/Net.Http/src/Core/ConnectionInfo.cs
+++ b/lib/Net.Http/src/Core/ConnectionInfo.cs
@@ -88,33 +88,17 @@ namespace VNLib.Net.Http
public IReadOnlyCollection<string> Accept => Context.Request.Accept;
///<inheritdoc/>
- public ref readonly TransportSecurityInfo? GetTransportSecurityInfo() => ref Context.GetSecurityInfo();
+ public ref readonly TransportSecurityInfo? GetTransportSecurityInfo() => ref Context.GetSecurityInfo();
///<inheritdoc/>
- public void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure)
+ public void SetCookie(in HttpResponseCookie cookie)
{
//name MUST not be null
- ArgumentNullException.ThrowIfNull(name);
-
- //Create the new cookie
- HttpCookie cookie = new(name)
- {
- Value = value,
- Domain = domain,
- Path = path,
- MaxAge = Expires,
- //Set the session lifetime flag if the timeout is max value
- IsSession = Expires == TimeSpan.MaxValue,
- //If the connection is cross origin, then we need to modify the secure and samsite values
- SameSite = CrossOrigin ? CookieSameSite.None : sameSite,
- Secure = secure | CrossOrigin,
- HttpOnly = httpOnly
- };
-
- //Set the cookie
+ ArgumentException.ThrowIfNullOrWhiteSpace(cookie.Name, nameof(cookie.Name));
+
Context.Response.AddCookie(in cookie);
}
-
+
internal ConnectionInfo(HttpContext ctx)
{
//Update the context referrence
diff --git a/lib/Net.Http/src/Core/HttpCookie.cs b/lib/Net.Http/src/Core/HttpCookie.cs
deleted file mode 100644
index b805e3e..0000000
--- a/lib/Net.Http/src/Core/HttpCookie.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-* Copyright (c) 2024 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Net.Http
-* File: HttpCookie.cs
-*
-* HttpCookie.cs is part of VNLib.Net.Http which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Net.Http is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Net.Http is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-
-namespace VNLib.Net.Http.Core
-{
- internal readonly struct HttpCookie(string name) : IStringSerializeable, IEquatable<HttpCookie>
- {
- public readonly string Name { get; } = name;
- public readonly string? Value { get; init; }
- public readonly string? Domain { get; init; }
- public readonly string? Path { get; init; }
- public readonly TimeSpan MaxAge { get; init; }
- public readonly CookieSameSite SameSite { get; init; }
- public readonly bool Secure { get; init; }
- public readonly bool HttpOnly { get; init; }
- public readonly bool IsSession { get; init; }
-
- public readonly string Compile() => throw new NotImplementedException();
-
- public readonly void Compile(ref ForwardOnlyWriter<char> writer)
- {
- //set the name of the cookie
- writer.Append(Name);
- writer.Append('=');
-
- //set name
- writer.Append(Value);
-
- //Only set the max age parameter if the cookie is not a session cookie
- if (!IsSession)
- {
- writer.Append("; Max-Age=");
- writer.Append((int)MaxAge.TotalSeconds);
- }
-
- //Make sure domain is set
- if (!string.IsNullOrWhiteSpace(Domain))
- {
- writer.Append("; Domain=");
- writer.Append(Domain);
- }
-
- //Check and set path
- if (!string.IsNullOrWhiteSpace(Path))
- {
- //Set path
- writer.Append("; Path=");
- writer.Append(Path);
- }
-
- writer.Append("; SameSite=");
-
- //Set the samesite flag based on the enum value
- switch (SameSite)
- {
- case CookieSameSite.None:
- writer.Append("None");
- break;
- case CookieSameSite.Strict:
- writer.Append("Strict");
- break;
- case CookieSameSite.Lax:
- default:
- writer.Append("Lax");
- break;
- }
-
- //Set httponly flag
- if (HttpOnly)
- {
- writer.Append("; HttpOnly");
- }
-
- //Set secure flag
- if (Secure)
- {
- writer.Append("; Secure");
- }
- }
-
- public readonly ERRNO Compile(Span<char> buffer)
- {
- ForwardOnlyWriter<char> writer = new(buffer);
- Compile(ref writer);
- return writer.Written;
- }
-
- public readonly override int GetHashCode() => string.GetHashCode(Name, StringComparison.OrdinalIgnoreCase);
-
- public readonly override bool Equals(object? obj) => obj is HttpCookie other && Equals(other);
-
- public readonly bool Equals(HttpCookie other) => Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase);
- }
-} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpServerProcessing.cs b/lib/Net.Http/src/Core/HttpServerProcessing.cs
index 7770ad7..b6dbfef 100644
--- a/lib/Net.Http/src/Core/HttpServerProcessing.cs
+++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs
@@ -34,9 +34,11 @@ using System.Runtime.CompilerServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
using VNLib.Net.Http.Core;
using VNLib.Net.Http.Core.Buffering;
using VNLib.Net.Http.Core.Response;
+using VNLib.Net.Http.Core.PerfCounter;
namespace VNLib.Net.Http
{
@@ -167,14 +169,20 @@ namespace VNLib.Net.Http
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private async Task<bool> ProcessHttpEventAsync(HttpContext context)
{
+ HttpPerfCounterState counter = default;
+
//Prepare http context to process a new message
context.BeginRequest();
try
{
+ HttpPerfCounter.StartCounter(ref counter);
+
//Try to parse the http request (may throw exceptions, let them propagate to the transport layer)
int status = (int)ParseRequest(context);
+ HttpPerfCounter.StopAndLog(ref counter, in _config, "HTTP Parse");
+
//Check status code for socket error, if so, return false to close the connection
if (status >= 1000)
{
@@ -204,7 +212,7 @@ namespace VNLib.Net.Http
context.Request.Compile(ref writer);
//newline
- writer.Append("\r\n");
+ writer.AppendSmall("\r\n");
//Response
context.Response.Compile(ref writer);
@@ -218,11 +226,15 @@ namespace VNLib.Net.Http
WriteConnectionDebugLog(this, context);
}
#endif
+
+ HttpPerfCounter.StartCounter(ref counter);
await context.WriteResponseAsync();
-
+
await context.FlushTransportAsync();
-
+
+ HttpPerfCounter.StopAndLog(ref counter, in _config, "HTTP Response");
+
/*
* If an alternate protocol was specified, we need to break the keepalive loop
* the handler will manage the alternate protocol
diff --git a/lib/Net.Http/src/Core/IHttpContextInformation.cs b/lib/Net.Http/src/Core/IHttpContextInformation.cs
index 14067f5..38e86b3 100644
--- a/lib/Net.Http/src/Core/IHttpContextInformation.cs
+++ b/lib/Net.Http/src/Core/IHttpContextInformation.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -22,7 +22,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System;
using System.IO;
using System.Text;
diff --git a/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounter.cs b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounter.cs
new file mode 100644
index 0000000..0dcefb1
--- /dev/null
+++ b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounter.cs
@@ -0,0 +1,76 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpPerfCounter.cs
+*
+* HttpPerfCounter.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+
+using VNLib.Utils.Logging;
+
+
+namespace VNLib.Net.Http.Core.PerfCounter
+{
+ internal static class HttpPerfCounter
+ {
+
+ [Conditional("DEBUG")]
+ internal static void StartCounter(ref HttpPerfCounterState state)
+ {
+ state.StopValue = state.StartValue = TimeProvider.System.GetTimestamp();
+ }
+
+ [Conditional("DEBUG")]
+ internal static void StopCounter(ref HttpPerfCounterState state)
+ {
+ state.StopValue = TimeProvider.System.GetTimestamp();
+ }
+
+ /// <summary>
+ /// Gets the total time elapsed in microseconds
+ /// </summary>
+ /// <returns>The time in microseconds that has elapsed since the timer was started and stopped</returns>
+ internal static TimeSpan GetElapsedTime(ref readonly HttpPerfCounterState state)
+ => TimeProvider.System.GetElapsedTime(state.StartValue, state.StopValue);
+
+ /*
+ * Enable http performance counters for tracing.
+ * Only available in debug builds until it can be
+ * configured for zero-cost
+ */
+
+ [Conditional("DEBUG")]
+ internal static void StopAndLog(ref HttpPerfCounterState state, ref readonly HttpConfig config, string counter)
+ {
+ if (!config.DebugPerformanceCounters)
+ {
+ return;
+ }
+
+ StopCounter(ref state);
+
+ TimeSpan duration = GetElapsedTime(in state);
+
+ config.ServerLog.Debug("[PERF]: ({state}) - {us}us elapsed", counter, duration.TotalMicroseconds);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs
new file mode 100644
index 0000000..a86ac40
--- /dev/null
+++ b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ConnectionInfo.cs
+*
+* ConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Net.Http.Core.PerfCounter
+{
+ internal struct HttpPerfCounterState
+ {
+ internal long StartValue;
+ internal long StopValue;
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs
index 3ebf0d4..2c9eed0 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequest.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs
@@ -161,11 +161,12 @@ namespace VNLib.Net.Http.Core
{
return Array.Empty<FileUpload>();
}
+
//Create new array to hold uploads
- FileUpload[] uploads = new FileUpload[_state.UploadCount];
- //Copy uploads to new array
+ FileUpload[] uploads = GC.AllocateUninitializedArray<FileUpload>(_state.UploadCount, false);
+
Array.Copy(_uploads, uploads, _state.UploadCount);
- //Return new array
+
return uploads;
}
diff --git a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
index dcd0553..93ce5b2 100644
--- a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
+++ b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
@@ -93,7 +93,7 @@ namespace VNLib.Net.Http.Core
//Determine if compression should be used
bool compressionDisabled =
//disabled because app code disabled it
- ContextFlags.IsSet(HttpControlMask.CompressionDisabed)
+ ContextFlags.IsSet(HttpControlMask.CompressionDisabled)
//Disabled because too large or too small
|| length >= ParentServer.Config.CompressionLimit
|| length < ParentServer.Config.CompressionMinimum
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
index 1340dac..e354998 100644
--- a/lib/Net.Http/src/Core/Response/HttpResponse.cs
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -46,7 +46,7 @@ namespace VNLib.Net.Http.Core.Response
{
const int DefaultCookieCapacity = 2;
- private readonly Dictionary<string, HttpCookie> Cookies = new(DefaultCookieCapacity, StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary<string, HttpResponseCookie> Cookies = new(DefaultCookieCapacity, StringComparer.OrdinalIgnoreCase);
private readonly DirectStream ReusableDirectStream = new();
private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ContextInfo);
private readonly HeaderDataAccumulator Writer = new(manager.ResponseHeaderBuffer, ContextInfo);
@@ -61,7 +61,7 @@ namespace VNLib.Net.Http.Core.Response
/// <summary>
/// Response header collection
/// </summary>
- public VnWebHeaderCollection Headers { get; } = [];
+ public readonly VnWebHeaderCollection Headers = [];
/// <summary>
/// The current http status code value
@@ -88,7 +88,7 @@ namespace VNLib.Net.Http.Core.Response
/// </summary>
/// <param name="cookie">Cookie to add</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void AddCookie(in HttpCookie cookie) => Cookies[cookie.Name] = cookie;
+ internal void AddCookie(ref readonly HttpResponseCookie cookie) => Cookies[cookie.Name] = cookie;
/// <summary>
/// Compiles and flushes all headers to the header accumulator ready for sending
@@ -106,13 +106,13 @@ namespace VNLib.Net.Http.Core.Response
if (!HeadersBegun)
{
//write status code first
- writer.Append(HttpHelpers.GetResponseString(ContextInfo.CurrentVersion, _code));
- writer.Append(HttpHelpers.CRLF);
+ writer.AppendSmall(HttpHelpers.GetResponseString(ContextInfo.CurrentVersion, _code));
+ writer.AppendSmall(HttpHelpers.CRLF);
//Write the date to header buffer
- writer.Append("Date: ");
+ writer.AppendSmall("Date: ");
writer.Append(DateTimeOffset.UtcNow, "R");
- writer.Append(HttpHelpers.CRLF);
+ writer.AppendSmall(HttpHelpers.CRLF);
//Set begun flag
HeadersBegun = true;
@@ -122,10 +122,10 @@ namespace VNLib.Net.Http.Core.Response
for (int i = 0; i < Headers.Count; i++)
{
//<name>: <value>\r\n
- writer.Append(Headers.Keys[i]);
- writer.Append(": ");
- writer.Append(Headers[i]);
- writer.Append(HttpHelpers.CRLF);
+ writer.Append(Headers.Keys[i]);
+ writer.AppendSmall(": ");
+ writer.Append(Headers[i]);
+ writer.AppendSmall(HttpHelpers.CRLF);
}
//Remove writen headers
@@ -134,14 +134,14 @@ namespace VNLib.Net.Http.Core.Response
//Write cookies if any are set
if (Cookies.Count > 0)
{
- foreach (HttpCookie cookie in Cookies.Values)
+ foreach (HttpResponseCookie cookie in Cookies.Values)
{
- writer.Append("Set-Cookie: ");
+ writer.AppendSmall("Set-Cookie: ");
//Write the cookie to the header buffer
cookie.Compile(ref writer);
- writer.Append(HttpHelpers.CRLF);
+ writer.AppendSmall(HttpHelpers.CRLF);
}
Cookies.Clear();
@@ -431,7 +431,7 @@ namespace VNLib.Net.Http.Core.Response
}
//Enumerate and write
- foreach (HttpCookie cookie in Cookies.Values)
+ foreach (HttpResponseCookie cookie in Cookies.Values)
{
writer.Append("Set-Cookie: ");
diff --git a/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs b/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs
index 797d490..bd43def 100644
--- a/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs
+++ b/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs
@@ -3,10 +3,10 @@
*
* Library: VNLib
* Package: VNLib.Net.Http
-* File: ResponseWriter.cs
+* File: ResponsBodyDataState.cs
*
-* ResponseWriter.cs is part of VNLib.Net.Http which is part of the larger
-* VNLib collection of libraries and utilities.
+* ResponsBodyDataState.cs is part of VNLib.Net.Http which is part of
+* the larger VNLib collection of libraries and utilities.
*
* VNLib.Net.Http is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
diff --git a/lib/Net.Http/src/Core/Response/ResponseWriter.cs b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
index b60537d..fabdff3 100644
--- a/lib/Net.Http/src/Core/Response/ResponseWriter.cs
+++ b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
@@ -182,30 +182,33 @@ namespace VNLib.Net.Http.Core.Response
{
if (blockSize > 0)
{
+ /*
+ * Write data directly from memory response but fix the block size to the size
+ * of the compressor if it has one, to optimize compression
+ */
while (_userState.MemResponse.Remaining > 0)
{
- //Get next segment clamped to the block size
_readSegment = _userState.MemResponse.GetRemainingConstrained(blockSize);
-
- //Commit output bytes
+
await dest.WriteAsync(_readSegment);
-
- //Advance by the written amount
+
_userState.MemResponse.Advance(_readSegment.Length);
}
}
else
- {
- //Write response body from memory
+ {
+ /*
+ * Compressor block size is unkown so we can assume it does not matter
+ * and write full blocks as they are read. This will usually be a on-shot
+ * operation, since the writer handles chunk buffering
+ */
+
while (_userState.MemResponse.Remaining > 0)
{
- //Get remaining segment
_readSegment = _userState.MemResponse.GetMemory();
-
- //Write segment to output stream
+
await dest.WriteAsync(_readSegment);
-
- //Advance by the written amount
+
_userState.MemResponse.Advance(_readSegment.Length);
}
}
diff --git a/lib/Net.Http/src/Helpers/HttpControlMask.cs b/lib/Net.Http/src/Helpers/HttpControlMask.cs
index a2a004d..e24d088 100644
--- a/lib/Net.Http/src/Helpers/HttpControlMask.cs
+++ b/lib/Net.Http/src/Helpers/HttpControlMask.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -33,7 +33,7 @@ namespace VNLib.Net.Http
/// <summary>
/// Tells the http server that dynamic response compression should be disabled
/// </summary>
- public const ulong CompressionDisabed = 0x01UL;
+ public const ulong CompressionDisabled = 0x01UL;
/// <summary>
/// Tells the server not to set a 0 content length header when sending a response that does
diff --git a/lib/Net.Http/src/Helpers/HttpHelpers.cs b/lib/Net.Http/src/Helpers/HttpHelpers.cs
index 86616f8..cf8e189 100644
--- a/lib/Net.Http/src/Helpers/HttpHelpers.cs
+++ b/lib/Net.Http/src/Helpers/HttpHelpers.cs
@@ -227,29 +227,29 @@ namespace VNLib.Net.Http
ForwardOnlyWriter<char> sb = new(buffer);
if ((type & CacheType.NoCache) > 0)
{
- sb.Append("no-cache, ");
+ sb.AppendSmall("no-cache, ");
}
if ((type & CacheType.NoStore) > 0)
{
- sb.Append("no-store, ");
+ sb.AppendSmall("no-store, ");
}
if ((type & CacheType.Public) > 0)
{
- sb.Append("public, ");
+ sb.AppendSmall("public, ");
}
if ((type & CacheType.Private) > 0)
{
- sb.Append("private, ");
+ sb.AppendSmall("private, ");
}
if ((type & CacheType.Revalidate) > 0)
{
- sb.Append("must-revalidate, ");
+ sb.AppendSmall("must-revalidate, ");
}
if (immutable)
{
- sb.Append("immutable, ");
+ sb.AppendSmall("immutable, ");
}
- sb.Append("max-age=");
+ sb.AppendSmall("max-age=");
sb.Append(maxAge);
return sb.ToString();
}
diff --git a/lib/Net.Http/src/HttpConfig.cs b/lib/Net.Http/src/HttpConfig.cs
index 274e163..c74bdbb 100644
--- a/lib/Net.Http/src/HttpConfig.cs
+++ b/lib/Net.Http/src/HttpConfig.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -124,5 +124,10 @@ namespace VNLib.Net.Http
/// the server.
/// </summary>
public readonly IHttpCompressorManager? CompressorManager { get; init; } = null;
+
+ /// <summary>
+ /// Enables debug performance counters
+ /// </summary>
+ public readonly bool DebugPerformanceCounters { get; init; } = false;
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/HttpResponseCookie.cs b/lib/Net.Http/src/HttpResponseCookie.cs
new file mode 100644
index 0000000..8fc54c2
--- /dev/null
+++ b/lib/Net.Http/src/HttpResponseCookie.cs
@@ -0,0 +1,226 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpResponseCookie.cs
+*
+* HttpResponseCookie.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents an HTTP cookie that is set with responses.
+ /// </summary>
+ /// <param name="name">The cookie name</param>
+ public readonly struct HttpResponseCookie(string name) : IStringSerializeable, IEquatable<HttpResponseCookie>
+ {
+ /// <summary>
+ /// The default copy buffer allocated when calling the <see cref="Compile()"/>
+ /// family of methods.
+ /// </summary>
+ public const int DefaultCookieBufferSize = 4096;
+
+
+ /// <summary>
+ /// The name of the cookie to set.
+ /// </summary>
+ public readonly string Name { get; } = name;
+
+ /// <summary>
+ /// The actual cookie content or value.
+ /// </summary>
+ public readonly string? Value { get; init; }
+
+ /// <summary>
+ /// The domain this cookie will be sent to.
+ /// </summary>
+ public readonly string? Domain { get; init; }
+
+ /// <summary>
+ /// The cookie path the client will send this cookie with. Null
+ /// or empty string for all paths.
+ /// </summary>
+ public readonly string? Path { get; init; }
+
+ /// <summary>
+ /// Sets the duration of the cookie lifetime (in seconds), aka MaxAge
+ /// </summary>
+ public readonly TimeSpan MaxAge { get; init; }
+
+ /// <summary>
+ /// Sets the cookie Samesite field.
+ /// </summary>
+ public readonly CookieSameSite SameSite { get; init; }
+
+ /// <summary>
+ /// Sets the cookie Secure flag. If true only sends the cookie with requests
+ /// if the connection is secure.
+ /// </summary>
+ public readonly bool Secure { get; init; }
+
+ /// <summary>
+ /// Sets cookie HttpOnly flag. If true denies JavaScript access to
+ /// </summary>
+ public readonly bool HttpOnly { get; init; }
+
+ /// <summary>
+ /// Sets the cookie expiration to the duration of the user's session (aka no expiration)
+ /// </summary>
+ public readonly bool IsSession { get; init; }
+
+ /// <summary>
+ /// Creates an HTTP 1.x spec cookie header value from the
+ /// cookie fields
+ /// <para>
+ /// The internal copy buffer defaults to <see cref="DefaultCookieBufferSize"/>
+ /// use <see cref="Compile(int)"/> if you need control over the buffer size
+ /// </para>
+ /// </summary>
+ /// <returns>The cookie header value as a string</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public readonly string Compile()
+ {
+ nint bufSize = MemoryUtil.NearestPage(DefaultCookieBufferSize);
+
+ return Compile(bufSize.ToInt32());
+ }
+
+ /// <summary>
+ /// Creates an HTTP 1.x spec cookie header value from the
+ /// cookie fields.
+ /// </summary>
+ /// <param name="bufferSize">The size of the internal accumulator buffer</param>
+ /// <returns>The cookie header value as a string</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public readonly string Compile(int bufferSize)
+ {
+ using UnsafeMemoryHandle<char> cookieBuffer = MemoryUtil.UnsafeAlloc<char>(bufferSize, false);
+
+ ERRNO count = Compile(cookieBuffer.Span);
+
+ return cookieBuffer.AsSpan(0, (int)count).ToString();
+ }
+
+ /// <summary>
+ /// Creates an HTTP 1.x spec cookie header value from the
+ /// cookie fields.
+ /// </summary>
+ /// <param name="buffer">The character buffer to write the cookie data tor</param>
+ /// <returns>The cookie header value as a string</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public readonly ERRNO Compile(Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+
+ /// <summary>
+ /// Writes the HTTP 1.x header format for the cookie
+ /// </summary>
+ /// <param name="writer"></param>
+ public readonly void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ writer.Append(Name);
+ writer.Append('=');
+ writer.Append(Value);
+
+ /*
+ * If a session cookie is set, then do not include a max-age value
+ * browsers will default to session duration if not set
+ */
+ if (!IsSession)
+ {
+ writer.AppendSmall("; Max-Age=");
+ writer.Append((int)MaxAge.TotalSeconds);
+ }
+
+ if (!string.IsNullOrWhiteSpace(Domain))
+ {
+ writer.AppendSmall("; Domain=");
+ writer.Append(Domain);
+ }
+
+ if (!string.IsNullOrWhiteSpace(Path))
+ {
+ //Set path
+ writer.AppendSmall("; Path=");
+ writer.Append(Path);
+ }
+
+ writer.AppendSmall("; SameSite=");
+
+ switch (SameSite)
+ {
+ case CookieSameSite.None:
+ writer.AppendSmall("None");
+ break;
+ case CookieSameSite.Strict:
+ writer.AppendSmall("Strict");
+ break;
+ case CookieSameSite.Lax:
+ default:
+ writer.AppendSmall("Lax");
+ break;
+ }
+
+ if (HttpOnly)
+ {
+ writer.AppendSmall("; HttpOnly");
+ }
+
+ if (Secure)
+ {
+ writer.AppendSmall("; Secure");
+ }
+ }
+
+ ///<inheritdoc/>
+ public readonly override int GetHashCode() => string.GetHashCode(Name, StringComparison.OrdinalIgnoreCase);
+
+ ///<inheritdoc/>
+ public readonly override bool Equals(object? obj) => obj is HttpResponseCookie other && Equals(other);
+
+ ///<inheritdoc/>
+ public readonly bool Equals(HttpResponseCookie other) => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// Creates an HTTP 1.x spec cookie header value from the
+ /// cookie fields
+ /// <para>
+ /// The internal copy buffer defaults to <see cref="DefaultCookieBufferSize"/>
+ /// use <see cref="Compile(int)"/> if you need control over the buffer size
+ /// </para>
+ /// </summary>
+ /// <returns>The cookie header value as a string</returns>
+ public override string ToString() => Compile();
+
+ ///<inheritdoc/>
+ public static bool operator ==(HttpResponseCookie left, HttpResponseCookie right) => left.Equals(right);
+
+ ///<inheritdoc/>
+ public static bool operator !=(HttpResponseCookie left, HttpResponseCookie right) => !(left == right);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IConnectionInfo.cs b/lib/Net.Http/src/IConnectionInfo.cs
index 6cdb480..7598864 100644
--- a/lib/Net.Http/src/IConnectionInfo.cs
+++ b/lib/Net.Http/src/IConnectionInfo.cs
@@ -133,14 +133,7 @@ namespace VNLib.Net.Http
/// Adds a new cookie to the response. If a cookie with the same name and value
/// has been set, the old cookie is replaced with the new one.
/// </summary>
- /// <param name="name">Cookie name/id</param>
- /// <param name="value">Value to be stored in cookie</param>
- /// <param name="domain">Domain for cookie to operate</param>
- /// <param name="path">Path to store cookie</param>
- /// <param name="Expires">Timespan representing how long the cookie should exist</param>
- /// <param name="sameSite">Samesite attribute, Default = Lax</param>
- /// <param name="httpOnly">Specify the HttpOnly flag</param>
- /// <param name="secure">Specify the Secure flag</param>
- void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure);
+ /// <param name="cookie">A reference to the cookie to set on the current response</param>
+ void SetCookie(in HttpResponseCookie cookie);
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/VNLib.Net.Http.csproj b/lib/Net.Http/src/VNLib.Net.Http.csproj
index b491cc1..e57f784 100644
--- a/lib/Net.Http/src/VNLib.Net.Http.csproj
+++ b/lib/Net.Http/src/VNLib.Net.Http.csproj
@@ -2,16 +2,19 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
+ <Nullable>enable</Nullable>
<RootNamespace>VNLib.Net.Http</RootNamespace>
<AssemblyName>VNLib.Net.Http</AssemblyName>
<NeutralLanguage>en-US</NeutralLanguage>
- <Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
- <AnalysisLevel>latest-all</AnalysisLevel>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>
<PropertyGroup>
+ <AnalysisLevel Condition="'$(BuildingInsideVisualStudio)' == true">latest-all</AnalysisLevel>
+ </PropertyGroup>
+
+ <PropertyGroup>
<PackageId>VNLib.Net.Http</PackageId>
<Authors>Vaughn Nugent</Authors>
<Company>Vaughn Nugent</Company>