Skip to content

Commit fac1e9e

Browse files
committed
Add usage samples.
Add prepared headers.
1 parent 7ab3682 commit fac1e9e

12 files changed

+336
-3
lines changed

HttpPrimitives/Program.cs renamed to NetworkToolkit.Examples/Program.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ namespace HttpPrimitives
1111
class Program
1212
{
1313
static async Task Main(string[] args)
14+
{
15+
await RequestSimpleAsync();
16+
await PreparedHeadersAsync();
17+
}
18+
19+
static async Task RequestSimpleAsync()
1420
{
1521
await using ConnectionFactory connectionFactory = new SocketConnectionFactory();
1622
await using Connection connection = await connectionFactory.ConnectAsync(new DnsEndPoint("microsoft.com", 80));
@@ -69,6 +75,35 @@ static async Task Main(string[] args)
6975
}
7076
}
7177

78+
static async Task PreparedHeadersAsync()
79+
{
80+
PreparedHeaderSet preparedHeaders =
81+
new PreparedHeaderSetBuilder()
82+
.AddHeader("User-Agent", "NetworkToolkit")
83+
.AddHeader("Accept", "text/html")
84+
.Build();
85+
86+
await using ConnectionFactory connectionFactory = new SocketConnectionFactory();
87+
await using Connection connection = await connectionFactory.ConnectAsync(new DnsEndPoint("microsoft.com", 80));
88+
await using HttpConnection httpConnection = new Http1Connection(connection);
89+
90+
int requestCounter = 0;
91+
await SingleRequest();
92+
await SingleRequest();
93+
94+
async Task SingleRequest()
95+
{
96+
await using ValueHttpRequest request = (await httpConnection.CreateNewRequestAsync(HttpPrimitiveVersion.Version11, HttpVersionPolicy.RequestVersionExact)).Value;
97+
98+
request.ConfigureRequest(contentLength: 0, hasTrailingHeaders: false);
99+
request.WriteRequest(HttpMethod.Get, new Uri("http://microsoft.com"));
100+
request.WriteHeader(preparedHeaders);
101+
request.WriteHeader("X-Example-RequestNo", requestCounter++.ToString());
102+
await request.CompleteRequestAsync();
103+
await request.DrainAsync();
104+
}
105+
}
106+
72107
sealed class PrintingHeadersSink : IHttpHeadersSink
73108
{
74109
public void OnHeader(object state, ReadOnlySpan<byte> headerName, ReadOnlySpan<byte> headerValue)

NetworkToolkit.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkToolkit", "NetworkTo
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{8E172989-D93E-4746-A1A0-39978742AE5F}"
99
EndProject
10-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpPrimitives", "HttpPrimitives\HttpPrimitives.csproj", "{72B32DEA-6CEA-476F-9A38-B3C89A028127}"
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkToolkit.Examples", "NetworkToolkit.Examples\NetworkToolkit.Examples.csproj", "{72B32DEA-6CEA-476F-9A38-B3C89A028127}"
1111
EndProject
1212
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1E40D58A-9500-4F40-AED4-39078E5B6C55}"
1313
EndProject

NetworkToolkit/Http/Primitives/Http1Connection.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,30 @@ internal void WriteHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
276276
_writeBuffer.Commit(len);
277277
}
278278

279+
internal void WriteHeader(PreparedHeaderSet headers)
280+
{
281+
if (headers == null) throw new ArgumentNullException(nameof(headers));
282+
283+
switch (_writeState)
284+
{
285+
case WriteState.Unstarted:
286+
throw new InvalidOperationException($"Must call {nameof(ValueHttpRequest)}.{nameof(HttpRequest.WriteRequest)} prior to writing headers.");
287+
case WriteState.RequestWritten:
288+
_writeState = WriteState.HeadersWritten;
289+
break;
290+
case WriteState.HeadersWritten:
291+
break;
292+
default:
293+
throw new InvalidOperationException("Can not write headers after headers have been flushed.");
294+
}
295+
296+
byte[] headersBuffer = headers.Http1Value;
297+
298+
_writeBuffer.EnsureAvailableSpace(headersBuffer.Length);
299+
headersBuffer.AsSpan().CopyTo(_writeBuffer.AvailableSpan);
300+
_writeBuffer.Commit(headersBuffer.Length);
301+
}
302+
279303
internal void WriteTrailingHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
280304
{
281305
if (!_requestIsChunked)
@@ -321,7 +345,7 @@ internal static int GetEncodeHeaderLength(ReadOnlySpan<byte> name, ReadOnlySpan<
321345
return name.Length + value.Length + 4; // {name}: {value}\r\n
322346
}
323347

324-
internal static unsafe void EncodeHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value, Span<byte> buffer)
348+
internal static unsafe int EncodeHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value, Span<byte> buffer)
325349
{
326350
Debug.Assert(name.Length > 0);
327351
Debug.Assert(buffer.Length >= GetEncodeHeaderLength(name, value));
@@ -345,6 +369,8 @@ internal static unsafe void EncodeHeader(ReadOnlySpan<byte> name, ReadOnlySpan<b
345369

346370
int length = (int)(void*)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(buffer), ref pBuf);
347371
Debug.Assert(length == GetEncodeHeaderLength(name, value));
372+
373+
return length;
348374
}
349375

350376
private void WriteCRLF()

NetworkToolkit/Http/Primitives/Http1Request.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ protected internal override void WriteHeader(int version, ReadOnlySpan<byte> nam
7777
_connection.WriteHeader(name, value);
7878
}
7979

80+
protected internal override void WriteHeader(int version, PreparedHeaderSet headers)
81+
{
82+
ThrowIfDisposed(version);
83+
_connection.WriteHeader(headers);
84+
}
85+
8086
protected internal override void WriteTrailingHeader(int version, ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
8187
{
8288
ThrowIfDisposed(version);

NetworkToolkit/Http/Primitives/HttpRequest.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ public ValueHttpRequest GetValueRequest()
127127
/// <param name="value">The value of the header to write.</param>
128128
protected internal abstract void WriteHeader(int version, ReadOnlySpan<byte> name, ReadOnlySpan<byte> value);
129129

130+
/// <summary>
131+
/// Writes a set of headers.
132+
/// </summary>
133+
/// <param name="version">The version of the request to operate on. This must be validated by implementations.</param>
134+
/// <param name="headers">A set of headers to write to the request.</param>
135+
protected internal abstract void WriteHeader(int version, PreparedHeaderSet headers);
136+
130137
/// <summary>
131138
/// Writes a trailing header.
132139
/// To use, trailing headers must be enabled during <see cref="ConfigureRequest(int, long?, bool)"/>.

NetworkToolkit/Http/Primitives/PooledHttpConnection.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@ protected internal override void WriteHeader(int version, ReadOnlySpan<byte> nam
452452
_request.WriteHeader(name, value);
453453
}
454454

455+
protected internal override void WriteHeader(int version, PreparedHeaderSet headers)
456+
{
457+
ThrowIfDisposed(version);
458+
_request.WriteHeader(headers);
459+
}
460+
455461
protected internal override void WriteHeader(int version, string name, string value)
456462
{
457463
ThrowIfDisposed(version);
@@ -487,6 +493,12 @@ protected internal override void WriteRequest(int version, ReadOnlySpan<byte> me
487493
ThrowIfDisposed(version);
488494
_request.WriteRequest(method, scheme, authority, pathAndQuery);
489495
}
496+
497+
protected internal override void WriteRequest(int version, HttpMethod method, Uri uri)
498+
{
499+
ThrowIfDisposed(version);
500+
_request.WriteRequest(method, uri);
501+
}
490502
}
491503
}
492504
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace NetworkToolkit.Http.Primitives
5+
{
6+
/// <summary>
7+
/// A prepared header.
8+
/// </summary>
9+
public sealed class PreparedHeader
10+
{
11+
internal readonly byte[] _headerName, _headerValue;
12+
13+
/// <summary>
14+
/// Instantiates a new <see cref="PreparedHeader"/>.
15+
/// </summary>
16+
/// <param name="headerName">The header's name.</param>
17+
/// <param name="headerValue">The header's value.</param>
18+
public PreparedHeader(ReadOnlySpan<byte> headerName, ReadOnlySpan<byte> headerValue)
19+
{
20+
_headerName = headerName.ToArray();
21+
_headerValue = headerValue.ToArray();
22+
}
23+
24+
/// <summary>
25+
/// Instantiates a new <see cref="PreparedHeader"/>.
26+
/// </summary>
27+
/// <param name="headerName">The header's name.</param>
28+
/// <param name="headerValue">The header's value.</param>
29+
public PreparedHeader(string headerName, string headerValue)
30+
{
31+
_headerName = Encoding.ASCII.GetBytes(headerName);
32+
_headerValue = Encoding.ASCII.GetBytes(headerValue);
33+
}
34+
35+
/// <inheritdoc/>
36+
public override string ToString() => Encoding.ASCII.GetString(_headerName) + ": " + Encoding.ASCII.GetString(_headerValue);
37+
}
38+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace NetworkToolkit.Http.Primitives
9+
{
10+
/// <summary>
11+
/// A set of prepared headers, used to more efficiently write frequently reused headers.
12+
/// </summary>
13+
public sealed class PreparedHeaderSet
14+
{
15+
readonly PreparedHeader[] _headers;
16+
17+
byte[]? _http1Value;
18+
internal byte[] Http1Value => _http1Value ?? GetHttp1ValueSlow();
19+
20+
internal PreparedHeaderSet(List<PreparedHeader> headers)
21+
{
22+
_headers = headers.ToArray();
23+
}
24+
25+
/// <inheritdoc/>
26+
public override string ToString() => Encoding.ASCII.GetString(Http1Value);
27+
28+
private byte[] GetHttp1ValueSlow()
29+
{
30+
int totalLen = 0;
31+
foreach (PreparedHeader header in _headers)
32+
{
33+
int headerLen = Http1Connection.GetEncodeHeaderLength(header._headerName, header._headerValue);
34+
totalLen = checked(totalLen + headerLen);
35+
}
36+
37+
var buffer = new byte[totalLen];
38+
Span<byte> writePos = buffer;
39+
40+
foreach (PreparedHeader header in _headers)
41+
{
42+
int len = Http1Connection.EncodeHeader(header._headerName, header._headerValue, writePos);
43+
writePos = writePos.Slice(len);
44+
}
45+
46+
Volatile.Write(ref _http1Value, buffer);
47+
return buffer;
48+
}
49+
}
50+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace NetworkToolkit.Http.Primitives
6+
{
7+
/// <summary>
8+
/// A builder used to create a <see cref="PreparedHeaderSet"/>.
9+
/// </summary>
10+
public sealed class PreparedHeaderSetBuilder
11+
{
12+
private readonly List<PreparedHeader> _headers = new List<PreparedHeader>();
13+
14+
/// <summary>
15+
/// Adds a header to the builder.
16+
/// </summary>
17+
/// <param name="header">The header to add.</param>
18+
public PreparedHeaderSetBuilder AddHeader(PreparedHeader header)
19+
{
20+
_headers.Add(header);
21+
return this;
22+
}
23+
24+
/// <summary>
25+
/// Adds a header to the builder.
26+
/// </summary>
27+
/// <param name="name">The name of the header to add.</param>
28+
/// <param name="value">The value of the header to add.</param>
29+
public PreparedHeaderSetBuilder AddHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
30+
{
31+
_headers.Add(new PreparedHeader(name, value));
32+
return this;
33+
}
34+
35+
/// <summary>
36+
/// Adds a header to the builder.
37+
/// </summary>
38+
/// <param name="name">The name of the header to add.</param>
39+
/// <param name="value">The value of the header to add.</param>
40+
public PreparedHeaderSetBuilder AddHeader(string name, string value)
41+
{
42+
_headers.Add(new PreparedHeader(name, value));
43+
return this;
44+
}
45+
46+
/// <summary>
47+
/// Builds a <see cref="PreparedHeaderSet"/>.
48+
/// </summary>
49+
/// <returns>A <see cref="PreparedHeaderSet"/> instance representing the given headers</returns>
50+
public PreparedHeaderSet Build() => new PreparedHeaderSet(_headers);
51+
}
52+
}

0 commit comments

Comments
 (0)