Буферизация IIS запрашивает содержимое перед действием контроллера

Я пытаюсь настроить «внешний» веб-API, который сможет получать большие http-сообщения (+ 1 ГБ) и перенаправлять поток на другой «внутренний» веб-API, который записывает содержимое запроса в файл. Я смоделировал свою реализацию на основе примеров использования пользовательского WebHostBufferPolicySelector и использования метода UseBufferedInputStream в методе контроллера. Он работает, как и ожидалось, при использовании IIS Express, без значительного увеличения объема памяти, но как только мой код развертывается в IIS, объем памяти становится значительным и приводит к OOM.

Я поместил операторы трассировки в свои методы контроллера и в свой WebHostBufferPolicySelector.UseBufferedInputStream и убедился, что UseBufferedInputStream всегда возвращает false и что мои методы контроллера срабатывают. Единственное отличие, которое я заметил, заключается в том, что при отладке отметки времени между UseBufferedInputStream и моим методом контроллера очень близки. При размещении в IIS метки времени очень далеки друг от друга, что позволяет предположить, что что-то промежуточное между вызовом UseBufferedInputStream и вызовом моего метода контроллера полностью буферизует запрос.

Я ищу несколько советов, чтобы выяснить, что вызывает буферизацию запроса и как его не буферизовать и полностью использовать потоковую передачу.

Клиент подключается к внешнему веб-API с типом контента application/octet-stream с Transfer Encoding of Chucked.

Используется для построения реализации

https://forums.asp.net/t/2018289.aspx?Web+API2+WebHostBufferPolicySelector+UseBufferedInputStream+override

https://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/

Метод прокси-контроллера веб-API

        <HttpPost, Route("postLargeFile")>
        Protected Overridable Async Function PostLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
            Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Started {0}", NameOf(MyProxyController.PostLargeFile))

            Dim internalHttpClient As HttpClient
            Dim fowardingContent As StreamContent = Nothing
            Dim fowardingMessage As HttpRequestMessage = Nothing
            Dim fowardingResponse As HttpResponseMessage = Nothing
            Dim externalResponse As HttpResponseMessage = Nothing

            Try
                internalHttpClient = New HttpClient()
                internalHttpClient.BaseAddress = "https://myinternalService.com"

                fowardingMessage = New HttpRequestMessage(HttpMethod.Post, "https://myinternalService.com/saveLargeFile")
                fowardingContent = New StreamContent(HttpContext.Current.Request.GetBufferlessInputStream(True))
                CopyContentHeaders(Request.Content, fowardingContent)

                fowardingMessage.Headers.TransferEncodingChunked = True
                fowardingMessage.Content = fowardingContent

                fowardingResponse = Await internalHttpClient.SendAsync(fowardingMessage, HttpCompletionOption.ResponseHeadersRead)

                externalResponse = New HttpResponseMessage(fowardingResponse.StatusCode)
                externalResponse.Content = New StreamContent(Await fowardingResponse.Content.ReadAsStreamAsync)
                CopyContentHeaders(fowardingResponse.Content, externalResponse.Content)

                Return New Results.ResponseMessageResult(externalResponse)

            Catch ex As Exception
                Return InternalServerError(ex)
            Finally
                Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyProxyController)}", "Finished {0}", NameOf(MyProxyController.PostLargeFile))
            End Try
        End Function

Метод внутреннего контроллера веб-API

        <HttpPost, Route("saveLargeFile")>
        Protected Overridable Async Function SaveLargeFile() As Threading.Tasks.Task(Of IHttpActionResult)
            Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Started {0}", NameOf(MyInternalController.PostLargeFile))

            Dim bufferlessStream As IO.Stream
            Dim fowardingContent As StreamContent = Nothing

            Try

                bufferlessStream = HttpContext.Current.Request.GetBufferlessInputStream()
                Using fileStream As IO.FileStream = IO.File.Create("MyFile.txt")
                    bufferlessStream.CopyTo(fileStream)
                    fileStream.Flush()
                End Using

                Return New Results.StatusCodeResult(Net.HttpStatusCode.Created, Me)

            Catch ex As Exception
                Return InternalServerError(ex)
            Finally
                Configuration.Services.GetTraceWriter.Info(Request, $"{Me.GetType.Namespace}.{NameOf(MyInternalController)}", "Finished {0}", NameOf(MyInternalController.PostLargeFile))
            End Try
        End Function

Конфигурация селектора политик

Public Class MyBufferPolicySelector
    Inherits Http.WebHost.WebHostBufferPolicySelector

    Public Property Tracer As ITraceWriter

    Public Overrides Function UseBufferedInputStream(hostContext As Object) As Boolean
        UseBufferedInputStream = False
        Tracer?.Info(Nothing, $"{Me.GetType.Namespace}.{NameOf(MyBufferPolicySelector)}", "{0} UseBufferedInputStream={1}", HttpContext.Current?.Request?.Url?.AbsoluteUri, UseBufferedInputStream)
        Return UseBufferedInputStream
    End Function
End Class

WebApiConfig для внутренних и внешних веб-API

Public Module WebApiConfig

    Public Sub Register(ByVal config As HttpConfiguration)
        Dim tracer As SystemDiagnosticsTraceWriter

        ' Web API configuration and services

        ' Web API routes
        config.MapHttpAttributeRoutes()

        tracer = config.EnableSystemDiagnosticsTracing
        tracer.IsVerbose = True
        tracer.MinimumLevel = Tracing.TraceLevel.Debug

        GlobalConfiguration.Configuration.Services.Replace(GetType(IHostBufferPolicySelector), New MyBufferPolicySelector() With {.Tracer = tracer})

    End Sub

End Module

person Jade L.    schedule 28.05.2019    source источник


Ответы (1)


Мне удалось выяснить, что вызывает буферизацию в IIS. Ссылка ниже привела меня к настройке uploadReadAheadSize в IIS. Это было исчерпано. Таким образом, это приведет к тому, что IIS полностью прочитает/буферизует запрос, прежде чем передать его в модуль, где существует конвейер веб-API (контроллеры веб-API). Установив значение по умолчанию, я увидел, что мои сообщения с большими файлами не буферизировались, объем памяти пула приложений оставался низким, больше не было исключений из-за нехватки памяти, а производительность значительно повысилась. Здорово!

Но теперь у меня такая же проблема, как описано в ссылке ниже. Когда требуется SSL, установленный в IIS, что требуется в наших средах, не связанных с разработкой, необходимо увеличить uploadReadAheadSize, чтобы ssl мог работать в модуле ssl, я думаю. Возможно, это связано с некоторым повторным согласованием SSL.

Может ли кто-нибудь описать способ предотвращения буферизации в SSL, чтобы сохранить низкий объем памяти и предотвратить исключения из памяти для больших сообщений http?

Загрузка больших файлов при использовании ssl и клиентских сертификатов ( uploadReadAheadSize), но не хотите, чтобы все данные читались заранее

person Jade L.    schedule 30.05.2019