diff --git a/examples/Web-SelfSigned.ps1 b/examples/Web-SelfSigned.ps1
new file mode 100644
index 000000000..94a2e6356
--- /dev/null
+++ b/examples/Web-SelfSigned.ps1
@@ -0,0 +1,39 @@
+<#
+.SYNOPSIS
+ A sample PowerShell script to set up a HTTPS Pode server with a self-sign certificate
+
+.DESCRIPTION
+ This script sets up a Pode server listening on port 8081 in HTTPS
+
+.EXAMPLE
+ To run the sample: ./Web-SelfSigned.ps1
+
+.LINK
+ https://github.com/Badgerati/Pode/blob/develop/examples/Web-SelfSigned.ps1
+
+.NOTES
+ Author: Pode Team
+ License: MIT License
+#>
+try {
+ $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $podePath = Split-Path -Parent -Path $ScriptPath
+ if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) {
+ Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop
+ }
+ else {
+ Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop
+ }
+}
+catch { throw }
+
+Start-PodeServer -Threads 6 {
+ Add-PodeEndpoint -Address localhost -Port '8081' -Protocol 'Https' -SelfSigned
+
+ New-PodeLoggingMethod -File -Name 'requests' | Enable-PodeRequestLogging
+ New-PodeLoggingMethod -File -Name 'errors' | Enable-PodeErrorLogging
+
+ Add-PodeRoute -Method Get -Path / -ScriptBlock {
+ Write-PodeTextResponse -Value 'Test'
+ }
+}
\ No newline at end of file
diff --git a/src/Listener/PodeHttpRequest.cs b/src/Listener/PodeHttpRequest.cs
index 1742f0b17..6c3eee6b5 100644
--- a/src/Listener/PodeHttpRequest.cs
+++ b/src/Listener/PodeHttpRequest.cs
@@ -412,22 +412,35 @@ public override void PartialDispose()
base.PartialDispose();
}
- public override void Dispose()
+ ///
+ /// Dispose managed and unmanaged resources.
+ ///
+ /// Indicates whether the method is called explicitly or by garbage collection.
+ protected override void Dispose(bool disposing)
{
- RawBody = default;
- _body = string.Empty;
+ if (IsDisposed) return;
- if (BodyStream != default(MemoryStream))
+ if (disposing)
{
- BodyStream.Dispose();
- }
+ // Custom cleanup logic for PodeHttpRequest
+ RawBody = default;
+ _body = string.Empty;
- if (Form != default(PodeForm))
- {
- Form.Dispose();
+ if (BodyStream != default(MemoryStream))
+ {
+ BodyStream.Dispose();
+ BodyStream = default;
+ }
+
+ if (Form != default(PodeForm))
+ {
+ Form.Dispose();
+ Form = default;
+ }
}
- base.Dispose();
+ // Call the base Dispose to clean up shared resources
+ base.Dispose(disposing);
}
}
}
\ No newline at end of file
diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs
index f36a1d938..7fd7e4461 100644
--- a/src/Listener/PodeRequest.cs
+++ b/src/Listener/PodeRequest.cs
@@ -1,14 +1,13 @@
using System;
using System.IO;
using System.Net;
-using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
-using System.Threading.Tasks;
using System.Threading;
+using System.Threading.Tasks;
namespace Pode
{
@@ -27,9 +26,8 @@ public class PodeRequest : PodeProtocol, IDisposable
public bool IsKeepAlive { get; protected set; }
// Flags indicating request characteristics and handling status
- public virtual bool CloseImmediately { get => false; }
- public virtual bool IsProcessable { get => true; }
-
+ public virtual bool CloseImmediately => false;
+ public virtual bool IsProcessable => true;
// Input stream for incoming request data
public Stream InputStream { get; private set; }
public PodeStreamState State { get; private set; }
@@ -39,19 +37,17 @@ public class PodeRequest : PodeProtocol, IDisposable
public X509Certificate Certificate { get; private set; }
public bool AllowClientCertificate { get; private set; }
public PodeTlsMode TlsMode { get; private set; }
- public X509Certificate2 ClientCertificate { get; set; }
- public SslPolicyErrors ClientCertificateErrors { get; set; }
+ public X509Certificate2 ClientCertificate { get; private set; }
+ public SslPolicyErrors ClientCertificateErrors { get; private set; }
public SslProtocols Protocols { get; private set; }
-
- // Error handling for request processing
public PodeRequestException Error { get; set; }
public bool IsAborted => Error != default(PodeRequestException);
public bool IsDisposed { get; private set; }
// Address and Scheme properties for the request
public virtual string Address => Context.PodeSocket.HasHostnames
- ? $"{Context.PodeSocket.Hostname}:{((IPEndPoint)LocalEndPoint).Port}"
- : $"{((IPEndPoint)LocalEndPoint).Address}:{((IPEndPoint)LocalEndPoint).Port}";
+ ? $"{Context.PodeSocket.Hostname}:{((IPEndPoint)LocalEndPoint).Port}"
+ : $"{((IPEndPoint)LocalEndPoint).Address}:{((IPEndPoint)LocalEndPoint).Port}";
public virtual string Scheme => SslUpgraded ? $"{Context.PodeSocket.Type}s" : $"{Context.PodeSocket.Type}";
@@ -60,8 +56,12 @@ public class PodeRequest : PodeProtocol, IDisposable
protected PodeContext Context;
// Encoding and buffer for handling incoming data
- protected static UTF8Encoding Encoding = new UTF8Encoding();
- private byte[] Buffer;
+ protected static readonly UTF8Encoding Encoding = new UTF8Encoding();
+
+ // A fixed buffer used to temporarily store data read from the input stream.
+ // This buffer is readonly to prevent reassignment and reduce memory allocations.
+ private readonly byte[] Buffer;
+
private MemoryStream BufferStream;
private const int BufferSize = 16384;
@@ -83,6 +83,7 @@ public PodeRequest(Socket socket, PodeSocket podeSocket, PodeContext context)
Protocols = podeSocket.Protocols;
Context = context;
State = PodeStreamState.New;
+ Buffer = new byte[BufferSize]; // Allocate buffer once
}
///
@@ -145,6 +146,7 @@ public async Task Open(CancellationToken cancellationToken)
}
}
+
///
/// Upgrades the current connection to SSL/TLS.
///
@@ -152,49 +154,62 @@ public async Task Open(CancellationToken cancellationToken)
/// A Task representing the async operation.
public async Task UpgradeToSSL(CancellationToken cancellationToken)
{
- if (SslUpgraded)
+ if (SslUpgraded || IsDisposed)
{
State = PodeStreamState.Open;
return; // Already upgraded
}
// Create an SSL stream for secure communication
- var ssl = new SslStream(InputStream, false, new RemoteCertificateValidationCallback(ValidateCertificateCallback));
+ var ssl = new SslStream(InputStream, false, ValidateCertificateCallback);
// Authenticate the SSL stream, handling cancellation and exceptions
- using (cancellationToken.Register(() => ssl.Dispose()))
+ try
{
- try
+ using (cancellationToken.Register(() =>
{
- // Authenticate the SSL stream
- await ssl.AuthenticateAsServerAsync(Certificate, AllowClientCertificate, Protocols, false).ConfigureAwait(false);
-
- // Set InputStream to the upgraded SSL stream
- InputStream = ssl;
- SslUpgraded = true;
- State = PodeStreamState.Open;
- }
- catch (Exception ex) when (ex is OperationCanceledException || ex is IOException || ex is ObjectDisposedException)
- {
- PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose);
- State = PodeStreamState.Error;
- Error = new PodeRequestException(ex, 500);
- }
- catch (AuthenticationException ex)
- {
- PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug);
- State = PodeStreamState.Error;
- Error = new PodeRequestException(ex, 400);
- }
- catch (Exception ex)
+ if (ssl != null && !IsDisposed)
+ {
+ ssl.Dispose();
+ }
+ }))
{
- PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error);
- State = PodeStreamState.Error;
- Error = new PodeRequestException(ex, 502);
+
+ // Authenticate the SSL stream
+ await ssl.AuthenticateAsServerAsync(Certificate, AllowClientCertificate, Protocols, false)
+ .ConfigureAwait(false);
}
+
+ // Set InputStream to the upgraded SSL stream
+ InputStream = ssl;
+ SslUpgraded = true;
+ State = PodeStreamState.Open;
+ }
+ catch (Exception ex) when (ex is OperationCanceledException || ex is IOException || ex is ObjectDisposedException)
+ {
+ PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose);
+ ssl?.Dispose();
+ State = PodeStreamState.Error;
+ Error = new PodeRequestException(ex, 500);
+ }
+
+ catch (AuthenticationException ex)
+ {
+ PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug);
+ ssl?.Dispose();
+ State = PodeStreamState.Error;
+ Error = new PodeRequestException(ex, 400);
+ }
+ catch (Exception ex)
+ {
+ PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error);
+ ssl?.Dispose();
+ State = PodeStreamState.Error;
+ Error = new PodeRequestException(ex, 502);
}
}
+
///
/// Callback to validate client certificates during the SSL handshake.
///
@@ -221,42 +236,49 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific
/// A Task representing the async operation, with a boolean indicating whether the connection should be closed.
public async Task Receive(CancellationToken cancellationToken)
{
- // Check if the stream is open
- if (State != PodeStreamState.Open)
- {
- return false;
- }
-
try
{
- Error = default;
+ if (State != PodeStreamState.Open || InputStream == null)
+ {
+ return false;
+ }
+
+ Error = null;
- Buffer = new byte[BufferSize];
using (BufferStream = new MemoryStream())
{
var close = true;
while (true)
{
-#if NETCOREAPP2_1_OR_GREATER
- // Read data from the input stream
- var read = await InputStream.ReadAsync(Buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false);
- if (read <= 0)
+ if (InputStream == null || cancellationToken.IsCancellationRequested || IsDisposed)
{
break;
}
- // Write the data to the buffer stream
- await BufferStream.WriteAsync(Buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
+ int read = 0;
+ try
+ {
+ // Read data from the input stream
+#if NETCOREAPP2_1_OR_GREATER
+ read = await InputStream.ReadAsync(Buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false);
#else
- // Read data from the input stream
- var read = await InputStream.ReadAsync(Buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false);
+ read = await InputStream.ReadAsync(Buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false);
+#endif
+ }
+ catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException)
+ {
+ PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug);
+ break;
+ }
if (read <= 0)
{
break;
}
- // Write the data to the buffer stream
+#if NETCOREAPP2_1_OR_GREATER
+ await BufferStream.WriteAsync(Buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
+#else
await BufferStream.WriteAsync(Buffer, 0, read, cancellationToken).ConfigureAwait(false);
#endif
@@ -301,10 +323,10 @@ public async Task Receive(CancellationToken cancellationToken)
{
PartialDispose();
}
-
return false;
}
+
///
/// Reads data from the input stream until the specified bytes are found.
///
@@ -421,41 +443,62 @@ protected virtual bool ValidateInput(byte[] bytes)
///
public virtual void PartialDispose()
{
- if (BufferStream != default(MemoryStream))
+ try
{
- BufferStream.Dispose();
- BufferStream = default;
- }
+ if (BufferStream != default(MemoryStream))
+ {
+ BufferStream.Dispose();
+ BufferStream = default;
+ }
- Buffer = default;
+ // Clear the contents of the Buffer array
+ if (Buffer != null)
+ {
+ Array.Clear(Buffer, 0, Buffer.Length);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error);
+ }
}
///
- /// Disposes of the request and its associated resources.
+ /// Dispose managed and unmanaged resources.
///
- public virtual void Dispose()
+ /// Indicates if disposing is called manually or by garbage collection.
+ protected virtual void Dispose(bool disposing)
{
- if (IsDisposed)
- {
- return;
- }
+ if (IsDisposed) return;
IsDisposed = true;
- if (Socket != default(Socket))
+ if (disposing)
{
- PodeSocket.CloseSocket(Socket);
- }
+ if (InputStream != default(Stream))
+ {
+ InputStream.Dispose();
+ InputStream = default;
+ }
- if (InputStream != default(Stream))
- {
- State = PodeStreamState.Closed;
- InputStream.Dispose();
- InputStream = default;
+ if (Socket != default(Socket))
+ {
+ PodeSocket.CloseSocket(Socket);
+ Socket = default;
+ }
+
+ PartialDispose();
}
+ }
- PartialDispose();
- PodeHelpers.WriteErrorMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context);
+ ///
+ /// Disposes of the request and its associated resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
}
}
diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs
index f7acc49cb..13e2fa420 100644
--- a/src/Listener/PodeSignalRequest.cs
+++ b/src/Listener/PodeSignalRequest.cs
@@ -127,18 +127,28 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel
return true;
}
- public override void Dispose()
+ ///
+ /// Dispose managed and unmanaged resources.
+ ///
+ /// Indicates if the method is called explicitly or by garbage collection.
+ protected override void Dispose(bool disposing)
{
- // send close frame
- if (!IsDisposed)
+ if (IsDisposed) return;
+
+ if (disposing)
{
+ // Send close frame
PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context);
+
+ // Wait for the close frame to be sent
Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait();
+
+ // Remove the client signal
+ Context.Listener.Signals.Remove(Signal.ClientId);
}
- // remove client, and dispose
- Context.Listener.Signals.Remove(Signal.ClientId);
- base.Dispose();
+ // Call the base Dispose to clean up other resources
+ base.Dispose(disposing);
}
}
diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs
index 5e30f2893..1c3bfca08 100644
--- a/src/Listener/PodeSmtpRequest.cs
+++ b/src/Listener/PodeSmtpRequest.cs
@@ -50,7 +50,7 @@ public PodeSmtpRequest(Socket socket, PodeSocket podeSocket, PodeContext context
Type = PodeProtocolType.Smtp;
}
- private bool IsCommand(string content, string command)
+ private static bool IsCommand(string content, string command)
{
if (string.IsNullOrWhiteSpace(content))
{
@@ -291,7 +291,7 @@ public void Reset()
Attachments = new List();
}
- private string ParseEmail(string value)
+ private static string ParseEmail(string value)
{
var parts = value.Split(':');
if (parts.Length > 1)
@@ -364,7 +364,7 @@ private Hashtable ParseHeaders(string value)
return headers;
}
- private bool IsBodyValid(string value)
+ private static bool IsBodyValid(string value)
{
var lines = value.Split(new string[] { PodeHelpers.NEW_LINE }, StringSplitOptions.None);
return Array.LastIndexOf(lines, ".") > -1;
@@ -423,7 +423,7 @@ private void ParseBoundary()
}
}
- private string ParseBody(string value, string boundary = null)
+ private static string ParseBody(string value, string boundary = null)
{
// split the message up
var lines = value.Split(new string[] { PodeHelpers.NEW_LINE }, StringSplitOptions.None);
@@ -455,7 +455,7 @@ private string ParseBody(string value, string boundary = null)
return body;
}
- private byte[] ConvertBodyEncoding(string body, string contentEncoding)
+ private static byte[] ConvertBodyEncoding(string body, string contentEncoding)
{
switch (contentEncoding.ToLowerInvariant())
{
@@ -476,7 +476,7 @@ private byte[] ConvertBodyEncoding(string body, string contentEncoding)
}
}
- private string ConvertBodyType(byte[] bytes, string contentType)
+ private static string ConvertBodyType(byte[] bytes, string contentType)
{
if (bytes == default(byte[]) || bytes.Length == 0)
{
@@ -516,20 +516,33 @@ private string ConvertBodyType(byte[] bytes, string contentType)
}
}
- public override void Dispose()
+ ///
+ /// Dispose managed and unmanaged resources.
+ ///
+ /// Indicates if the method is called explicitly or by garbage collection.
+ protected override void Dispose(bool disposing)
{
- RawBody = default;
- Body = string.Empty;
-
- if (Attachments != default(List))
+ if (IsDisposed) return;
+
+ if (disposing)
{
- foreach (var attachment in Attachments)
+ // Custom cleanup logic for PodeSmtpRequest
+ RawBody = default;
+ Body = string.Empty;
+
+ if (Attachments != null)
{
- attachment.Dispose();
+ foreach (var attachment in Attachments)
+ {
+ attachment.Dispose();
+ }
+
+ Attachments = null;
}
}
- base.Dispose();
+ // Call the base Dispose to clean up other resources
+ base.Dispose(disposing);
}
}
}
\ No newline at end of file
diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs
index c48982538..debd03b94 100644
--- a/src/Listener/PodeTcpRequest.cs
+++ b/src/Listener/PodeTcpRequest.cs
@@ -76,11 +76,23 @@ public void Close()
Context.Dispose(true);
}
- public override void Dispose()
+ ///
+ /// Dispose managed and unmanaged resources.
+ ///
+ /// Indicates if the method is called explicitly or by garbage collection.
+ protected override void Dispose(bool disposing)
{
- RawBody = default;
- _body = string.Empty;
- base.Dispose();
+ if (IsDisposed) return;
+ if (disposing)
+ {
+ // Custom cleanup logic for PodeTcpRequest
+ RawBody = default;
+ _body = string.Empty;
+ }
+
+ // Call the base Dispose to clean up other resources
+ base.Dispose(disposing);
}
+
}
}
\ No newline at end of file