Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Conversation

Tratcher
Copy link
Member

@Tratcher Tratcher commented Nov 5, 2014

#139

This implementation includes several layers of support for reading multipart request bodies.

  1. The lowest level is the MultipartReader that can parse any stream minimal buffering. It gives you the headers and body of each section one at a time and then you do what you want with the body of that section (buffer, discard, write to disk, etc.).
  2. The next level up supports buffering the individual bodies to memory or disk and returning you an IEnumerable of all the sections.
  3. The next level up exposes extension methods on HttpRequest to determine if this is a multipart request and to read it using the methods from level 2 above. This will also cache the results in a feature interface so they can be accessed again by later calls.
  4. There is also extension method support for reading nested multipart entries.

Here is a functional sample:
aspnet/Entropy@4e083df

None of this work is prescriptive about how you want to interpret the content. If we want to interpret it as files then we would add one additional layer that would read the content-disposition header to get the file details. However, when we get strongly typed headers that information should be more readily available anyways.

@Tratcher
Copy link
Member Author

Tratcher commented Nov 5, 2014

@GrabYourPitchforks

@kichalla
Copy link
Member

kichalla commented Nov 5, 2014

@Tratcher : Just FYI...in Web API, some users had concerns about upload files to a directory on the web server and wanted the ability to upload files directly to Amazon or Azure storage services...if its small files then one can read the multipart content in-memory and upload them to these external storage services, but obviously this is not a recommended approach for large files...so checking to see if we have/plan the extensibility points for achieving this scenario...

@Tratcher
Copy link
Member Author

Tratcher commented Nov 5, 2014

@kichalla That should be easy enough. See MultipartReader.ReadMultipartToFileAsync to see how to stream the data to the destination of your choice.

@kichalla
Copy link
Member

kichalla commented Nov 5, 2014

👍 Thanks...

while (await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) > 0)
{
// Not all streams support cancellation directly.
cancellationToken.ThrowIfCancellationRequested();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not check the passive way too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (cancellationToken.IsCancellationRequested)?
Then what? I can't return a cancelled task from an async method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes true.

@Eilon
Copy link
Contributor

Eilon commented Nov 6, 2014

@Tratcher this is one of the topics that we should discuss in next week's mtg to go over the HTTP abstractions. This is a pretty big feature area that we want to have a broad discussion on before committing any codez.

{
#if ASPNETCORE50
Task<int> t = (Task<int>)asyncResult;
t.Wait();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 171 - 177 can be replaced by try { return t.GetAwaiter().GetResult(); } catch (Exception ex) { /* wrap and rethrow ex */ }. This has the added benefit of not exposing AggregateException up the stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's even worse, actually 😄

Given that Wait always throws an AggregateException when the task doesn't run to completion, the if statement is either not reached - when an exception is thrown - or necessarily evaluates to true 💥

@Tratcher
Copy link
Member Author

Updated.

@Tratcher
Copy link
Member Author

Tratcher commented Dec 4, 2014

Updated. Samples: aspnet/Entropy#32

@Tratcher
Copy link
Member Author

Tratcher commented Dec 4, 2014

@Tratcher
Copy link
Member Author

Tratcher commented Dec 4, 2014

@ajaybhargavb

@Tratcher
Copy link
Member Author

Tratcher commented Dec 6, 2014

I need to move IFormCollection from Http to Extensions or WebUtilities.

/// <param name="enableRewind">Indicates that the entire body should be buffered so it can be rewound afterwards.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<IEnumerable<IFileUpload>> ReadFileUploadsAsync([NotNull] this HttpRequest request, bool enableRewind = true,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an IFileCollection : IDictionary<string, IFileUpload[]>, indexed by the CD name parameter?

@Tratcher
Copy link
Member Author

Tratcher commented Dec 8, 2014

Updated.

var ignored = new ArraySegment<byte>(buffer, offset, count);
if (count == 0)
{
throw new ArgumentOutOfRangeException("count", "The value must be greater than zero.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nameof (might want to check other instance in this PR)

@Tratcher
Copy link
Member Author

Tratcher commented Dec 8, 2014

Updated.

@Eilon
Copy link
Contributor

Eilon commented Dec 8, 2014

The changes in response to my comments look good.

@Tratcher
Copy link
Member Author

Tratcher commented Dec 8, 2014

Also fixes: #84

var element = elements.Where(entry => entry.StartsWith("boundary=")).First();
var boundary = element.Substring("boundary=".Length);
// Remove quotes
if (boundary.Length >=2 && boundary[0] == '"' && boundary[boundary.Length - 1] == '"')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fomatting busted here

@davidfowl
Copy link
Member

:shipit:

@Tratcher
Copy link
Member Author

Redone, with updated samples: aspnet/Entropy#32


namespace Microsoft.AspNet.WebUtilities
{
public class BufferedReadStream : Stream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internal?

@ajaybhargavb
Copy link
Contributor

The ReadFormAsync method doesn't support multiple files with same name. It always returns the last file.
Eg:

<input type="file" name="files" id="file1" />
<input type="file" name="files" id="file2" />

Here ReadFormAsync returns only the second file.

@Tratcher
Copy link
Member Author

Tratcher commented Jan 5, 2015

@ajaybhargavb Fixed.

@rynowak
Copy link
Member

rynowak commented Jan 7, 2015

:shipit: from me based on the public surface area


if (_bodyStream == null || _bodyStream != body)
// Read the form async and block, but don't block indefinitely, this could be a 2gb file upload.
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a really difficult timeout to predict, reproduce, etc in production. How about adding an optional cancellation token argument to ReadForm() but have the default behavior assume a timeout for synchronous-request-body-reading is provided in a different way.

@Tratcher Tratcher merged commit 5872feb into aspnet:dev Jan 7, 2015
@Tratcher Tratcher deleted the Multipart branch January 7, 2015 23:46
ajaybhargavb added a commit to aspnet/Mvc that referenced this pull request Jan 8, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants