web-dev-qa-db-fra.com

Streaming de gros fichiers (> 2 Go sur IIS) à l'aide de l'API Web

J'essaie de télécharger de très gros fichiers (> 2 Go) sur mon application WebAPI (sous .NET 4.5.2, Windows 2012R2).

La définition de la propriété httpRuntime maxRequestLength est inutile car elle ne fonctionne qu'avec des fichiers de moins de 2 Go.

J'utilise actuellement un MultipartFormDataStreamProvider personnalisé pour lire l'intégralité du flux sur le serveur et j'ai déjà désactivé la mise en mémoire tampon à l'aide d'un WebHostBufferPolicySelector personnalisé.

Ce que j'ai découvert, c'est qu'ASP.NET (ou WebAPI d'ailleurs) utilise un flux HttpBufferlessInputStream sous le capot qui a un champ appelé _disableMaxRequestLength. Si je règle cette valeur sur true (via réflexion), je peux diffuser des fichiers de toute taille.

Cependant, les manipuler avec ces internas n'est clairement pas un bon moyen.

La classe HttpRequest utilisée pour la demande a une méthode appelée GetBufferlessInputStream qui a une surcharge qui permet de désactiver maxRequestLength.

Ma question est la suivante: comment faire en sorte que WebAPI utilise cette surcharge au lieu de la version standard?

Existe-t-il un moyen de remplacer la classe HttpRequest ou HttpContext par défaut? Ou ai-je vraiment besoin d'utiliser la réflexion pour tout?

C'est le code que j'utilise actuellement pour désactiver maxRequestLength:

    private void DisableRequestLengthOnStream(HttpContent parent)
    {
        var streamContentProperty = parent.GetType().GetProperty("StreamContent", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        if (streamContentProperty == null) return;
        var streamContent = streamContentProperty.GetValue(parent, null);
        if (streamContent == null) return;
        var contentProperty = typeof(StreamContent).GetField("content", BindingFlags.Instance | BindingFlags.NonPublic);
        if (contentProperty == null) return;
        var content = contentProperty.GetValue(streamContent);
        if (content == null) return;
        var requestLengthField = content.GetType().GetField("_disableMaxRequestLength", BindingFlags.Instance | BindingFlags.NonPublic);
        if (requestLengthField == null) return;
        requestLengthField.SetValue(content, true);
    }
19
Henning Krause

Ok, j'ai trouvé une solution assez simple. La réponse de @JustinR. fonctionnerait, bien sûr. Mais je voulais continuer à utiliser MultipartFormDataStreamProvider car il gère tous les éléments MIME.

La solution consiste simplement à créer une nouvelle instance StreamContent avec le flux d'entrée sans tampon approprié et à la remplir avec les en-têtes du contenu d'origine:

        var provider = new MultipartFormDataStreamProvider(path);
        var content = new StreamContent(HttpContext.Current.Request.GetBufferlessInputStream(true));
        foreach (var header in Request.Content.Headers)
        {
            content.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        await content.ReadAsMultipartAsync(provider);
6
Henning Krause

Selon MSDN , la manière de lire une longueur de flux illimitée est HttpRequest.GetBufferlessInputStream. Vous pourriez faire quelque chose comme:

public void ReadStream(HttpContext context, string filePath)
{
    using (var reader = new StreamReader(context.Request.GetBufferlessInputStream(true)))
    using (var filestream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, true))
    using (var writer = new StreamWriter(filestream))
    {
        var readBuffer = reader.ReadToEnd();
        writer.WriteAsync(readBuffer);
    }
}
2
Justin R.

IMHO il n'y a pas de moyen facile de faire cela. 

L'appel à GetBufferlessInputStream est enterré profondément dans HttpControllerHandler, qui constitue la couche la plus basse possible de l'API Web ASP.NET (il s'agit d'un gestionnaire HTTP sur lequel toute la pile d'API Web est construite.

Vous pouvez voir le code ici .

Comme vous le voyez, il regorge de méthodes statiques, de méthodes longues avec des conditions logiques imbriquées, d'internals et de privates. Il n'est donc pas vraiment personnalisable.
Bien que l’ensemble HttpControllerHandler de l’API Web puisse théoriquement être remplacé par une implémentation personnalisée (ceci est fait dans HttpControllerRouteHandler - en remplaçant la méthode GetHttpHandler), c’est de facto impossible (vous pouvez essayer d’internaliser ce code dans votre application, mais finira par faire glisser beaucoup de classes internes supplémentaires aussi).

La meilleure chose (et je crains de ne pas le dire) qui me vienne à l’esprit est de modifier la classe source HttpControllerHandler afin d’utiliser la surcharge de GetBufferlessInputStream qui désactive la limite de longueur de la requête, de recompiler l’assembly System.Web.Http.WebHost et de déployer cette version modifiée avec votre application.

1
Filip W