@@ -227,79 +227,125 @@ public static HttpMethod GetKnownMethod(this ReadOnlySpan<byte> span, out int me
227
227
/// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE)
228
228
/// </remarks>
229
229
/// <returns><see cref="HttpMethod"/></returns>
230
- public static HttpMethod GetKnownMethod ( string value )
230
+ public static HttpMethod GetKnownMethod ( string ? value )
231
231
{
232
- // Called by http/2
233
- if ( value == null )
232
+ // A perfect hash is used to get an index into a lookup-table for know HTTP-methods.
233
+ //
234
+ // If value is not null or an empty string, then local function PerfectHash is called.
235
+ // This perfect hashing is done by lookup up 'associatedValues' from a pre-generated lookup-table.
236
+ // The generation of that table was done by GNU gperf tool.
237
+ // Once we have that perfect hash we use another lookup-table to get the know HTTP-method if found
238
+ // or return HttpMethod.Custom if not found.
239
+ //
240
+ // Further info and how to call gperf see https://github.com/dotnet/aspnetcore/pull/44096
241
+ //
242
+ // Code here could be removed if Roslyn improvements from
243
+ // https://github.com/dotnet/roslyn/issues/56374 are added.
244
+
245
+ const int MinWordLength = 3 ;
246
+ const int MaxWordLength = 7 ;
247
+ const int MaxHashValue = 12 ;
248
+
249
+ if ( string . IsNullOrEmpty ( value ) )
234
250
{
235
251
return HttpMethod . None ;
236
252
}
237
253
238
- var length = value . Length ;
239
- if ( length == 0 )
254
+ if ( ( ( uint ) ( value . Length - MinWordLength ) <= ( MaxWordLength - MinWordLength ) ) )
240
255
{
241
- return HttpMethod . None ;
242
- }
256
+ var methodsLookup = Methods ( ) ;
243
257
244
- // Start with custom and assign if known method is found
245
- var method = HttpMethod . Custom ;
258
+ Debug . Assert ( WordListForPerfectHashOfMethods . Length == ( MaxHashValue + 1 ) && methodsLookup . Length == ( MaxHashValue + 1 ) ) ;
246
259
247
- var firstChar = value [ 0 ] ;
248
- if ( length == 3 )
249
- {
250
- if ( firstChar == 'G' && string . Equals ( value , HttpMethods . Get , StringComparison . Ordinal ) )
251
- {
252
- method = HttpMethod . Get ;
253
- }
254
- else if ( firstChar == 'P' && string . Equals ( value , HttpMethods . Put , StringComparison . Ordinal ) )
255
- {
256
- method = HttpMethod . Put ;
257
- }
258
- }
259
- else if ( length == 4 )
260
- {
261
- if ( firstChar == 'H' && string . Equals ( value , HttpMethods . Head , StringComparison . Ordinal ) )
262
- {
263
- method = HttpMethod . Head ;
264
- }
265
- else if ( firstChar == 'P' && string . Equals ( value , HttpMethods . Post , StringComparison . Ordinal ) )
266
- {
267
- method = HttpMethod . Post ;
268
- }
269
- }
270
- else if ( length == 5 )
271
- {
272
- if ( firstChar == 'T' && string . Equals ( value , HttpMethods . Trace , StringComparison . Ordinal ) )
273
- {
274
- method = HttpMethod . Trace ;
275
- }
276
- else if ( firstChar == 'P' && string . Equals ( value , HttpMethods . Patch , StringComparison . Ordinal ) )
277
- {
278
- method = HttpMethod . Patch ;
279
- }
280
- }
281
- else if ( length == 6 )
282
- {
283
- if ( firstChar == 'D' && string . Equals ( value , HttpMethods . Delete , StringComparison . Ordinal ) )
260
+ var index = PerfectHash ( value ) ;
261
+
262
+ if ( index < ( uint ) WordListForPerfectHashOfMethods . Length
263
+ && WordListForPerfectHashOfMethods [ index ] == value
264
+ && index < ( uint ) methodsLookup . Length )
284
265
{
285
- method = HttpMethod . Delete ;
266
+ return ( HttpMethod ) methodsLookup [ ( int ) index ] ;
286
267
}
287
268
}
288
- else if ( length == 7 )
269
+
270
+ return HttpMethod . Custom ;
271
+
272
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
273
+ static uint PerfectHash ( ReadOnlySpan < char > str )
289
274
{
290
- if ( firstChar == 'C' && string . Equals ( value , HttpMethods . Connect , StringComparison . Ordinal ) )
275
+ // This uses C#'s optimization to refer to static data of assembly, and doesn't allocate.
276
+ ReadOnlySpan < byte > associatedValues = new byte [ ]
291
277
{
292
- method = HttpMethod . Connect ;
293
- }
294
- else if ( firstChar == 'O' && string . Equals ( value , HttpMethods . Options , StringComparison . Ordinal ) )
295
- {
296
- method = HttpMethod . Options ;
297
- }
298
- }
299
-
300
- return method ;
278
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
279
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
280
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
281
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
282
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
283
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
284
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 5 , 0 , 13 ,
285
+ 13 , 0 , 0 , 13 , 13 , 13 , 13 , 13 , 13 , 0 ,
286
+ 5 , 13 , 13 , 13 , 0 , 13 , 13 , 13 , 13 , 13 ,
287
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
288
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
289
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
290
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
291
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
292
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
293
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
294
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
295
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
296
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
297
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
298
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
299
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
300
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
301
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
302
+ 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 ,
303
+ 13 , 13 , 13 , 13 , 13 , 13
304
+ } ;
305
+
306
+ var c = MemoryMarshal . GetReference ( str ) ;
307
+
308
+ Debug . Assert ( char . IsAscii ( c ) , "Must already be valiated" ) ;
309
+
310
+ return ( uint ) str . Length + associatedValues [ c ] ;
311
+ }
312
+
313
+ // This uses C#'s optimization to refer to static data of assembly, and doesn't allocate.
314
+ static ReadOnlySpan < byte > Methods ( ) => new byte [ ]
315
+ {
316
+ ( byte ) HttpMethod . None ,
317
+ ( byte ) HttpMethod . None ,
318
+ ( byte ) HttpMethod . None ,
319
+ ( byte ) HttpMethod . Get ,
320
+ ( byte ) HttpMethod . Head ,
321
+ ( byte ) HttpMethod . Trace ,
322
+ ( byte ) HttpMethod . Delete ,
323
+ ( byte ) HttpMethod . Options ,
324
+ ( byte ) HttpMethod . Put ,
325
+ ( byte ) HttpMethod . Post ,
326
+ ( byte ) HttpMethod . Patch ,
327
+ ( byte ) HttpMethod . None ,
328
+ ( byte ) HttpMethod . Connect
329
+ } ;
301
330
}
302
331
332
+ private static readonly string [ ] WordListForPerfectHashOfMethods =
333
+ {
334
+ "" ,
335
+ "" ,
336
+ "" ,
337
+ "GET" ,
338
+ "HEAD" ,
339
+ "TRACE" ,
340
+ "DELETE" ,
341
+ "OPTIONS" ,
342
+ "PUT" ,
343
+ "POST" ,
344
+ "PATCH" ,
345
+ "" ,
346
+ "CONNECT"
347
+ } ;
348
+
303
349
/// <summary>
304
350
/// Checks 9 bytes from <paramref name="span"/> correspond to a known HTTP version.
305
351
/// </summary>
0 commit comments