我正在构建一个接受"多部分/form-data"请求的ASP.NET Web API端点。我使用.NETFramework4.5和Webneneneba API 2.1实现了本文中描述的它。我创建的操作方法的简化版本如下:
public async Task<HttpResponseMessage> PostFile()
{
if (!Request.Content.IsMimeMultipartContent()) throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var rootPath = System.Configuration.ConfigurationManager.AppSettings["StorageLocation"].ToString();
var provider = new MultipartFormDataStreamProvider(rootPath);
var response = Request.CreateResponse(HttpStatusCode.OK);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
// Imagine awesome logic here, unicorns and rainbows! Instead of that, we do the following:
response.Content = new StringContent("You uploaded " + provider.FileData.Count.ToString() + " files.");
}
catch (Exception e) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e)); }
return response;
}
因为上传的文件可能很大(高达2GiB),我希望我的请求不被ASP.NET缓冲,从而避免高内存使用率。为了实现这一点,我告诉Web API对传入请求进行流式处理,而不是缓冲它们,如本文所述。自定义WebHostBufferPolicySelector看起来像这样:
public class CustomWebHostBufferPolicySelector : WebHostBufferPolicySelector
{
public override bool UseBufferedInputStream(object hostContext)
{
System.Web.HttpContextBase contextBase = hostContext as System.Web.HttpContextBase;
if (contextBase != null && contextBase.Request.ContentType != null && contextBase.Request.ContentType.Contains("multipart")) return false;
else return base.UseBufferedInputStream(hostContext);
}
public override bool UseBufferedOutputStream(System.Net.Http.HttpResponseMessage response)
{
return base.UseBufferedOutputStream(response);
}
}
我在应用程序启动时将这个家伙加载到Global.asax中,如下所示:
protected void Application_Start(object sender, EventArgs e)
{
// Here, other stuff got did.
GlobalConfiguration.Configuration.Services.Replace(typeof(IHostBufferPolicySelector), new CustomWebHostBufferPolicySelector());
}
好了,棋盘已经装好了,让我们把棋子移动起来。如果我不使用CustomWebHostBufferPolicySelector,一切都会很好。然而,当它被使用时,我得到了以下异常:
Message: "An error has occurred."
ExceptionMessage: "Error reading MIME multipart body part."
ExceptionType: "System.IO.IOException"
StackTrace: " at System.Net.Http.HttpContentMultipartExtensions.<ReadAsMultipartAsync>d__0`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at ..."
以下内部异常:
Message: "An error has occurred."
ExceptionMessage: "Unable to read the entity body in Bufferless mode. The request stream has already been buffered."
ExceptionType: "System.InvalidOperationException"
StackTrace: " at System.Web.Http.WebHost.HttpControllerHandler.<>c__DisplayClass13.<GetStreamContent>b__10() at System.Web.Http.WebHost.HttpControllerHandler.LazyStreamContent.get_StreamContent() at System.Web.Http.WebHost.HttpControllerHandler.LazyStreamContent.CreateContentReadStreamAsync() at System.Net.Http.HttpContent.ReadAsStreamAsync() at System.Net.Http.HttpContentMultipartExtensions.<ReadAsMultipartAsync>d__0`1.MoveNext()"
看起来请求仍然被其他东西缓冲了。在ASP.NET管道中还有其他位置吗?甚至可能是IIS?在这个请求的生命周期中,还有哪些地方可以缓冲它,我该如何控制它们?
为了使问题更加清晰并与他人共享,我创建了一个简单的项目来尝试重现问题。在这样做的时候,我找到了答案:禁用所有类型的跟踪。
在我的案例中,我启用了ASP.NET自己的跟踪功能,还启用了Glimpse。这两者都在请求到达Web API操作之前对其进行缓冲。
为了完整起见,这里给出了在测试和生产时在Web.Config中关闭它们的正确方法。
<configuration>
<system.web>
<trace enabled="false" />
</system.web>
<glimpse defaultRuntimePolicy="Off">
</glimpse>
</configuration>
就我而言,这两人是罪魁祸首,但我可以想象可能还有其他人,所以要警惕这一点。