-
Notifications
You must be signed in to change notification settings - Fork 244
/
Copy pathBenchmarkApp.cs
198 lines (152 loc) · 6.88 KB
/
BenchmarkApp.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
namespace Kestrel;
public sealed partial class BenchmarkApp : IHttpApplication<IFeatureCollection>
{
private const string TextPlainContentType = "text/plain";
private const string JsonContentTypeWithCharset = "application/json; charset=utf-8";
public IFeatureCollection CreateContext(IFeatureCollection features) => features;
public Task ProcessRequestAsync(IFeatureCollection features)
{
var req = features.GetRequestFeature();
var res = features.GetResponseFeature();
//if (req.Method != "GET")
//{
// res.StatusCode = StatusCodes.Status405MethodNotAllowed;
// return Task.CompletedTask;
//}
var pathSpan = req.Path.AsSpan();
if (Paths.IsPath(pathSpan, Paths.Plaintext))
{
return Plaintext(res, features);
}
else if (Paths.IsPath(pathSpan, Paths.Json))
{
return Json(res, features);
}
else if (Paths.IsPath(pathSpan, Paths.JsonString))
{
return JsonString(res, features);
}
else if (Paths.IsPath(pathSpan, Paths.JsonUtf8Bytes))
{
return JsonUtf8Bytes(res, features);
}
else if (Paths.IsPath(pathSpan, Paths.JsonChunked))
{
return JsonChunked(res, features);
}
else if (pathSpan.IsEmpty || Paths.IsPath(pathSpan, Paths.Index))
{
return Index(res, features);
}
return NotFound(res, features);
}
private static Task NotFound(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
}
public void DisposeContext(IFeatureCollection features, Exception? exception) { }
private static ReadOnlySpan<byte> IndexPayload => "Running directly on Kestrel! Navigate to /plaintext and /json to see other endpoints."u8;
private static async Task Index(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = TextPlainContentType;
res.Headers.ContentLength = IndexPayload.Length;
var body = features.GetResponseBodyFeature();
await body.StartAsync();
body.Writer.Write(IndexPayload);
await body.Writer.FlushAsync();
}
private static ReadOnlySpan<byte> HelloWorldPayload => "Hello, World!"u8;
private static Task Plaintext(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = TextPlainContentType;
res.Headers.ContentLength = HelloWorldPayload.Length;
var body = features.GetResponseBodyFeature();
body.Writer.Write(HelloWorldPayload);
return Task.CompletedTask;
}
private static Task JsonChunked(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = JsonContentTypeWithCharset;
var body = features.GetResponseBodyFeature();
return JsonSerializer.SerializeAsync(body.Writer, new JsonMessage { message = "Hello, World!" }, SerializerContext.JsonMessage);
}
private static Task JsonString(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = JsonContentTypeWithCharset;
var message = JsonSerializer.Serialize(new JsonMessage { message = "Hello, World!" }, SerializerContext.JsonMessage);
Span<byte> buffer = stackalloc byte[64];
var length = Encoding.UTF8.GetBytes(message, buffer);
res.Headers.ContentLength = length;
var body = features.GetResponseBodyFeature();
body.Writer.Write(buffer[..length]);
return Task.CompletedTask;
}
private static Task JsonUtf8Bytes(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = JsonContentTypeWithCharset;
var messageBytes = JsonSerializer.SerializeToUtf8Bytes(new JsonMessage { message = "Hello, World!" }, SerializerContext.JsonMessage);
res.Headers.ContentLength = messageBytes.Length;
var body = features.GetResponseBodyFeature();
body.Writer.Write(messageBytes);
return Task.CompletedTask;
}
private static Task Json(IHttpResponseFeature res, IFeatureCollection features)
{
res.StatusCode = StatusCodes.Status200OK;
res.Headers.ContentType = JsonContentTypeWithCharset;
var messageSpan = JsonSerializeToUtf8Span(new JsonMessage { message = "Hello, World!" }, SerializerContext.JsonMessage);
res.Headers.ContentLength = messageSpan.Length;
var body = features.GetResponseBodyFeature();
body.Writer.Write(messageSpan);
return Task.CompletedTask;
}
[ThreadStatic]
private static ArrayBufferWriter<byte>? _bufferWriter;
[ThreadStatic]
private static Utf8JsonWriter? _jsonWriter;
private static ReadOnlySpan<byte> JsonSerializeToUtf8Span<T>(T value, JsonTypeInfo<T> jsonTypeInfo)
{
var bufferWriter = _bufferWriter ??= new(64);
var jsonWriter = _jsonWriter ??= new(_bufferWriter, new() { Indented = false, SkipValidation = true });
bufferWriter.ResetWrittenCount();
jsonWriter.Reset(bufferWriter);
JsonSerializer.Serialize(jsonWriter, value, jsonTypeInfo);
return bufferWriter.WrittenSpan;
}
private struct JsonMessage
{
public required string message { get; set; }
}
private static readonly JsonContext SerializerContext = JsonContext.Default;
// BUG: Can't use GenerationMode = JsonSourceGenerationMode.Serialization here due to https://github.com/dotnet/runtime/issues/111477
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Default)]
[JsonSerializable(typeof(JsonMessage))]
private partial class JsonContext : JsonSerializerContext
{
}
private static class Paths
{
public static ReadOnlySpan<char> Plaintext => "/plaintext";
public static ReadOnlySpan<char> Json => "/json";
public static ReadOnlySpan<char> JsonString => "/json-string";
public static ReadOnlySpan<char> JsonChunked => "/json-chunked";
public static ReadOnlySpan<char> JsonUtf8Bytes => "/json-utf8bytes";
public static ReadOnlySpan<char> Index => "/";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPath(ReadOnlySpan<char> path, ReadOnlySpan<char> targetPath) => path.SequenceEqual(targetPath);
}
}