Потоковая передача больших файлов (> 2 ГБ по IIS) с использованием WebAPI
Я пытаюсь загрузить очень большие файлы ( > 2 ГБ) в мое приложение WebAPI (работающий на .NET 4.5.2, Windows 2012R2).
Установка свойства httpRuntime maxRequestLength бесполезна, поскольку он работает только с файлами размером менее 2 ГБ.
В настоящее время я использую настраиваемый файл MultipartFormDataStreamProvider для чтения всего потока на сервере, и я уже отключил буферизацию с помощью настраиваемого WebHostBufferPolicySelector.
Я обнаружил, что ASP.NET(или WebAPI) использует HttpBufferlessInputStream под капотом, у которого есть поле с именем _disableMaxRequestLength. Если я установил для этого значения значение true (через отражение), я могу передавать файлы любого размера.
Однако, сражаться с ними с помощью этих внутренних дел явно не очень хорошо.
Класс HttpRequest, используемый для запроса, имеет метод GetBufferlessInputStream, который имеет перегрузку, которая позволяет отключить maxRequestLength.
Мой вопрос: как я могу заставить WebAPI использовать эту перегрузку вместо стандартного?
Есть ли способ заменить класс HttpRequest по умолчанию или класс HttpContext? Или мне действительно нужно использовать отражение для всего материала?
Это код, который я использую сейчас для отключения 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);
}
Ответы
Ответ 1
Хорошо, я нашел довольно простое решение. Ответ от @JustinR. будет работать, конечно. Но я хотел продолжать использовать MultipartFormDataStreamProvider, потому что он обрабатывает все вещи MIME.
Решение состоит в том, чтобы просто создать новый экземпляр StreamContent с правильным входным потоком без буфера и заполнить его заголовками из исходного содержимого:
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);
Ответ 2
В соответствии с MSDN способ чтения неограниченной длины потока HttpRequest.GetBufferlessInputStream
. Вы можете сделать что-то вроде:
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);
}
}
Ответ 3
ИМХО, нет простого способа сделать это.
Вызов GetBufferlessInputStream
находится в глубине HttpControllerHandler
, что является самым низким уровнем ASP.NET Web API (это HTTP-обработчик, поверх которого построен весь стек Web API.
Вы можете увидеть здесь код.
Как вы видите, это полная статика, длинные методы с вложенными логическими условиями, внутренними и рядовыми, поэтому он не настраивается вообще.
Хотя весь HttpControllerHandler
в Web API теоретически может быть заменен на пользовательскую реализацию (это делается внутри HttpControllerRouteHandler
- путем переопределения метода GetHttpHandler
), это де-факто невозможно (вы можете попытаться интернализировать этот код в своем приложении, но вы тоже втянете лишние внутренние классы).
Лучше всего (и я боюсь сказать это), который приходит мне на ум, состоит в том, чтобы изменить исходный класс HttpControllerHandler
, чтобы использовать перегрузку GetBufferlessInputStream
, которая отключает ограничение длины запроса и перекомпилирует сборку System.Web.Http.WebHost
и развертывать эту модифицированную версию с вашим приложением.