大きなhttp投稿(+1GB)で受信できる「外部」Web APIをセットアップし、ストリームを別の「内部」Web APIに転送してリクエストの内容をファイルに書き込もうとしています。カスタム WebHostBufferPolicySelector を使用し、コントローラー メソッドで UseBufferedInputStream メソッドを使用する例に基づいて、実装をモデル化しました。IIS Express を使用すると期待どおりに動作し、メモリ フットプリントが大幅に増加することはありませんが、コードが IIS に展開されるとすぐにメモリ フットプリントが大きくなり、OOM が発生します。
コントローラー メソッドと WebHostBufferPolicySelector.UseBufferedInputStream にトレース ステートメントを配置し、UseBufferedInputStream が常に false を返し、コントローラー メソッドがヒットすることを確認しました。私が気付いた唯一の違いは、デバッグ時に、UseBufferedInputStream とコントローラー メソッドの間のタイム スタンプが非常に近いことです。IIS でホストされている場合、タイム スタンプは非常に離れています。これは、UseBufferedInputStream が呼び出されてからコントローラー メソッドが呼び出されるまでの間に、リクエスト全体がバッファリングされていることを示唆しています。
リクエストがバッファリングされる原因と、バッファリングされずにストリーミングを使用する方法を見つけるためのヒントを探しています。
クライアントは、コンテンツ タイプが application/octet-stream で、Transfer Encoding が Chucked である外部 Web API にアクセスしています。
実装の構築に使用
https://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/
Proxy Web API Controller メソッド
<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
内部 Web 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
内部 Web API と外部 Web API の両方の WebApiConfig
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