Skip to content

Commit bd911ea

Browse files
gscatgscat
authored andcommitted
wip
1 parent b05c612 commit bd911ea

File tree

10 files changed

+340
-198
lines changed

10 files changed

+340
-198
lines changed

cadente/Sisk.Cadente.CoreEngine/CadenteHttpServerEngineResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal void SetContext ( CadenteHttpServerEngineContext context ) {
3636
public CadenteHttpServerEngineResponse ( HttpHostContext.HttpResponse response, HttpHostContext httpHostContext ) {
3737
_response = response;
3838
_httpHostContext = httpHostContext;
39-
_outputStream = new Lazy<Stream> ( () => _response.GetResponseStream ( SendChunked ) );
39+
_outputStream = new Lazy<Stream> ( () => _response.GetResponseStreamAsync ( SendChunked ).ConfigureAwait ( false ).GetAwaiter ().GetResult () );
4040
_headers = new CadenteEngineHeaderList ( _response );
4141
}
4242

cadente/Sisk.Cadente/HttpConnection.cs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
// File name: HttpConnection.cs
88
// Repository: https://github.com/sisk-http/core
99

10+
using System.Buffers;
1011
using System.Net;
12+
using System.Runtime.CompilerServices;
1113
using Sisk.Cadente.HttpSerializer;
1214
using Sisk.Core.Http;
1315

@@ -16,7 +18,6 @@ namespace Sisk.Cadente;
1618
sealed class HttpConnection : IDisposable {
1719
private readonly HttpHost _host;
1820
private readonly IPEndPoint _endpoint;
19-
private readonly Stream _connectionStream;
2021
private readonly HttpHostClient _client;
2122
private bool disposedValue;
2223

@@ -27,29 +28,34 @@ sealed class HttpConnection : IDisposable {
2728
#endif
2829

2930
// buffer dedicated to headers.
30-
public const int REQUEST_BUFFER_SIZE = 8192;
31-
public const int RESPONSE_BUFFER_SIZE = 4096;
31+
public const int RESERVED_BUFFER_SIZE = 8192;
32+
33+
internal readonly Stream networkStream;
34+
internal byte [] sharedPool;
3235

3336
public HttpConnection ( HttpHostClient client, Stream connectionStream, HttpHost host, IPEndPoint endpoint ) {
3437
_client = client;
35-
_connectionStream = connectionStream;
3638
_host = host;
3739
_endpoint = endpoint;
40+
41+
networkStream = connectionStream;
42+
sharedPool = ArrayPool<byte>.Shared.Rent ( RESERVED_BUFFER_SIZE );
3843
}
3944

45+
[MethodImpl ( MethodImplOptions.AggressiveOptimization )]
4046
public async Task<HttpConnectionState> HandleConnectionEventsAsync () {
4147
bool connectionCloseRequested = false;
4248

43-
while (_connectionStream.CanRead && !disposedValue) {
49+
while (!disposedValue) {
4450

45-
HttpRequestReader requestReader = new HttpRequestReader ( _connectionStream );
51+
using HttpRequestBase? nextRequest = await HttpRequestReader.TryReadHttpRequestAsync ( networkStream ).ConfigureAwait ( false );
4652

47-
if (requestReader.TryReadHttpRequest ( out HttpRequestBase? nextRequest ) == false) {
53+
if (nextRequest is null) {
4854
return HttpConnectionState.ConnectionClosed;
4955
}
5056

51-
HttpHostContext managedSession = new HttpHostContext ( _host, nextRequest, _client, _connectionStream );
52-
await _host.InvokeContextCreated ( managedSession );
57+
HttpHostContext managedSession = new HttpHostContext ( _host, this, nextRequest, _client );
58+
await _host.InvokeContextCreated ( managedSession ).ConfigureAwait ( false );
5359

5460
if (!managedSession.KeepAlive || !nextRequest.CanKeepAlive) {
5561
connectionCloseRequested = true;
@@ -59,11 +65,11 @@ public async Task<HttpConnectionState> HandleConnectionEventsAsync () {
5965
if (managedSession.ResponseHeadersAlreadySent == false) {
6066
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.ContentLength, "0" ) );
6167

62-
if (!managedSession.WriteHttpResponseHeaders ())
68+
if (!await managedSession.WriteHttpResponseHeadersAsync ())
6369
return HttpConnectionState.ResponseWriteException;
6470
}
6571

66-
await _connectionStream.FlushAsync ();
72+
await networkStream.FlushAsync ().ConfigureAwait ( false );
6773

6874
if (connectionCloseRequested) {
6975
break;
@@ -76,7 +82,8 @@ public async Task<HttpConnectionState> HandleConnectionEventsAsync () {
7682
private void Dispose ( bool disposing ) {
7783
if (!disposedValue) {
7884
if (disposing) {
79-
_connectionStream.Dispose ();
85+
networkStream.Dispose ();
86+
ArrayPool<byte>.Shared.Return ( sharedPool );
8087
}
8188

8289
disposedValue = true;

cadente/Sisk.Cadente/HttpHost.cs

Lines changed: 17 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,10 @@ public sealed class HttpHost : IDisposable {
2323

2424
private readonly IPEndPoint _endpoint;
2525
private readonly TcpListener _listener;
26+
private Thread? _eventLoopThread;
2627
private bool disposedValue;
2728
private bool isListening;
2829

29-
/// <summary>
30-
/// Gets or sets the client queue size of all <see cref="HttpHost"/> instances. This value indicates how many
31-
/// connections the server can maintain simultaneously before queueing other connections attempts.
32-
/// </summary>
33-
public static int QueueSize { get; set; } = 1024;
34-
3530
/// <summary>
3631
/// Gets or sets the name of the server in the header name.
3732
/// </summary>
@@ -90,19 +85,30 @@ public void Start () {
9085

9186
_listener.Server.NoDelay = true;
9287
_listener.Server.LingerState = new LingerOption ( true, 3 );
93-
_listener.Server.ReceiveBufferSize = HttpConnection.REQUEST_BUFFER_SIZE;
94-
_listener.Server.SendBufferSize = HttpConnection.RESPONSE_BUFFER_SIZE;
88+
_listener.Server.ReceiveBufferSize = HttpConnection.RESERVED_BUFFER_SIZE;
89+
_listener.Server.SendBufferSize = HttpConnection.RESERVED_BUFFER_SIZE;
9590

9691
_listener.Server.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 1 );
9792
_listener.Server.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 120 );
9893
_listener.Server.SetSocketOption ( SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 3 );
9994
_listener.Server.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true );
10095
_listener.Server.SetSocketOption ( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true );
10196

102-
_listener.Start ( QueueSize );
97+
_listener.Start ( backlog: 8192 );
10398
isListening = true;
10499

105-
_listener.BeginAcceptTcpClient ( ReceiveClient, null );
100+
_eventLoopThread = new Thread ( EventLoopThreadRunner );
101+
_eventLoopThread.Priority = ThreadPriority.Highest;
102+
_eventLoopThread.Start ();
103+
}
104+
105+
async void EventLoopThreadRunner () {
106+
107+
while (isListening) {
108+
var client = await _listener.AcceptTcpClientAsync ().ConfigureAwait ( false );
109+
HttpHostThreadPoolWorkItem workItem = new HttpHostThreadPoolWorkItem ( this, client );
110+
ThreadPool.UnsafeQueueUserWorkItem ( workItem, preferLocal: true );
111+
}
106112
}
107113

108114
/// <summary>
@@ -116,101 +122,6 @@ public void Stop () {
116122
_listener.Stop ();
117123
}
118124

119-
private async void ReceiveClient ( IAsyncResult result ) {
120-
121-
if (!isListening)
122-
return;
123-
124-
_listener.BeginAcceptTcpClient ( ReceiveClient, null );
125-
var client = _listener.EndAcceptTcpClient ( result );
126-
127-
await HandleTcpClient ( client );
128-
}
129-
130-
private byte[] GetBadRequestMessage ( string message ) {
131-
string content = $"""
132-
<HTML>
133-
<HEAD>
134-
<TITLE>400 - Bad Request</TITLE>
135-
</HEAD>
136-
<BODY>
137-
<H1>400 - Bad Request</H1>
138-
<P>{message}</P>
139-
<HR>
140-
<P><EM>Cadente</EM></P>
141-
</BODY>
142-
</HTML>
143-
""";
144-
145-
string html =
146-
$"HTTP/1.1 400 Bad Request\r\n" +
147-
$"Content-Type: text/html\r\n" +
148-
$"Content-Length: {content.Length}\r\n" +
149-
$"Connection: close\r\n" +
150-
$"\r\n" +
151-
content;
152-
153-
return Encoding.ASCII.GetBytes ( html );
154-
}
155-
156-
private async Task HandleTcpClient ( TcpClient client ) {
157-
158-
try {
159-
int clientReadTimeoutMs = (int) TimeoutManager.ClientReadTimeout.TotalMilliseconds;
160-
int clientWriteTimeoutMs = (int) TimeoutManager.ClientWriteTimeout.TotalMilliseconds;
161-
162-
client.ReceiveTimeout = clientReadTimeoutMs;
163-
client.SendTimeout = clientWriteTimeoutMs;
164-
165-
if (Handler is null)
166-
return;
167-
168-
Stream connectionStream;
169-
using Stream clientStream = client.GetStream ();
170-
171-
if (HttpsOptions is not null) {
172-
connectionStream = new SslStream ( clientStream, leaveInnerStreamOpen: false );
173-
}
174-
else {
175-
connectionStream = clientStream;
176-
}
177-
178-
IPEndPoint clientEndpoint = (IPEndPoint) client.Client.RemoteEndPoint!;
179-
HttpHostClient hostClient = new HttpHostClient ( clientEndpoint, CancellationToken.None );
180-
181-
connectionStream.ReadTimeout = clientReadTimeoutMs;
182-
connectionStream.WriteTimeout = clientWriteTimeoutMs;
183-
184-
using (HttpConnection connection = new HttpConnection ( hostClient, connectionStream, this, clientEndpoint )) {
185-
186-
if (connectionStream is SslStream sslStream) {
187-
try {
188-
await sslStream.AuthenticateAsServerAsync (
189-
serverCertificate: HttpsOptions!.ServerCertificate,
190-
clientCertificateRequired: HttpsOptions.ClientCertificateRequired,
191-
checkCertificateRevocation: HttpsOptions.CheckCertificateRevocation,
192-
enabledSslProtocols: HttpsOptions.AllowedProtocols );
193-
194-
hostClient.ClientCertificate = sslStream.RemoteCertificate;
195-
}
196-
catch (Exception) {
197-
198-
var message = GetBadRequestMessage ( "SSL/TLS Handshake failed." );
199-
await clientStream.WriteAsync ( message, 0, message.Length );
200-
return;
201-
}
202-
}
203-
204-
await Handler.OnClientConnectedAsync ( this, hostClient );
205-
await connection.HandleConnectionEventsAsync ();
206-
await Handler.OnClientDisconnectedAsync ( this, hostClient );
207-
}
208-
}
209-
finally {
210-
client.Dispose ();
211-
}
212-
}
213-
214125
private void Dispose ( bool disposing ) {
215126
if (!disposedValue) {
216127
if (disposing) {
@@ -224,7 +135,7 @@ private void Dispose ( bool disposing ) {
224135
}
225136

226137
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
227-
internal async ValueTask InvokeContextCreated ( HttpHostContext context ) {
138+
internal async Task InvokeContextCreated ( HttpHostContext context ) {
228139
if (disposedValue)
229140
return;
230141
if (Handler is null)

cadente/Sisk.Cadente/HttpHostContext.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ namespace Sisk.Cadente;
2121
public sealed class HttpHostContext {
2222

2323
private HttpHost _host;
24-
private Stream _connectionStream;
24+
private HttpConnection _connection;
25+
2526
internal CancellationTokenSource abortedSource = new CancellationTokenSource ();
2627
internal bool ResponseHeadersAlreadySent = false;
2728

2829
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
29-
internal bool WriteHttpResponseHeaders () {
30+
internal async ValueTask<bool> WriteHttpResponseHeadersAsync () {
3031
if (ResponseHeadersAlreadySent) {
3132
return true;
3233
}
3334

3435
ResponseHeadersAlreadySent = true;
35-
return HttpResponseSerializer.WriteHttpResponseHeaders ( _connectionStream, Response );
36+
return await HttpResponseSerializer.WriteHttpResponseHeaders ( _connection.sharedPool, _connection.networkStream, Response );
3637
}
3738

3839
/// <summary>
@@ -60,14 +61,14 @@ internal bool WriteHttpResponseHeaders () {
6061
/// </summary>
6162
public HttpHost Host => _host;
6263

63-
internal HttpHostContext ( HttpHost host, HttpRequestBase baseRequest, HttpHostClient client, Stream connectionStream ) {
64+
internal HttpHostContext ( HttpHost host, HttpConnection connection, HttpRequestBase baseRequest, HttpHostClient client ) {
6465
Client = client;
65-
_connectionStream = connectionStream;
66+
_connection = connection;
6667
_host = host;
6768

68-
HttpRequestStream requestStream = new HttpRequestStream ( connectionStream, baseRequest );
69+
HttpRequestStream requestStream = new HttpRequestStream ( _connection.networkStream, baseRequest );
6970
Request = new HttpRequest ( baseRequest, requestStream );
70-
Response = new HttpResponse ( this, connectionStream );
71+
Response = new HttpResponse ( this, _connection.networkStream );
7172
}
7273

7374
/// <summary>
@@ -151,11 +152,11 @@ public sealed class HttpResponse {
151152
public List<HttpHeader> Headers { get; set; }
152153

153154
/// <summary>
154-
/// Gets the content stream for the response.
155+
/// Asynchronously gets the content stream for the response.
155156
/// </summary>
156157
/// <returns>A task that represents the asynchronous operation, with the response content stream as the result.</returns>
157158
/// <exception cref="InvalidOperationException">Thrown when unable to obtain an output stream for the response.</exception>
158-
public Stream GetResponseStream ( bool chunked = false ) {
159+
public async ValueTask<Stream> GetResponseStreamAsync ( bool chunked = false ) {
159160
if (headersSent) {
160161
throw new InvalidOperationException ( "Headers already sent for this HTTP response." );
161162
}
@@ -172,14 +173,14 @@ public Stream GetResponseStream ( bool chunked = false ) {
172173
}
173174
}
174175

175-
if (!_session.WriteHttpResponseHeaders ()) {
176+
if (!await _session.WriteHttpResponseHeadersAsync ()) {
176177
throw new InvalidOperationException ( "Unable to obtain an output stream for the response." );
177178
}
178179

179180
headersSent = true;
180181
return chunked switch {
181182
true => new HttpChunkedWriteStream ( _baseOutputStream ),
182-
false => _baseOutputStream
183+
false => new UndisposableNetworkStream ( _baseOutputStream )
183184
};
184185
}
185186

0 commit comments

Comments
 (0)