Skip to content

Commit

Permalink
Use ValueTask in FormReading
Browse files Browse the repository at this point in the history
Addressing Task allocations in  aspnet#553

Unfortunately this has a return type split between in public api net451
and dotnet5.4
  • Loading branch information
benaadams committed Feb 7, 2016
1 parent c30e9f4 commit 8ff6275
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 2 deletions.
14 changes: 13 additions & 1 deletion src/Microsoft.AspNetCore.Http/Features/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,19 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
if (HasApplicationFormContentType(contentType))
{
var encoding = FilterEncoding(contentType.Encoding);
formFields = new FormCollection(await FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken));
var formDataTask = FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken);
#if NET451
formFields = new FormCollection(await formDataTask);
#else
if (!formDataTask.IsCompletedSuccessfully)
{
formFields = new FormCollection(await formDataTask.AsTask());
}
else
{
formFields = new FormCollection(formDataTask.Result);
}
#endif
}
else if (HasMultipartFormContentType(contentType))
{
Expand Down
199 changes: 199 additions & 0 deletions src/Microsoft.AspNetCore.WebUtilities/FormReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public FormReader(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, Ar
return new KeyValuePair<string, string>(key, value);
}

#if NET451
// Format: key1=value1&key2=value2
/// <summary>
/// Asynchronously reads the next key value pair from the form.
Expand Down Expand Up @@ -156,6 +157,136 @@ private async Task<string> ReadWordAsync(char seperator, CancellationToken cance
_builder.Append(c);
}
}
#else
// Format: key1=value1&key2=value2
/// <summary>
/// Asynchronously reads the next key value pair from the form.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>The next key value pair, or null when the end of the form is reached.</returns>
public ValueTask<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken)
{
var keyTask = ReadWordAsync('=', cancellationToken);
if (!keyTask.IsCompletedSuccessfully)
{
return ReadNextPairAsyncKeyAwaited(keyTask.AsTask(), cancellationToken);
}
var key = keyTask.Result;
if (string.IsNullOrEmpty(key) && _bufferCount == 0)
{
return (KeyValuePair<string, string>?)null;
}

var valueTask = ReadWordAsync('&', cancellationToken);
if (!valueTask.IsCompletedSuccessfully)
{
return ReadNextPairAsyncValueAwaited(key, valueTask.AsTask(), cancellationToken);
}

var value = valueTask.Result;
return new KeyValuePair<string, string>(key, value);
}

private async Task<KeyValuePair<string, string>?> ReadNextPairAsyncKeyAwaited(Task<string> keyTask, CancellationToken cancellationToken)
{
var key = await keyTask;
if (string.IsNullOrEmpty(key) && _bufferCount == 0)
{
return null;
}

var valueTask = ReadWordAsync('&', cancellationToken);
if (!valueTask.IsCompletedSuccessfully)
{
var value = await valueTask;
return new KeyValuePair<string, string>(key, value);
}

return new KeyValuePair<string, string>(key, valueTask.Result);
}

private async Task<KeyValuePair<string, string>?> ReadNextPairAsyncValueAwaited(string key, Task<string> valueTask, CancellationToken cancellationToken)
{
var value = await valueTask;
return new KeyValuePair<string, string>(key, value);
}

private string ReadWord(char seperator)
{
// TODO: Configurable value size limit
while (true)
{
// Empty
if (_bufferCount == 0)
{
Buffer();
}

var word = ReadWordImpl(seperator);
if (word != null)
{
return word;
}
}
}

private ValueTask<string> ReadWordAsync(char seperator, CancellationToken cancellationToken)
{
// TODO: Configurable value size limit
while (true)
{
// Empty
if (_bufferCount == 0)
{
return ReadWordAsyncAwaited(seperator, cancellationToken);
}

var word = ReadWordImpl(seperator);
if (word != null)
{
return word;
}
}
}
private async Task<string> ReadWordAsyncAwaited(char seperator, CancellationToken cancellationToken)
{
// TODO: Configurable value size limit
while (true)
{
// Empty
if (_bufferCount == 0)
{
await BufferAsync(cancellationToken);
}

var word = ReadWordImpl(seperator);
if (word != null)
{
return word;
}
}
}

private string ReadWordImpl(char seperator)
{
// End
if (_bufferCount == 0)
{
return BuildWord();
}

var c = _buffer[_bufferOffset++];
_bufferCount--;

if (c == seperator)
{
return BuildWord();
}
_builder.Append(c);

return null;
}
#endif

// '+' un-escapes to ' ', %HH un-escapes as ASCII (or utf-8?)
private string BuildWord()
Expand Down Expand Up @@ -204,6 +335,7 @@ public static Dictionary<string, StringValues> ReadForm(string text)
}
}

#if NET451
/// <summary>
/// Parses an HTTP form body.
/// </summary>
Expand Down Expand Up @@ -234,6 +366,73 @@ public static Dictionary<string, StringValues> ReadForm(string text)
return accumulator.GetResults();
}
}
#else
/// <summary>
/// Parses an HTTP form body.
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static ValueTask<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
{
return ReadFormAsync(stream, Encoding.UTF8, cancellationToken);
}

/// <summary>
/// Parses an HTTP form body.
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static ValueTask<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
{
using (var reader = new FormReader(stream, encoding))
{
var accumulator = new KeyValueAccumulator();

var pairTask = reader.ReadNextPairAsync(cancellationToken);
if (!pairTask.IsCompletedSuccessfully)
{
return ReadFormAsyncAwaited(pairTask.AsTask(), reader, accumulator, cancellationToken);
}

var pair = pairTask.Result;
while (pair.HasValue)
{
accumulator.Append(pair.Value.Key, pair.Value.Value);

pairTask = reader.ReadNextPairAsync(cancellationToken);
if (!pairTask.IsCompletedSuccessfully)
{
return ReadFormAsyncAwaited(pairTask.AsTask(), reader, accumulator, cancellationToken);
}

pair = pairTask.Result;
}

return accumulator.GetResults();
}
}

private static async Task<Dictionary<string, StringValues>> ReadFormAsyncAwaited(Task<KeyValuePair<string, string>?> pairTask, FormReader reader, KeyValueAccumulator accumulator, CancellationToken cancellationToken)
{
var pair = await pairTask;
while (pair.HasValue)
{
accumulator.Append(pair.Value.Key, pair.Value.Value);

var pairValueTask = reader.ReadNextPairAsync(cancellationToken);
if (!pairValueTask.IsCompletedSuccessfully)
{
pair = await pairTask;
}
else
{
pair = pairValueTask.Result;
}
}

return accumulator.GetResults();
}
#endif

public void Dispose()
{
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.AspNetCore.WebUtilities/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"dependencies": {
"System.Collections": "4.0.11-*",
"System.IO": "4.1.0-*",
"System.IO.FileSystem": "4.0.1-*"
"System.IO.FileSystem": "4.0.1-*",
"System.Threading.Tasks.Extensions": "4.0.0-*"
}
}
}
Expand Down

0 comments on commit 8ff6275

Please sign in to comment.