web-dev-qa-db-fra.com

WebAPI StreamContent vs PushStreamContent

J'implémente une version MVC4 + WebAPI du BluImp jQuery File Upload tout fonctionne bien avec ma tentative initiale mais j'essaie d'assurer la meilleure utilisation de la mémoire lors du téléchargement de très gros fichiers (~ 2 Go).

J'ai lu article de Filip Woj sur PushStreamContent et l'ai implémenté du mieux que je peux (en supprimant les parties asynchrones - c'est peut-être le problème?). Lorsque Im exécute des tests et regarde TaskManager, je ne vois pas beaucoup de différence d'utilisation de la mémoire et j'essaie de comprendre la différence entre la façon dont les réponses sont traitées.

Voici ma version StreamContent:

private HttpResponseMessage DownloadContentNonChunked()
{
    var filename = HttpContext.Current.Request["f"];
    var filePath = _storageRoot + filename;
    if (File.Exists(filePath))
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = filename
        };
        return response;
    }
    return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
}

Et voici ma version de PushStreamContent:

public class FileDownloadStream
{
    private readonly string _filename;

    public FileDownloadStream(string filePath)
    {
        _filename = filePath;
    }

    public void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
    {
        try
        {
            var buffer = new byte[4096];

            using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read))
            {
                var length = (int)video.Length;
                var bytesRead = 1;

                while (length > 0 && bytesRead > 0)
                {
                    bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
                    outputStream.Write(buffer, 0, bytesRead);
                    length -= bytesRead;
                }
            }
        }
        catch (HttpException ex)
        {
            return;
        }
        finally
        {
            outputStream.Close();
        }
    }
}

private HttpResponseMessage DownloadContentChunked()
{
    var filename = HttpContext.Current.Request["f"];
    var filePath = _storageRoot + filename;
    if (File.Exists(filePath))
    {
        var fileDownload = new FileDownloadStream(filePath);
        var response = Request.CreateResponse();
        response.Content = new PushStreamContent(fileDownload.WriteToStream, new MediaTypeHeaderValue("application/octet-stream"));
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = filename
        };
        return response;
    }
    return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
}

Ma question est pourquoi je ne vois pas beaucoup de différence dans l'utilisation de la mémoire entre les deux approches? De plus, j'ai téléchargé la PDB pour le type StreamContent et je peux voir des références aux tailles de tampons et ainsi de suite (voir ci-dessous), donc je voudrais savoir exactement ce que PushStreamContent fait au-delà de StreamContent. J'ai vérifié les informations de type sur MSDN mais l'article était un peu léger sur l'explication!

namespace System.Net.Http
{
  /// <summary>
  /// Provides HTTP content based on a stream.
  /// </summary>
  [__DynamicallyInvokable]
  public class StreamContent : HttpContent
  {
    private Stream content;
    private int bufferSize;
    private bool contentConsumed;
    private long start;
    private const int defaultBufferSize = 4096;

    /// <summary>
    /// Creates a new instance of the <see cref="T:System.Net.Http.StreamContent"/> class.
    /// </summary>
    /// <param name="content">The content used to initialize the <see cref="T:System.Net.Http.StreamContent"/>.</param>
    [__DynamicallyInvokable]
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public StreamContent(Stream content)
      : this(content, 4096)
    {
    }
35
bUKaneer

En ce qui concerne l'utilisation de la mémoire de ces deux approches, pour StreamContent et PushStreamContent, l'API Web ne met pas en mémoire tampon les réponses. L'instantané de code suivant provient de WebHostBufferPolicySelector. Code source ( ici .

    /// <summary>
    /// Determines whether the Host should buffer the <see cref="HttpResponseMessage"/> entity body.
    /// </summary>
    /// <param name="response">The <see cref="HttpResponseMessage"/>response for which to determine
    /// whether Host output buffering should be used for the response entity body.</param>
    /// <returns><c>true</c> if buffering should be used; otherwise a streamed response should be used.</returns>
    public virtual bool UseBufferedOutputStream(HttpResponseMessage response)
    {
        if (response == null)
        {
            throw Error.ArgumentNull("response");
        }

        // Any HttpContent that knows its length is presumably already buffered internally.
        HttpContent content = response.Content;
        if (content != null)
        {
            long? contentLength = content.Headers.ContentLength;
            if (contentLength.HasValue && contentLength.Value >= 0)
            {
                return false;
            }

            // Content length is null or -1 (meaning not known).  
            // Buffer any HttpContent except StreamContent and PushStreamContent
            return !(content is StreamContent || content is PushStreamContent);
        }

        return false;
    }

PushStreamContent est également destiné aux scénarios dans lesquels vous devez "pousser" les données vers le flux, tandis que StreamContent "extrait" les données du flux. Donc, pour votre scénario actuel de téléchargement de fichiers, l'utilisation de StreamContent devrait convenir.

Exemples ci-dessous:

// Here when the response is being written out the data is pulled from the file to the destination(network) stream
response.Content = new StreamContent(File.OpenRead(filePath));

// Here we create a Push stream content so that we can use XDocument.Save to Push data to the destination(network) stream
XDocument xDoc = XDocument.Load("Sample.xml", LoadOptions.None);
PushStreamContent xDocContent = new PushStreamContent(
(stream, content, context) =>
{
     // After save we close the stream to signal that we are done writing.
     xDoc.Save(stream);
     stream.Close();
},
"application/xml");
25
Kiran Challa