Skip to content

Commit 8d66d99

Browse files
committed
wip
1 parent 7edcc4f commit 8d66d99

11 files changed

+182
-100
lines changed

cadente/Sisk.Cadente/HttpConnection.cs

+42-56
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// File name: HttpConnection.cs
88
// Repository: https://github.com/sisk-http/core
99

10-
using System.Buffers;
1110
using System.Net;
1211
using Sisk.Cadente.HttpSerializer;
1312
using Sisk.Cadente.Streams;
@@ -27,7 +26,8 @@ sealed class HttpConnection : IDisposable {
2726
public readonly int Id = 0;
2827
#endif
2928

30-
public const int REQUEST_BUFFER_SIZE = 8192; // buffer dedicated to headers. more than it? return 400.
29+
// buffer dedicated to headers.
30+
public const int REQUEST_BUFFER_SIZE = 8192;
3131
public const int RESPONSE_BUFFER_SIZE = 4096;
3232

3333
public HttpConnection ( Stream connectionStream, HttpHost host, IPEndPoint endpoint ) {
@@ -39,78 +39,64 @@ public HttpConnection ( Stream connectionStream, HttpHost host, IPEndPoint endpo
3939
public async Task<HttpConnectionState> HandleConnectionEvents () {
4040
bool connectionCloseRequested = false;
4141

42-
var requestBuffer = ArrayPool<byte>.Shared.Rent ( REQUEST_BUFFER_SIZE );
42+
while (this._connectionStream.CanRead && !this.disposedValue) {
4343

44-
try {
44+
HttpRequestReader requestReader = new HttpRequestReader ( this._connectionStream );
45+
Stream? responseStream = null;
4546

46-
while (this._connectionStream.CanRead && !this.disposedValue) {
47+
try {
48+
if (requestReader.TryReadHttpRequest ( out HttpRequestBase? nextRequest ) == false) {
49+
return HttpConnectionState.ConnectionClosed;
50+
}
4751

48-
HttpRequestReader requestReader = new HttpRequestReader ( this._connectionStream, ref requestBuffer );
49-
Stream? responseStream = null;
52+
HttpHostContext managedSession = new HttpHostContext ( nextRequest, this._endpoint, this._connectionStream );
53+
await this._host.InvokeContextCreated ( managedSession );
5054

51-
try {
52-
var readRequestState = await requestReader.ReadHttpRequest ();
53-
var nextRequest = readRequestState.Item2;
55+
if (!managedSession.KeepAlive || !nextRequest.CanKeepAlive) {
56+
connectionCloseRequested = true;
57+
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.Connection, "close" ) );
58+
}
5459

55-
if (nextRequest is null) {
56-
return readRequestState.Item1 switch {
57-
HttpRequestReadState.StreamZero => HttpConnectionState.ConnectionClosedByStreamRead,
58-
_ => HttpConnectionState.BadRequest
59-
};
60-
}
60+
if (managedSession.Response.ResponseStream is Stream { } s) {
61+
responseStream = s;
62+
}
63+
else {
64+
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.ContentLength, "0" ) );
65+
}
6166

62-
HttpHostContext managedSession = new HttpHostContext ( nextRequest, this._endpoint, this._connectionStream );
63-
this._host.InvokeContextCreated ( managedSession );
67+
Stream outputStream = this._connectionStream;
68+
if (responseStream is not null) {
6469

65-
if (!managedSession.KeepAlive || !nextRequest.CanKeepAlive) {
66-
connectionCloseRequested = true;
67-
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.Connection, "close" ) );
68-
}
70+
if (managedSession.Response.SendChunked || !responseStream.CanSeek) {
6971

70-
if (managedSession.Response.ResponseStream is Stream { } s) {
71-
responseStream = s;
72+
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.TransferEncoding, "chunked" ) );
73+
responseStream = new HttpChunkedStream ( responseStream );
7274
}
7375
else {
74-
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.ContentLength, "0" ) );
75-
}
76-
77-
Stream outputStream = this._connectionStream;
78-
if (responseStream is not null) {
79-
80-
if (managedSession.Response.SendChunked || !responseStream.CanSeek) {
81-
82-
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.TransferEncoding, "chunked" ) );
83-
responseStream = new HttpChunkedStream ( responseStream );
84-
}
85-
else {
86-
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.ContentLength, responseStream.Length.ToString () ) );
87-
}
76+
managedSession.Response.Headers.Set ( new HttpHeader ( HttpHeaderName.ContentLength, responseStream.Length.ToString () ) );
8877
}
78+
}
8979

90-
if (managedSession.ResponseHeadersAlreadySent == false && !managedSession.WriteHttpResponseHeaders ()) {
91-
return HttpConnectionState.ResponseWriteException;
92-
}
80+
if (managedSession.ResponseHeadersAlreadySent == false && !managedSession.WriteHttpResponseHeaders ()) {
81+
return HttpConnectionState.ResponseWriteException;
82+
}
9383

94-
if (responseStream is not null) {
95-
await responseStream.CopyToAsync ( outputStream );
96-
}
84+
if (responseStream is not null) {
85+
await responseStream.CopyToAsync ( outputStream );
86+
}
9787

98-
this._connectionStream.Flush ();
88+
await this._connectionStream.FlushAsync ();
9989

100-
if (connectionCloseRequested) {
101-
break;
102-
}
103-
}
104-
finally {
105-
responseStream?.Dispose ();
90+
if (connectionCloseRequested) {
91+
break;
10692
}
10793
}
108-
109-
return HttpConnectionState.ConnectionClosed;
110-
}
111-
finally {
112-
ArrayPool<byte>.Shared.Return ( requestBuffer );
94+
finally {
95+
responseStream?.Dispose ();
96+
}
11397
}
98+
99+
return HttpConnectionState.ConnectionClosed;
114100
}
115101

116102
private void Dispose ( bool disposing ) {

cadente/Sisk.Cadente/HttpContextHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ 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 void HttpContextHandler ( HttpHost sender, HttpHostContext session );
17+
public delegate Task HttpContextHandler ( HttpHost sender, HttpHostContext session );

cadente/Sisk.Cadente/HttpHost.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ private void Dispose ( bool disposing ) {
143143
}
144144

145145
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
146-
internal void InvokeContextCreated ( HttpHostContext context ) {
147-
this.ContextCreated?.Invoke ( this, context );
146+
internal async ValueTask InvokeContextCreated ( HttpHostContext context ) {
147+
if (ContextCreated != null)
148+
await ContextCreated.Invoke ( this, context );
148149
}
149150

150151
/// <inheritdoc/>

cadente/Sisk.Cadente/HttpHostContext.cs

+6
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ internal bool WriteHttpResponseHeaders () {
4848
/// </summary>
4949
public bool KeepAlive { get; set; } = true;
5050

51+
/// <summary>
52+
/// Gets the remote client endpoint.
53+
/// </summary>
54+
public IPEndPoint ClientEndpoint { get; }
55+
5156
internal HttpHostContext ( HttpRequestBase baseRequest, IPEndPoint clientEndpoint, Stream connectionStream ) {
57+
this.ClientEndpoint = clientEndpoint;
5258
this._connectionStream = connectionStream;
5359

5460
HttpRequestStream requestStream = new HttpRequestStream ( connectionStream, baseRequest );

cadente/Sisk.Cadente/HttpSerializer/HttpRequestReader.cs

+13-17
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
// File name: HttpRequestReader.cs
88
// Repository: https://github.com/sisk-http/core
99

10-
using System.Buffers;
10+
using System.Diagnostics.CodeAnalysis;
1111
using System.Text;
1212

1313
namespace Sisk.Cadente.HttpSerializer;
1414

1515
sealed class HttpRequestReader {
1616

1717
Stream _stream;
18-
Memory<byte> buffer;
1918

2019
const byte SPACE = (byte) ' ';
2120
const byte LINE_FEED = (byte) '\n';
@@ -24,40 +23,37 @@ sealed class HttpRequestReader {
2423
private static ReadOnlySpan<byte> RequestLineDelimiters => [ LINE_FEED, 0 ];
2524
private static ReadOnlySpan<byte> RequestHeaderLineSpaceDelimiters => [ SPACE, 0 ];
2625

27-
public HttpRequestReader ( Stream stream, ref byte [] bufferOwnership ) {
26+
public HttpRequestReader ( Stream stream ) {
2827
this._stream = stream;
29-
this.buffer = bufferOwnership;
3028
}
3129

32-
public async Task<(HttpRequestReadState, HttpRequestBase?)> ReadHttpRequest () {
30+
public bool TryReadHttpRequest ( [NotNullWhen ( true )] out HttpRequestBase? request ) {
3331
try {
34-
int read = await this._stream.ReadAsync ( this.buffer );
3532

36-
if (read == 0) {
37-
return (HttpRequestReadState.StreamZero, null);
38-
}
33+
Span<byte> buffer = stackalloc byte [ HttpConnection.REQUEST_BUFFER_SIZE ];
34+
int read = this._stream.Read ( buffer );
3935

40-
var request = this.ParseHttpRequest ( read );
41-
return (HttpRequestReadState.RequestRead, request);
36+
request = this.ParseHttpRequest ( buffer [ ..read ] );
37+
return request != null;
4238
}
4339
catch (Exception ex) {
4440
Logger.LogInformation ( $"HttpRequestReader finished with exception: {ex.Message}" );
45-
return (HttpRequestReadState.StreamError, null);
41+
request = null;
42+
return false;
4643
}
4744
}
4845

49-
HttpRequestBase? ParseHttpRequest ( int length ) {
46+
HttpRequestBase? ParseHttpRequest ( scoped ReadOnlySpan<byte> buffer ) {
5047

51-
ReadOnlyMemory<byte> bufferPart = this.buffer [ 0..length ];
52-
SequenceReader<byte> reader = new SequenceReader<byte> ( new ReadOnlySequence<byte> ( bufferPart ) );
48+
SpanReader<byte> reader = new SpanReader<byte> ( buffer );
5349

5450
if (!reader.TryReadToAny ( out ReadOnlySpan<byte> method, RequestHeaderLineSpaceDelimiters, advancePastDelimiter: true )) {
5551
return null;
5652
}
5753
if (!reader.TryReadToAny ( out ReadOnlySpan<byte> path, RequestHeaderLineSpaceDelimiters, advancePastDelimiter: true )) {
5854
return null;
5955
}
60-
if (!reader.TryReadToAny ( out ReadOnlySpan<byte> protocol, RequestHeaderLineSpaceDelimiters, advancePastDelimiter: true )) {
56+
if (!reader.TryReadToAny ( out ReadOnlySpan<byte> protocol, RequestLineDelimiters, advancePastDelimiter: true )) {
6157
return null;
6258
}
6359

@@ -93,7 +89,7 @@ public HttpRequestReader ( Stream stream, ref byte [] bufferOwnership ) {
9389
}
9490

9591
return new HttpRequestBase () {
96-
BufferedContent = expect100 ? Memory<byte>.Empty : bufferPart [ (int) reader.Consumed.. ],
92+
BufferedContent = expect100 ? Memory<byte>.Empty : buffer [ reader.Consumed.. ].ToArray (),
9793

9894
Headers = headers.ToArray (),
9995
MethodRef = method.ToArray (),

cadente/Sisk.Cadente/HttpSerializer/HttpResponseSerializer.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Sisk.Cadente.HttpSerializer;
1515

1616
internal static class HttpResponseSerializer {
1717

18-
static ASCIIEncoding _headerDataEncoding = new ASCIIEncoding ();
18+
static Encoding _headerDataEncoding = Encoding.ASCII;
1919

2020
const byte _H = (byte) 'H';
2121
const byte _T = (byte) 'T';
@@ -32,17 +32,17 @@ internal static class HttpResponseSerializer {
3232
public static int GetResponseHeadersBytes ( scoped Span<byte> buffer, HttpHostContext.HttpResponse response ) {
3333

3434
// HTTP/1.1
35-
buffer [ 0 ] = _H;
36-
buffer [ 1 ] = _T;
37-
buffer [ 2 ] = _T;
38-
buffer [ 3 ] = _P;
39-
buffer [ 4 ] = _SLASH;
40-
buffer [ 5 ] = _1;
41-
buffer [ 6 ] = _DOT;
42-
buffer [ 7 ] = _1;
43-
buffer [ 8 ] = _SPACE;
44-
45-
int position = 9;
35+
int position = 0;
36+
37+
buffer [ position++ ] = _H;
38+
buffer [ position++ ] = _T;
39+
buffer [ position++ ] = _T;
40+
buffer [ position++ ] = _P;
41+
buffer [ position++ ] = _SLASH;
42+
buffer [ position++ ] = _1;
43+
buffer [ position++ ] = _DOT;
44+
buffer [ position++ ] = _1;
45+
buffer [ position++ ] = _SPACE;
4646

4747
int statusCodeCount = _headerDataEncoding.GetBytes ( response.StatusCode.ToString (), buffer [ position.. ] );
4848
position += statusCodeCount;

cadente/Sisk.Cadente/SpanReader.cs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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: SpanReader.cs
8+
// Repository: https://github.com/sisk-http/core
9+
10+
using System.Runtime.CompilerServices;
11+
12+
namespace Sisk.Cadente;
13+
14+
ref struct SpanReader<T> where T : IEquatable<T> {
15+
16+
int readLength = 0;
17+
18+
public ReadOnlySpan<T> UnreadSpan { get => this.Span [ this.readLength.. ]; }
19+
public ReadOnlySpan<T> Span { get; }
20+
21+
public int Consumed { get => this.readLength; }
22+
23+
public SpanReader ( in ReadOnlySpan<T> span ) {
24+
this.Span = span;
25+
}
26+
27+
public bool TryReadToAny ( out ReadOnlySpan<T> result, scoped ReadOnlySpan<T> delimiters, bool advancePastDelimiter = false ) {
28+
29+
ReadOnlySpan<T> remaining = this.UnreadSpan;
30+
31+
int index = delimiters.Length switch {
32+
0 => -1,
33+
2 => remaining.IndexOfAny ( delimiters [ 0 ], delimiters [ 1 ] ),
34+
3 => remaining.IndexOfAny ( delimiters [ 0 ], delimiters [ 1 ], delimiters [ 3 ] ),
35+
_ => remaining.IndexOfAny ( delimiters )
36+
};
37+
38+
if (index != -1) {
39+
result = remaining.Slice ( 0, index );
40+
this.Advance ( index + (advancePastDelimiter ? 1 : 0) );
41+
return true;
42+
}
43+
44+
result = default;
45+
return false;
46+
}
47+
48+
[MethodImpl ( MethodImplOptions.AggressiveInlining )]
49+
public void Advance ( int count ) {
50+
this.readLength += count;
51+
}
52+
}

src/Entity/CrossOriginResourceSharingHeaders.cs

+33-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ namespace Sisk.Core.Entity {
1212
/// Provides a class to provide Cross Origin response headers for when communicating with a browser.
1313
/// </summary>
1414
public sealed class CrossOriginResourceSharingHeaders {
15+
16+
/// <summary>
17+
/// When applied to the <see cref="AllowOrigin"/> property, the HTTP server automatically applies
18+
/// the incoming request Origin header value to the Access-Control-Allow-Origin header.
19+
/// </summary>
20+
public const string AutoAllowOrigin = "<SISK_AUTO_ALLOW_ORIGIN_NAME>";
21+
1522
/// <summary>
1623
/// Gets an instance of an empty CrossOriginResourceSharingHeaders.
1724
/// </summary>
@@ -62,13 +69,38 @@ public sealed class CrossOriginResourceSharingHeaders {
6269
/// </summary>
6370
public CrossOriginResourceSharingHeaders () {
6471
this.ExposeHeaders = Array.Empty<string> ();
65-
this.AllowOrigin = null;
6672
this.AllowOrigins = Array.Empty<string> ();
6773
this.AllowMethods = Array.Empty<string> ();
6874
this.AllowHeaders = Array.Empty<string> ();
75+
this.AllowOrigin = null;
6976
this.MaxAge = TimeSpan.Zero;
7077
}
7178

79+
/// <summary>
80+
/// Initializes a new instance of the <see cref="CrossOriginResourceSharingHeaders"/> class with the specified CORS headers.
81+
/// </summary>
82+
/// <param name="allowOrigin">The value of the Access-Control-Allow-Origin header.</param>
83+
/// <param name="allowOrigins">The values of the Access-Control-Allow-Origin header.</param>
84+
/// <param name="allowMethods">The values of the Access-Control-Allow-Methods header.</param>
85+
/// <param name="allowHeaders">The values of the Access-Control-Allow-Headers header.</param>
86+
/// <param name="exposeHeaders">The values of the Access-Control-Expose-Headers header.</param>
87+
/// <param name="maxAge">The value of the Access-Control-Max-Age header.</param>
88+
public CrossOriginResourceSharingHeaders (
89+
string? allowOrigin = null,
90+
string []? allowOrigins = null,
91+
string []? allowMethods = null,
92+
string []? allowHeaders = null,
93+
string []? exposeHeaders = null,
94+
TimeSpan? maxAge = null ) {
95+
96+
this.ExposeHeaders = exposeHeaders ?? Array.Empty<string> ();
97+
this.AllowOrigins = allowOrigins ?? Array.Empty<string> ();
98+
this.AllowHeaders = allowHeaders ?? Array.Empty<string> ();
99+
this.AllowMethods = allowMethods ?? Array.Empty<string> ();
100+
this.AllowOrigin = allowOrigin;
101+
this.MaxAge = maxAge ?? TimeSpan.Zero;
102+
}
103+
72104
/// <summary>
73105
/// Create an instance of Cross-Origin Resource Sharing that allows any origin, any method and any header in the request.
74106
/// </summary>

0 commit comments

Comments
 (0)