-
-
Save gbaldera/68f301836ef330ab844c845f195697fb to your computer and use it in GitHub Desktop.
FormReader prototype
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class FormReaderExtensions | |
{ | |
public static async ValueTask<IFormCollection> ReadFormAsync2(this HttpRequest request) | |
{ | |
var reader = request.BodyPipe; | |
KeyValueAccumulator accumulator = default; | |
while (true) | |
{ | |
var result = await reader.ReadAsync(); | |
var buffer = result.Buffer; | |
if (!buffer.IsEmpty) | |
{ | |
TryParseFormValues(ref buffer, ref accumulator, result.IsCompleted); | |
} | |
if (result.IsCompleted) | |
{ | |
if (!buffer.IsEmpty) | |
{ | |
throw new InvalidOperationException("End of body before form was fully parsed."); | |
} | |
break; | |
} | |
reader.AdvanceTo(buffer.Start, buffer.End); | |
} | |
return new FormCollection(accumulator.GetResults()); | |
} | |
private static void TryParseFormValuesFast(ReadOnlySpan<byte> span, ref KeyValueAccumulator accumulator, bool isFinalBlock, out int consumed) | |
{ | |
ReadOnlySpan<byte> key = default; | |
ReadOnlySpan<byte> value = default; | |
consumed = 0; | |
while (span.Length > 0) | |
{ | |
var equals = span.IndexOf((byte)'='); | |
if (equals == -1) | |
{ | |
break; | |
} | |
key = span.Slice(0, equals); | |
span = span.Slice(key.Length + 1); | |
var ampersand = span.IndexOf((byte)'&'); | |
value = span; | |
if (ampersand == -1) | |
{ | |
if (!isFinalBlock) | |
{ | |
// We can't that what is currently read is the end of the form value, that's only the case if this is the final block | |
// If we're not in the final block, the consume nothing | |
break; | |
} | |
span = Span<byte>.Empty; | |
} | |
else | |
{ | |
value = span.Slice(0, ampersand); | |
span = span.Slice(ampersand + 1); | |
} | |
accumulator.Append(Encoding.UTF8.GetString(key), Encoding.UTF8.GetString(value)); | |
consumed += key.Length + value.Length + (ampersand == -1 ? 1 : 2); | |
} | |
} | |
private static void TryParseFormValues(ref ReadOnlySequence<byte> buffer, ref KeyValueAccumulator accumulator, bool isFinalBlock) | |
{ | |
if (buffer.IsSingleSegment) | |
{ | |
TryParseFormValuesFast(buffer.First.Span, ref accumulator, isFinalBlock, out var consumed); | |
buffer = buffer.Slice(consumed); | |
return; | |
} | |
TryParseFormValuesSlow(ref buffer, ref accumulator, isFinalBlock); | |
} | |
private static void TryParseFormValuesSlow(ref ReadOnlySequence<byte> buffer, ref KeyValueAccumulator accumulator, bool isFinalBlock) | |
{ | |
var reader = new SequenceReader<byte>(buffer); | |
var consumed = reader.Position; | |
while (!reader.End) | |
{ | |
// Read the key | |
if (reader.TryReadTo(out ReadOnlySpan<byte> key, (byte)'=')) | |
{ | |
// Read the value | |
if (!reader.TryReadTo(out ReadOnlySpan<byte> value, (byte)'&')) | |
{ | |
// We didn't find the & so we can only assume the rest of the buffer is the full value | |
// if this is the final payload | |
if (!isFinalBlock) | |
{ | |
break; | |
} | |
// Otherwise, the rest of the buffer is the value | |
var valueBuffer = buffer.Slice(reader.Position); | |
value = valueBuffer.IsSingleSegment ? valueBuffer.First.Span : valueBuffer.ToArray(); | |
// Advance to the end | |
reader.Advance(valueBuffer.Length); | |
} | |
accumulator.Append(Encoding.UTF8.GetString(key), Encoding.UTF8.GetString(value)); | |
// Mark this data as consumed if we wrote both a key and a value | |
consumed = reader.Position; | |
} | |
} | |
buffer = buffer.Slice(consumed); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment