Skip to content

Commit 7cc529a

Browse files
committed
feat: add event listeners for connection context
1 parent b688417 commit 7cc529a

File tree

4 files changed

+120
-3
lines changed

4 files changed

+120
-3
lines changed

cadente/Sisk.Cadente/HttpConnection.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ sealed class HttpConnection : IDisposable {
1818
private readonly HttpHost _host;
1919
private readonly IPEndPoint _endpoint;
2020
private readonly Stream _connectionStream;
21+
private readonly HttpHostClientContext _clientContext;
2122
private bool disposedValue;
2223

2324
#if DEBUG
@@ -30,7 +31,8 @@ sealed class HttpConnection : IDisposable {
3031
public const int REQUEST_BUFFER_SIZE = 8192;
3132
public const int RESPONSE_BUFFER_SIZE = 4096;
3233

33-
public HttpConnection ( Stream connectionStream, HttpHost host, IPEndPoint endpoint ) {
34+
public HttpConnection ( HttpHostClientContext clientContext, Stream connectionStream, HttpHost host, IPEndPoint endpoint ) {
35+
this._clientContext = clientContext;
3436
this._connectionStream = connectionStream;
3537
this._host = host;
3638
this._endpoint = endpoint;
@@ -50,6 +52,7 @@ public async Task<HttpConnectionState> HandleConnectionEvents () {
5052
}
5153

5254
HttpHostContext managedSession = new HttpHostContext ( nextRequest, this._endpoint, this._connectionStream );
55+
await this._clientContext.InvokeContextCreated ( managedSession );
5356
await this._host.InvokeContextCreated ( managedSession );
5457

5558
if (!managedSession.KeepAlive || !nextRequest.CanKeepAlive) {

cadente/Sisk.Cadente/HttpContextHandler.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ namespace Sisk.Cadente;
1414
/// </summary>
1515
/// <param name="sender">The <see cref="HttpHost"/> which created the <see cref="HttpHostContext"/> object.</param>
1616
/// <param name="session">The HTTP session associated with the action.</param>
17-
public delegate Task HttpContextHandler ( HttpHost sender, HttpHostContext session );
17+
public delegate Task HttpContextHandler ( HttpHost sender, HttpHostContext session );
18+
19+
/// <summary>
20+
/// Represents a method that takes an <see cref="HttpHostClientContext"/> as a parameter and does not return a value.
21+
/// </summary>
22+
/// <param name="sender">The <see cref="HttpHost"/> which created the <see cref="HttpHostContext"/> object.</param>
23+
/// <param name="context">The HTTP context associated with the action.</param>
24+
public delegate Task HttpClientContextHandler ( HttpHost sender, HttpHostClientContext context );

cadente/Sisk.Cadente/HttpHost.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public sealed class HttpHost : IDisposable {
3333
/// </summary>
3434
public event HttpContextHandler? ContextCreated;
3535

36+
/// <summary>
37+
/// Gets or sets the action handler for incoming HTTP clients.
38+
/// </summary>
39+
public event HttpClientContextHandler? ClientCreated;
40+
3641
/// <summary>
3742
/// Gets a value indicating whether this <see cref="HttpHost"/> has been disposed.
3843
/// </summary>
@@ -108,7 +113,10 @@ private async Task HandleTcpClient ( TcpClient client ) {
108113
connectionStream = clientStream;
109114
}
110115

111-
using (HttpConnection connection = new HttpConnection ( connectionStream, this, (IPEndPoint) client.Client.RemoteEndPoint! )) {
116+
IPEndPoint clientEndpoint = (IPEndPoint) client.Client.RemoteEndPoint!;
117+
using HttpHostClientContext clientContext = new HttpHostClientContext ( client, this, clientEndpoint );
118+
119+
using (HttpConnection connection = new HttpConnection ( clientContext, connectionStream, this, clientEndpoint )) {
112120

113121
if (connectionStream is SslStream sslStream) {
114122
try {
@@ -117,6 +125,8 @@ await sslStream.AuthenticateAsServerAsync (
117125
clientCertificateRequired: this.HttpsOptions.ClientCertificateRequired,
118126
checkCertificateRevocation: this.HttpsOptions.CheckCertificateRevocation,
119127
enabledSslProtocols: this.HttpsOptions.AllowedProtocols );
128+
129+
clientContext.RemoteCertificate = sslStream.RemoteCertificate;
120130
}
121131
catch (Exception) {
122132
return;
@@ -144,6 +154,8 @@ private void Dispose ( bool disposing ) {
144154

145155
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
146156
internal async ValueTask InvokeContextCreated ( HttpHostContext context ) {
157+
if (!this.disposedValue)
158+
return;
147159
if (ContextCreated != null)
148160
await ContextCreated.Invoke ( this, context );
149161
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// The Sisk Framework source code
2+
// Copyright (c) 2024- PROJECT PRINCIPIUM and all Sisk contributors
3+
//
4+
// The code below is licensed under the MIT license as
5+
// of the date of its publication, available at
6+
//
7+
// File name: HttpHostClientContext.cs
8+
// Repository: https://github.com/sisk-http/core
9+
10+
using System.Net;
11+
using System.Net.Sockets;
12+
using System.Runtime.CompilerServices;
13+
using System.Security.Cryptography.X509Certificates;
14+
15+
namespace Sisk.Cadente;
16+
17+
/// <summary>
18+
/// Represents an HTTP connection state that manages the entire client connection.
19+
/// </summary>
20+
public sealed class HttpHostClientContext : IDisposable {
21+
22+
private TcpClient client;
23+
private AutoResetEvent disposedEvent = new AutoResetEvent ( false );
24+
private HttpHost baseHost;
25+
bool ended = false;
26+
27+
/// <summary>
28+
/// Gets the remote address of the connected client.
29+
/// </summary>
30+
public IPEndPoint RemoteAddress { get; }
31+
32+
/// <summary>
33+
/// Gets the remote certificate of the connected client if authenticated with an SSL connection.
34+
/// </summary>
35+
public X509Certificate? RemoteCertificate { get; internal set; }
36+
37+
/// <summary>
38+
/// Gets or sets the action handler for incoming HTTP requests within this client context.
39+
/// </summary>
40+
public event HttpContextHandler? ContextCreated;
41+
42+
/// <summary>
43+
/// Closes the client connection.
44+
/// </summary>
45+
46+
// Note: This method shouldn't be called on the dispose. The HttpHost.HandleTcpClient method should
47+
// dispose the TcpClient instead.
48+
public void Close () {
49+
this.client.Close ();
50+
this.ended = true;
51+
}
52+
53+
/// <summary>
54+
/// Blocks the current thread until this <see cref="HttpHostClientContext"/> connection
55+
/// is terminated.
56+
/// </summary>
57+
public bool WaitUntilClose () {
58+
return this.disposedEvent.WaitOne ();
59+
}
60+
61+
/// <summary>
62+
/// Blocks the current thread until this <see cref="HttpHostClientContext"/> connection
63+
/// is terminated, using a maximum timeout interval.
64+
/// </summary>
65+
/// <param name="timeout">Defines the maximum time waiting for the connection termination.</param>
66+
/// <param name="closeOnTimeout">Defines if this client context should terminate the client connection if the timeout is reached.</param>
67+
public bool WaitUntilClose ( TimeSpan timeout, bool closeOnTimeout = false ) {
68+
var b = this.disposedEvent.WaitOne ( timeout );
69+
if (!b && closeOnTimeout)
70+
this.Close ();
71+
72+
return b;
73+
}
74+
75+
internal HttpHostClientContext ( TcpClient client, HttpHost baseHost, IPEndPoint remoteAddress ) {
76+
this.baseHost = baseHost;
77+
this.RemoteAddress = remoteAddress;
78+
this.client = client;
79+
}
80+
81+
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
82+
internal async ValueTask InvokeContextCreated ( HttpHostContext context ) {
83+
if (this.ended)
84+
return;
85+
if (ContextCreated != null)
86+
await ContextCreated.Invoke ( this.baseHost, context );
87+
}
88+
89+
/// <inheritdoc/>
90+
public void Dispose () {
91+
this.ended = true;
92+
this.disposedEvent.Set ();
93+
this.disposedEvent.Dispose ();
94+
}
95+
}

0 commit comments

Comments
 (0)