【问题标题】:WebAPI error reading MIME multipart body partWebAPI 错误读取 MIME 多部分正文部分
【发布时间】:2016-12-24 13:04:00
【问题描述】:

我的团队最近重构了一个 Web API 服务,将一些重复的代码移动到静态方法中。一种方法与从请求中提取上传的文件有关。该方法在单元测试中有效,但在负载下会引发异常。部分代码是在 SO 帖子中找到的,但我担心总的来说,我们没有正确使用它。代码如下:

internal static string ExtractFile(HttpRequestMessage request)
{
    if (request.Content.IsMimeMultipartContent())
    {
        string uploadRoot = ServiceHelper.GetUploadDirectoryPath();

        var provider = new MultipartFormDataStreamProvider(uploadRoot);

        try
        {
            Task.Factory
            .StartNew(() => provider = request.Content.ReadAsMultipartAsync(provider).Result,
                CancellationToken.None,
                TaskCreationOptions.LongRunning, // guarantees separate thread
                TaskScheduler.Default)
            .Wait();
        }
        catch(System.AggregateException ae)
        {
            if(log.IsErrorEnabled)
            {
                foreach(var ex in ae.InnerExceptions)
                {
                    log.Error("ReadAsMultipartAsync task error.", ex);
                }
            }

            var errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, "An error occurred while extracting the uploaded file from the request.");
            throw new HttpResponseException(errorResponse);
        }

        var fileData = provider.FileData.First();

        var localName = fileData.LocalFileName;
        var content = File.ReadAllText(localName);

        if (log.IsDebugEnabled)
        {
            var embeddedName = fileData.Headers.ContentDisposition.FileName;
            log.DebugFormat("File {0} was successfully uploaded as '{1}'.", embeddedName, localName);
        }

        return content;
    }
    else
    {
        log.Error("Invalid request received. Request must be in a multipart/form-data request.");

        var errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Request must be a multipart/form-data request and contain one file.");
        throw new HttpResponseException(errorResponse);
    }
}

浏览日志,我看到如下错误:

System.IO.IOException:读取 MIME 多部分正文部分时出错。 ---> System.IO.IOException ---> System.Net.HttpListenerException: I/O 操作由于线程退出或应用程序请求而中止

HttpListenerRequest 已处理

System.IO.IOException:读取 MIME 多部分正文部分时出错。 ---> System.IO.IOException ---> System.Net.HttpListenerException: 在不存在的网络连接上尝试了操作

此 Web 服务作为自托管的 OWIN Windows 服务运行。文件上传很小(3k 到 4k)。

我无法通过一次上传重新创建问题。与服务对话的客户端使用任务来发布文件,但它通常不会同时运行超过 4 或 5 个任务。我和我的团队对 .NET 任务还比较陌生。一位开发人员想知道 TaskCreationOptions.LongRunning 参数是否真的比它的帮助更大。有什么建议吗?

更新:

我尝试使用以下代码切换 Task.Factory 代码:

var task = Task.Run(async () => await request.Content.ReadAsMultipartAsync(provider));
task.Wait();
provider = task.Result;

我仍然遇到一些问题,但这似乎效果更好。不过不知道为什么。

【问题讨论】:

  • 也许检查您的 Owin 配置的超时/保持活动设置?这可能是由于连接缓慢/不稳定造成的?

标签: c# asp.net-web-api


【解决方案1】:

我遇到了同样的问题,通过在 web.config 中添加以下代码解决了这个问题

<system.web>
    <httpRuntime maxRequestLength="30000000" />
</system.web>

<system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="30000000" />
      </requestFiltering>
    </security>
</system.webServer>

来源:http://stackoverflow.com/questions/20942636/webapi-cannot-parse-multipart-form-data-post

【讨论】:

  • 似乎是maxRequestLength(默认为4096 kB documented in HttpRuntimeSection class)特别需要更新,因为默认maxAllowedContentLength在IIS中已经是30000000(see doc)。请注意,maxRequestLength 应该以 kB 为单位设置,因此 maxAllowedContentLength 中对应于 30,000,000 的值将是 29296。我自己将其限制为 15360 kB。
【解决方案2】:

在分析日志时,我意识到我们的客户端代码在生成多少线程方面非常激进。我为客户端添加了一个节流阀以减慢一些速度,服务器更喜欢这一点。我相信我最初的服务器代码更改几乎没有影响。输出的差异可能是由于网络延迟等其他变量造成的。

这是我更新客户端代码的方式:

// New private field in my client class
private SemaphoreSlim _semaphore = new SemaphoreSlim(Settings.Default.MaxConcurrentRequests, Settings.Default.MaxConcurrentRequests);

// Added continuation to Polly tasks
foreach (var file in files)
{
    var task = retryPolicy.ExecuteAsync(() => SendIllustrationRequest(file));
    task.ContinueWith((x) => _semaphore.Release());

    _transactionTasks.Add(task);
}

Task.WaitAll(_transactionTasks.ToArray());

// Added semaphore wait method to the start of the SendIllustrationRequest(file) method
_semaphore.Wait();

这项技术对我来说效果很好,因为我正在使用 Polly,并且 Polly 会立即开始执行任务。如果您可以选择创建任务而不立即启动它们,还有其他选项。

【讨论】:

【解决方案3】:

让我声明,这不是您的代码失败原因的答案,而是我有一个更容易提出的问题,有一些空间可以显示一些格式化的代码。如果这不提供任何帮助/指导,我将/可以将其作为答案删除。

我假设您的方法是从传入的 Web api 请求中调用的。你有什么理由不让这个“一直异步”吗?这是构建它的最简单方法,并且由于代码不佳/有缺陷而导致死锁的可能性最小(如果您刚开始使用 Tasks 和 async/await,这真的很容易做到)。

// new signature (I changed the name and prefixed Async which is a common convention)
internal static async Task<string> ExtractFileAsync(HttpRequestMessage request)
{
    if (request.Content.IsMimeMultipartContent())
    {
        string uploadRoot = ServiceHelper.GetUploadDirectoryPath();

        var provider = new MultipartFormDataStreamProvider(uploadRoot);

        await request.Content.ReadAsMultipartAsync(provider);
        /* rest of code */
    }
}

// calling method example from some Web API Controller
public async Task<IHttpActionResult> Post(CancellationToken token)
{ 
    var result = await ExtractFileAsync(Request).ConfigureAwait(true); 
    /*some other code*/
}

【讨论】:

  • 只是因为原始版本的代码并不是一直异步的。程序员重构了那里的内容,希望减少重复代码。我同意这不是最优的。我可以继续重构以实现这一点,但我不确定这是否能解决我遇到的错误。
猜你喜欢
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-12
  • 2014-08-03
  • 1970-01-01
  • 2012-05-11
  • 2011-12-29
相关资源
最近更新 更多