diff --git a/extensions/Sisk.SslProxy/DnsUtil.cs b/extensions/Sisk.SslProxy/DnsUtil.cs index a534fd5..687e14d 100644 --- a/extensions/Sisk.SslProxy/DnsUtil.cs +++ b/extensions/Sisk.SslProxy/DnsUtil.cs @@ -33,9 +33,8 @@ public static IPEndPoint ResolveEndpoint(ListeningPort port, bool onlyUseIPv4 = else { resolvedAddress = - // try to return the last IPv6, or the last IPv4 if no IPv6 was found (#16) - hostEntry.AddressList.LastOrDefault(a => a.AddressFamily == AddressFamily.InterNetworkV6) - ?? hostEntry.AddressList.LastOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork); + // try to return the last IPv6 or IPv4 + hostEntry.AddressList.LastOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6); } if (resolvedAddress is null) diff --git a/src/Helpers/PathHelper.cs b/src/Helpers/PathHelper.cs index bf61d69..b2af734 100644 --- a/src/Helpers/PathHelper.cs +++ b/src/Helpers/PathHelper.cs @@ -24,6 +24,17 @@ public static string CombinePaths(params string[] paths) { return PathUtility.CombinePaths(paths); } + + /// + /// Normalizes and combines the specified file-system paths into one. + /// + /// Specifies if relative paths should be merged and ".." returns should be respected. + /// Specifies the path separator character. + /// Specifies the array of paths to combine. + public static string FilesystemCombinePaths(bool allowRelativeReturn, char separator, params string[] paths) + { + return PathUtility.NormalizedCombine(allowRelativeReturn, separator, paths); + } /// /// Normalizes and combines the specified file-system paths into one. diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs index f22c171..4820a90 100644 --- a/src/Http/HttpServer__Core.cs +++ b/src/Http/HttpServer__Core.cs @@ -357,7 +357,9 @@ private void ProcessRequest(HttpListenerContext context) if (response.Content is ByteArrayContent barrayContent) { ApplyHttpContentHeaders(baseResponse, barrayContent.Headers); - ReadOnlySpan contentBytes = ByteArrayAccessors.UnsafeGetContent(barrayContent); + byte[] contentBytes = ByteArrayAccessors.UnsafeGetContent(barrayContent); + int offset = ByteArrayAccessors.UnsafeGetOffset(barrayContent); + int count = ByteArrayAccessors.UnsafeGetCount(barrayContent); if (response.SendChunked) { @@ -366,10 +368,10 @@ private void ProcessRequest(HttpListenerContext context) else { baseResponse.SendChunked = false; - baseResponse.ContentLength64 = contentBytes.Length; + baseResponse.ContentLength64 = count; } - baseResponse.OutputStream.Write(contentBytes); + baseResponse.OutputStream.Write(contentBytes, offset, count); } else if (response.Content is HttpContent httpContent) { diff --git a/src/Http/Streams/HttpStreamPingPolicy.cs b/src/Http/Streams/HttpStreamPingPolicy.cs index e00c404..9aceede 100644 --- a/src/Http/Streams/HttpStreamPingPolicy.cs +++ b/src/Http/Streams/HttpStreamPingPolicy.cs @@ -44,6 +44,19 @@ public void Start() { this._timer = new Timer(new TimerCallback(this.OnCallback), null, 0, (int)this.Interval.TotalMilliseconds); } + + /// + /// Configures and starts sending periodic pings to the client. + /// + /// The payload message that is sent to the server as a ping message. + /// The sending interval for each ping message. + public void Start(string dataMessage, TimeSpan interval) + { + this.DataMessage = dataMessage; + this.Interval = interval; + + this.Start(); + } private void OnCallback(object? state) { diff --git a/src/Http/Streams/HttpWebSocket.cs b/src/Http/Streams/HttpWebSocket.cs index 44a9fb5..082b4ae 100644 --- a/src/Http/Streams/HttpWebSocket.cs +++ b/src/Http/Streams/HttpWebSocket.cs @@ -9,7 +9,6 @@ using System.Net.WebSockets; using System.Runtime.CompilerServices; -using System.Text; namespace Sisk.Core.Http.Streams { @@ -78,7 +77,7 @@ public sealed class HttpWebSocket /// Represents the event which is called when this web socket receives an message from /// remote origin. /// - public event WebSocketMessageReceivedEventHandler? OnReceive = null; + public event WebSocketMessageReceivedEventHandler? OnReceive; internal HttpWebSocket(HttpListenerWebSocketContext ctx, HttpRequest req, string? identifier) { @@ -170,7 +169,7 @@ internal async void ReceiveTask() } else { - if (OnReceive != null) OnReceive(this, message); + if (OnReceive != null) OnReceive.Invoke(this, message); } } } @@ -199,35 +198,61 @@ public HttpWebSocket WithPing(string probeMessage, TimeSpan interval) } /// - /// Sends an text message to the remote point. + /// Asynchronously sends an message to the remote point. /// /// The target message which will be as an encoded UTF-8 string. - public void Send(object? message) + public Task SendAsync(object message) { - string? t = message?.ToString(); - this.Send(t ?? string.Empty); + return Task.FromResult(this.Send(message)); + } + + /// + /// Asynchronously sends an text message to the remote point. + /// + /// The target message which will be as an encoded UTF-8 string. + public Task SendAsync(string message) + { + return Task.FromResult(this.Send(message)); + } + + /// + /// Asynchronously sends an binary message to the remote point. + /// + /// The target message which will be as an encoded UTF-8 string. + public Task SendAsync(byte[] buffer) + { + return Task.FromResult(this.Send(buffer)); } /// /// Sends an text message to the remote point. /// /// The target message which will be as an encoded UTF-8 string. - public void Send(string message) + public bool Send(object message) + { + string? t = message.ToString(); + if (t is null) throw new ArgumentNullException(nameof(message)); + + return this.Send(t); + } + + /// + /// Sends an text message to the remote point. + /// + /// The target message which will be as an encoded using the request preferred encoding. + public bool Send(string message) { - byte[] messageBytes = Encoding.UTF8.GetBytes(message); - ReadOnlyMemory span = new ReadOnlyMemory(messageBytes); - this.SendInternal(span, WebSocketMessageType.Text); + ArgumentNullException.ThrowIfNull(message); + + byte[] messageBytes = this.request.RequestEncoding.GetBytes(message); + return this.SendInternal(messageBytes, WebSocketMessageType.Text); } /// /// Sends an binary message to the remote point. /// /// The target byte array. - public void Send(byte[] buffer) - { - ReadOnlyMemory span = new ReadOnlyMemory(buffer); - this.SendInternal(span, WebSocketMessageType.Binary); - } + public bool Send(byte[] buffer) => this.Send(buffer, 0, buffer.Length); /// /// Sends an binary message to the remote point. @@ -235,19 +260,19 @@ public void Send(byte[] buffer) /// The target byte array. /// The index at which to begin the memory. /// The number of items in the memory. - public void Send(byte[] buffer, int start, int length) + public bool Send(byte[] buffer, int start, int length) { ReadOnlyMemory span = new ReadOnlyMemory(buffer, start, length); - this.SendInternal(span, WebSocketMessageType.Binary); + return this.SendInternal(span, WebSocketMessageType.Binary); } /// /// Sends an binary message to the remote point. /// /// The target byte memory. - public void Send(ReadOnlyMemory buffer) + public bool Send(ReadOnlyMemory buffer) { - this.SendInternal(buffer, WebSocketMessageType.Binary); + return this.SendInternal(buffer, WebSocketMessageType.Binary); } /// @@ -290,9 +315,9 @@ public HttpResponse Close() } [MethodImpl(MethodImplOptions.Synchronized)] - private void SendInternal(ReadOnlyMemory buffer, WebSocketMessageType msgType) + private bool SendInternal(ReadOnlyMemory buffer, WebSocketMessageType msgType) { - if (this.isClosed) { return; } + if (this.isClosed) { return false; } if (this.closeTimeout.TotalMilliseconds > 0) this.asyncListenerToken?.CancelAfter(this.closeTimeout); @@ -323,9 +348,10 @@ private void SendInternal(ReadOnlyMemory buffer, WebSocketMessageType msgT if (this.MaxAttempts >= 0 && this.attempt >= this.MaxAttempts) { this.Close(); - return; + return false; } } + return true; } /// @@ -419,18 +445,18 @@ public sealed class WebSocketMessage /// /// Reads the message bytes as string using the specified encoding. /// - /// The encoding which will be used to decode the message. - public string GetString(System.Text.Encoding encoder) + /// The encoding which will be used to decode the message. + public string GetString(System.Text.Encoding encoding) { - return encoder.GetString(this.MessageBytes); + return encoding.GetString(this.MessageBytes); } /// - /// Reads the message bytes as string using the UTF-8 text encoding. + /// Reads the message bytes as string using the HTTP request encoding. /// public string GetString() { - return this.GetString(Encoding.UTF8); + return this.GetString(this.Sender.HttpRequest.RequestEncoding); } internal WebSocketMessage(HttpWebSocket httpws, int bufferLen) diff --git a/src/Internal/ByteArrayAccessors.cs b/src/Internal/ByteArrayAccessors.cs index 6ca1968..f685038 100644 --- a/src/Internal/ByteArrayAccessors.cs +++ b/src/Internal/ByteArrayAccessors.cs @@ -15,4 +15,10 @@ class ByteArrayAccessors { [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_content")] public extern static ref byte[] UnsafeGetContent(ByteArrayContent bcontent); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_offset")] + public extern static ref int UnsafeGetOffset(ByteArrayContent bcontent); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_count")] + public extern static ref int UnsafeGetCount(ByteArrayContent bcontent); } diff --git a/tcp/Sisk.ManagedHttpListener/HttpConnection.cs b/tcp/Sisk.ManagedHttpListener/HttpConnection.cs index a2f91e0..3d4fb78 100644 --- a/tcp/Sisk.ManagedHttpListener/HttpConnection.cs +++ b/tcp/Sisk.ManagedHttpListener/HttpConnection.cs @@ -25,7 +25,7 @@ public int HandleConnectionEvents() { //try //{ - using var bufferedStreamSession = new Streams.HttpBufferedStream(_connectionStream); + using var bufferedStreamSession = new Streams.HttpBufferedReadStream(_connectionStream); if (!HttpRequestSerializer.TryReadHttp1Request( bufferedStreamSession, diff --git a/tcp/Sisk.ManagedHttpListener/HttpSerializer/HttpRequestSerializer.cs b/tcp/Sisk.ManagedHttpListener/HttpSerializer/HttpRequestSerializer.cs index 0991200..ce804fd 100644 --- a/tcp/Sisk.ManagedHttpListener/HttpSerializer/HttpRequestSerializer.cs +++ b/tcp/Sisk.ManagedHttpListener/HttpSerializer/HttpRequestSerializer.cs @@ -5,7 +5,7 @@ namespace Sisk.ManagedHttpListener.HttpSerializer; internal static class HttpRequestSerializer { - static ReadOnlySpan ReadUntil(Span readBuffer, Streams.HttpBufferedStream bufferStream, byte intercept, out bool found) + static ReadOnlySpan ReadUntil(Span readBuffer, Streams.HttpBufferedReadStream bufferStream, byte intercept, out bool found) { int accumulatedPosition = (int)bufferStream.Position; while (bufferStream.Read(readBuffer) > 0) @@ -29,7 +29,7 @@ static ReadOnlySpan ReadUntil(Span readBuffer, Streams.HttpBufferedS } public static bool TryReadHttp1Request( - Streams.HttpBufferedStream inboundStream, + Streams.HttpBufferedReadStream inboundStream, scoped Span lineMemory, [NotNullWhen(true)] out string? method, [NotNullWhen(true)] out string? path, diff --git a/tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedStream.cs b/tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedReadStream.cs similarity index 87% rename from tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedStream.cs rename to tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedReadStream.cs index 20358b8..53837a2 100644 --- a/tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedStream.cs +++ b/tcp/Sisk.ManagedHttpListener/Streams/HttpBufferedReadStream.cs @@ -2,20 +2,22 @@ namespace Sisk.ManagedHttpListener.Streams; -internal class HttpBufferedStream : Stream +internal class HttpBufferedReadStream : Stream { + private const int INITIAL_BUFFER = 4096; + private readonly Stream _stream; private readonly byte[] _b; private long _position; private int _read; - public HttpBufferedStream(Stream stream) + public HttpBufferedReadStream(Stream stream) { _stream = stream; _position = 0; _read = 0; - _b = ArrayPool.Shared.Rent(4096); + _b = ArrayPool.Shared.Rent(INITIAL_BUFFER); } public Span BufferedBytes => _b; @@ -100,4 +102,10 @@ public void Move(int toPosition) ArgumentOutOfRangeException.ThrowIfGreaterThan(toPosition, _read); _position = toPosition; } + + protected override void Dispose(bool disposing) + { + ArrayPool.Shared.Return(_b); + base.Dispose(disposing); + } }