|
| 1 | +using BenchmarkDotNet.Attributes; |
| 2 | +using NetworkToolkit.Connections; |
| 3 | +using NetworkToolkit.Http.Primitives; |
| 4 | +using System; |
| 5 | +using System.Collections.Generic; |
| 6 | +using System.Net.Http; |
| 7 | +using System.Text; |
| 8 | +using System.Threading; |
| 9 | +using System.Threading.Tasks; |
| 10 | + |
| 11 | +namespace NetworkToolkit.Benchmarks |
| 12 | +{ |
| 13 | + [Config(typeof(NetworkToolkitConfig))] |
| 14 | + public class SerializedGet |
| 15 | + { |
| 16 | + private readonly ConnectionFactory _connectionFactory = new MemoryConnectionFactory(); |
| 17 | + |
| 18 | + private ConnectionListener? _connectionListener; |
| 19 | + private SimpleHttp1Server? _server; |
| 20 | + |
| 21 | + private HttpMessageInvoker? _httpClient; |
| 22 | + private Uri? _uri; |
| 23 | + |
| 24 | + private HttpConnection? _primitiveConnection; |
| 25 | + private byte[]? _encodedScheme, _encodedAuthority, _encodedPathAndQuery; |
| 26 | + |
| 27 | + [ParamsSource(nameof(ValuesForRequestHeaders))] |
| 28 | + public RequestHeaderCollection? RequestHeaders { get; set; } |
| 29 | + |
| 30 | + public static IEnumerable<RequestHeaderCollection> ValuesForRequestHeaders => new[] |
| 31 | + { |
| 32 | + new RequestHeaderCollection("Minimal").Build(), |
| 33 | + new RequestHeaderCollection("Normal") |
| 34 | + { |
| 35 | + StaticHeaders = new List<(string, string)> |
| 36 | + { |
| 37 | + ("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"), |
| 38 | + ("accept-encoding", "gzip, deflate, br"), |
| 39 | + ("accept-language", "en-US,en;q=0.9"), |
| 40 | + ("sec-fetch-dest", "document"), |
| 41 | + ("sec-fetch-mode", "navigate"), |
| 42 | + ("sec-fetch-site", "none"), |
| 43 | + ("sec-fetch-user", "?1"), |
| 44 | + ("upgrade-insecure-requests", "1"), |
| 45 | + ("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69") |
| 46 | + }, |
| 47 | + DynamicHeaders = new List<(string, string)> |
| 48 | + { |
| 49 | + ("cookie", "cookie: aaaa=000000000000000000000000000000000000; bbb=111111111111111111111111111; ccccc=22222222222222222222222222; dddddd=333333333333333333333333333333333333333333333333333333333333333333333; eeee=444444444444444444444444444444444444444444444444444444444444444444444444444") |
| 50 | + } |
| 51 | + }.Build() |
| 52 | + }; |
| 53 | + |
| 54 | + [ParamsSource(nameof(ValuesForResponseBytes))] |
| 55 | + public EncodedMessage? ResponseBytes { get; set; } |
| 56 | + |
| 57 | + public static IEnumerable<EncodedMessage> ValuesForResponseBytes => new[] |
| 58 | + { |
| 59 | + new EncodedMessage("Minimal", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"), |
| 60 | + new EncodedMessage("StackOverflow", |
| 61 | + "HTTP/1.1 200 OK\r\n" + |
| 62 | + "Content-Length: 0\r\n" + |
| 63 | + "Accept-Ranges: bytes\r\n" + |
| 64 | + "Cache-Control: private\r\n" + |
| 65 | + "Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com\r\n" + |
| 66 | + "Content-Type: text/html; charset=utf-8\r\n" + |
| 67 | + "Date: Mon, 16 Nov 2020 23:35:36 GMT\r\n" + |
| 68 | + "Feature-Policy: microphone 'none'; speaker 'none'\r\n" + |
| 69 | + "Server: Microsoft-IIS/10.0\r\n" + |
| 70 | + "Strict-Transport-Security: max-age=15552000\r\n" + |
| 71 | + "Vary: Accept-Encoding,Fastly-SSL\r\n" + |
| 72 | + "Via: 1.1 varnish\r\n" + |
| 73 | + "x-account-id: 12345\r\n" + |
| 74 | + "x-aspnet-duration-ms: 44\r\n" + |
| 75 | + "x-cache: MISS\r\n" + |
| 76 | + "x-cache-hits: 0\r\n" + |
| 77 | + "x-dns-prefetch-control: off\r\n" + |
| 78 | + "x-flags: QA\r\n" + |
| 79 | + "x-frame-options: SAMEORIGIN\r\n" + |
| 80 | + "x-http-count: 2\r\n" + |
| 81 | + "x-http-duration-ms: 8\r\n" + |
| 82 | + "x-is-crawler: 0\r\n" + |
| 83 | + "x-page-view: 1\r\n" + |
| 84 | + "x-providence-cookie: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\r\n" + |
| 85 | + "x-redis-count: 22\r\n" + |
| 86 | + "x-redis-duration-ms: 2\r\n" + |
| 87 | + "x-request-guid: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\r\n" + |
| 88 | + "x-route-name: Home/Index\r\n" + |
| 89 | + "x-served-by: cache-sea4460-SEA\r\n" + |
| 90 | + "x-sql-count: 12\r\n" + |
| 91 | + "x-sql-duration-ms: 12\r\n" + |
| 92 | + "x-timer: S1605569737.604081,VS0,VE106\r\n" + |
| 93 | + "\r\n") |
| 94 | + }; |
| 95 | + |
| 96 | + [GlobalSetup] |
| 97 | + public void GlobalSetup() |
| 98 | + { |
| 99 | + _connectionListener = _connectionFactory.ListenAsync().AsTask().Result; |
| 100 | + _server = new SimpleHttp1Server(_connectionListener, trigger: Encoding.ASCII.GetBytes("\r\n\r\n"), response: ResponseBytes!.Response); |
| 101 | + _primitiveConnection = new Http1Connection(_connectionFactory.ConnectAsync(_connectionListener.EndPoint!).AsTask().Result); |
| 102 | + _httpClient = new HttpMessageInvoker(new SocketsHttpHandler |
| 103 | + { |
| 104 | + AllowAutoRedirect = false, |
| 105 | + AutomaticDecompression = System.Net.DecompressionMethods.None, |
| 106 | + ConnectCallback = async (ct, token) => (await _connectionFactory.ConnectAsync(_connectionListener.EndPoint!).ConfigureAwait(false)).Stream, |
| 107 | + UseProxy = false |
| 108 | + }); |
| 109 | + |
| 110 | + _uri = new Uri("http://localhost/"); |
| 111 | + _encodedScheme = Encoding.ASCII.GetBytes("http"); |
| 112 | + _encodedAuthority = Encoding.ASCII.GetBytes(_uri.IdnHost); |
| 113 | + _encodedPathAndQuery = Encoding.ASCII.GetBytes("/"); |
| 114 | + } |
| 115 | + |
| 116 | + [GlobalCleanup] |
| 117 | + public void GlobalCleanup() |
| 118 | + { |
| 119 | + _httpClient!.Dispose(); |
| 120 | + _primitiveConnection!.DisposeAsync().AsTask().GetAwaiter().GetResult(); |
| 121 | + _server!.DisposeAsync().AsTask().GetAwaiter().GetResult(); |
| 122 | + _connectionListener!.DisposeAsync().AsTask().GetAwaiter().GetResult(); |
| 123 | + } |
| 124 | + |
| 125 | + [Benchmark] |
| 126 | + public Task Primitive() => |
| 127 | + Primitive(RequestHeaders!.EncodedAllHeaders, preparedHeaders: null); |
| 128 | + |
| 129 | + [Benchmark] |
| 130 | + public Task PrimitivePrepared() => |
| 131 | + Primitive(RequestHeaders!.EncodedDynamicHeaders, RequestHeaders!.PreparedHeaders); |
| 132 | + |
| 133 | + private async Task Primitive(List<(byte[], byte[])>? encodedHeaders, PreparedHeaderSet? preparedHeaders) |
| 134 | + { |
| 135 | + ValueHttpRequest request = (await _primitiveConnection!.CreateNewRequestAsync(HttpPrimitiveVersion.Version11, HttpVersionPolicy.RequestVersionExact).ConfigureAwait(false)).Value; |
| 136 | + await using (request.ConfigureAwait(false)) |
| 137 | + { |
| 138 | + request.ConfigureRequest(contentLength: 0, hasTrailingHeaders: false); |
| 139 | + request.WriteRequest(HttpRequest.GetMethod, _encodedScheme!, _encodedAuthority!, _encodedPathAndQuery!); |
| 140 | + |
| 141 | + if (encodedHeaders != null) |
| 142 | + { |
| 143 | + foreach ((byte[] name, byte[] value) in encodedHeaders) |
| 144 | + { |
| 145 | + request.WriteHeader(name, value); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + if (preparedHeaders != null) |
| 150 | + { |
| 151 | + request.WriteHeader(preparedHeaders); |
| 152 | + } |
| 153 | + |
| 154 | + await request.CompleteRequestAsync().ConfigureAwait(false); |
| 155 | + |
| 156 | + while (await request.ReadAsync().ConfigureAwait(false) != HttpReadType.EndOfStream) |
| 157 | + { |
| 158 | + // skip everything. |
| 159 | + } |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + [Benchmark(Baseline = true)] |
| 164 | + public async Task SocketsHandler() |
| 165 | + { |
| 166 | + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _uri!); |
| 167 | + |
| 168 | + if (RequestHeaders!.StaticHeaders is List<(string, string)> staticHeaders) |
| 169 | + { |
| 170 | + foreach ((string name, string value) in staticHeaders) |
| 171 | + { |
| 172 | + requestMessage.Headers.TryAddWithoutValidation(name, value); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + if (RequestHeaders!.DynamicHeaders is List<(string, string)> dynamicHeaders) |
| 177 | + { |
| 178 | + foreach ((string name, string value) in dynamicHeaders) |
| 179 | + { |
| 180 | + requestMessage.Headers.TryAddWithoutValidation(name, value); |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + using var responseMessage = await _httpClient!.SendAsync(requestMessage, CancellationToken.None).ConfigureAwait(false); |
| 185 | + } |
| 186 | + |
| 187 | + public sealed class RequestHeaderCollection |
| 188 | + { |
| 189 | + public string Name { get; } |
| 190 | + public List<(string,string)>? StaticHeaders { get; init; } |
| 191 | + public List<(string,string)>? DynamicHeaders { get; init; } |
| 192 | + |
| 193 | + public PreparedHeaderSet? PreparedHeaders { get; private set; } |
| 194 | + public List<(byte[], byte[])>? EncodedDynamicHeaders { get; private set; } |
| 195 | + public List<(byte[], byte[])>? EncodedAllHeaders { get; private set; } |
| 196 | + |
| 197 | + public RequestHeaderCollection(string name) |
| 198 | + { |
| 199 | + Name = name; |
| 200 | + } |
| 201 | + |
| 202 | + public override string ToString() => Name; |
| 203 | + |
| 204 | + public RequestHeaderCollection Build() |
| 205 | + { |
| 206 | + if (StaticHeaders is List<(string, string)> staticHeaders) |
| 207 | + { |
| 208 | + EncodedAllHeaders ??= new List<(byte[], byte[])>(); |
| 209 | + |
| 210 | + var builder = new PreparedHeaderSetBuilder(); |
| 211 | + foreach ((string name, string value) in staticHeaders) |
| 212 | + { |
| 213 | + EncodedAllHeaders.Add((Encoding.ASCII.GetBytes(name), Encoding.ASCII.GetBytes(value))); |
| 214 | + builder.AddHeader(name, value); |
| 215 | + } |
| 216 | + |
| 217 | + PreparedHeaders = builder.Build(); |
| 218 | + } |
| 219 | + |
| 220 | + if (DynamicHeaders is List<(string, string)> dynamicHeaders) |
| 221 | + { |
| 222 | + EncodedAllHeaders ??= new List<(byte[], byte[])>(); |
| 223 | + EncodedDynamicHeaders = new List<(byte[], byte[])>(); |
| 224 | + |
| 225 | + foreach ((string name, string value) in dynamicHeaders) |
| 226 | + { |
| 227 | + EncodedAllHeaders.Add((Encoding.ASCII.GetBytes(name), Encoding.ASCII.GetBytes(value))); |
| 228 | + EncodedDynamicHeaders.Add((Encoding.ASCII.GetBytes(name), Encoding.ASCII.GetBytes(value))); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + return this; |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + public sealed record EncodedMessage(string Name, byte[] Response) |
| 237 | + { |
| 238 | + public EncodedMessage(string name, string response) |
| 239 | + : this(name, Encoding.ASCII.GetBytes(response)) |
| 240 | + { |
| 241 | + } |
| 242 | + |
| 243 | + public override string ToString() => Name; |
| 244 | + } |
| 245 | + } |
| 246 | +} |
0 commit comments