Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active March 15, 2019 00:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save davidfowl/6a529005287671490781bc2cccf79de2 to your computer and use it in GitHub Desktop.
Save davidfowl/6a529005287671490781bc2cccf79de2 to your computer and use it in GitHub Desktop.
FormReader prototype
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