Подтвердить что ты не робот

WCF REST, потоковая загрузка файлов и свойство httpRuntime maxRequestLength

Я создал простой сервис WCF для прототипа загрузки файлов. Услуга:

[ServiceContract]
public class Service1
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/Upload")]
    public void Upload(Stream stream)
    {
        using (FileStream targetStream = new FileStream(@"C:\Test\output.txt", FileMode.Create, FileAccess.Write))
        {
            stream.CopyTo(targetStream);
        }
    }
}

В нем используется webHttpBinding с transferMode, установленным в "Streamed" и maxReceivedMessageSize, maxBufferPoolSize и maxBufferSize, все установлены в 2GB. httpRuntime имеет maxRequestLength значение 10 МБ.

Клиент выдает HTTP-запросы следующим образом:

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(@"http://.../Service1.svc/Upload");

request.Method = "POST";
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.ContentType = MediaTypeNames.Application.Octet;

using (FileStream inputStream = new FileStream(@"C:\input.txt", FileMode.Open, FileAccess.Read))
{
    using (Stream outputStream = request.GetRequestStream())
    {
        inputStream.CopyTo(outputStream);
    }
}

Теперь, наконец, что не так:

При загрузке файла размером 100 МБ сервер возвращает HTTP 400 (неверный запрос). Я попытался включить трассировку WCF, но не обнаружил ошибки. Когда я увеличиваю httpRuntime. maxRequestLength до 1 ГБ, файл загружается без проблем. MSDN говорит, что maxRequestLength "задает ограничение для порога буферизации входного потока в КБ".

Это заставляет меня думать, что весь файл (все его 100 МБ) сначала сохраняется в "буфере входного потока", и только тогда он доступен для моего метода Upload на сервере. Я действительно могу видеть, что размер файла на сервере не увеличивается постепенно (как и следовало ожидать), вместо этого, в момент его создания он уже имеет размер 100 МБ.

Вопрос: Как я могу заставить это работать, чтобы "буфер входного потока" был достаточно мал (скажем, 1 МБ), и когда он переполняется, мой метод Upload вызывается? Другими словами, я хочу, чтобы загрузка была действительно потоковой, без необходимости хранить весь файл в любом месте.

EDIT: Теперь я обнаружил, что httpRuntime содержит другой параметр, который здесь имеет значение - requestLengthDiskThreshold. Похоже, что когда входной буфер растет выше этого порога, он больше не сохраняется в памяти, а вместо этого в файловой системе. Так что, по крайней мере, весь большой файл размером 100 МБ не хранится в памяти (это то, чего я больше всего боялся), однако мне все же хотелось бы знать, есть ли способ избежать этого буфера вообще.

4b9b3361

Ответ 1

Если вы используете .NET 4 и размещаете свою службу в IIS7 +, вы можете столкнуться с ошибкой ASP.NET, описанной в следующем сообщении в блоге:

http://blogs.microsoft.co.il/blogs/idof/archive/2012/01/17/what-s-new-in-wcf-4-5-improved-streaming-in-iis-hosting.aspx

В принципе, для потоковых запросов обработчик ASP.NET в IIS будет буферизовать весь запрос перед передачей управления WCF. И этот обработчик подчиняется пределу maxRequestLength.

Насколько я знаю, для ошибки нет обходного пути, и у вас есть следующие возможности:

  • обновление до .NET 4.5
  • самостоятельно использовать вашу службу вместо IIS
  • используйте привязку, которая не основана на HTTP, так что обработчик ASP.NET не задействован

Ответ 2

Это может быть ошибкой в ​​потоковой реализации. Я нашел статью MSDN, которая предлагает делать то, что вы описываете на http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/fb9efac5-8b57-417e-9f71-35d48d421eb4/. К сожалению, сотрудник Microsoft, предлагающий исправление, обнаружил ошибку в реализации и не отслеживал подробности об исправлении.

Это говорит о том, что реализация нарушена, которую вы можете протестировать, профилировав свой код с помощью профилировщика памяти и проверив, сохраняется ли весь файл в памяти. Если весь файл хранится в памяти, вы не сможете исправить эту проблему, если только кто-то не обнаружит ошибку конфигурации с вашим кодом.

Тем не менее, при использовании функции requestLengthDiskThreshold технически может работать, это значительно увеличит время записи, так как каждый файл должен быть сначала записан как временные данные, считанные из временных данных, снова записанные как окончательные, и, наконец, данные временных данных удаляются. Как вы уже сказали, вы имеете дело с чрезвычайно большими файлами, поэтому я сомневаюсь, что такое решение приемлемо.

Лучше всего использовать фреймворк и вручную восстановить файл. Я нашел инструкции о том, как написать такую ​​логику в http://aspilham.blogspot.com/2011/03/file-uploading-in-chunks-using.html, но не успел проверить ее на точность.

Извините, я не могу сказать вам, почему ваш код не работает как задокументированный, но что-то похожее на 2-й пример должен работать без всплытия вашей памяти.