【问题标题】:ASP.Net Web API doesn't read all bytes from StreamContentASP.Net Web API 不会从 StreamContent 读取所有字节
【发布时间】:2019-03-01 09:34:17
【问题描述】:

我在我的网站上设置了一个 ASP.Net Web API,用于与 WPF 桌面应用程序进行通信。我在 API 上有一个操作设置,用于从客户端应用程序接收二进制文件。但是,在某些(看似随机的)情况下,当我从请求中获取所有字节时,并非所有字节都被读取。希望你能给我一个关于如何以一种始终有效的方式来做到这一点的想法。代码如下:

客户端:

public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
    HttpClient client = CreateHttpClient();

    HttpContent content = new StreamContent(fileStream);
    content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    content.Headers.ContentDisposition.FileName = "new-turn.Civ5Save";
    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    content.Headers.ContentLength = fileStream.Length;

    HttpResponseMessage response = client.PostAsync(
        string.Format("SubmitTurn?authKey={0}&turnId={1}",
                        LocalSettings.Instance.AuthenticationKey,
                        turnId
                        ),
        content
    ).Result;

    response.EnsureSuccessStatusCode();

    return response.Content.ReadAsAsync<SubmitTurnResult>().Result;
}

SubmitTurnResult 是一个枚举,它定义了服务器上的结果,turnId 是这个文件附加到的实体的 ID,fileStream 是读取磁盘字节的实际 FileStream。

服务器端:

[HttpGet, HttpPost]
public SubmitTurnResult SubmitTurn(string authKey, int turnId)
{

    try
    {
        bool worked = false;
        int gameId = 0;

        using (GmrEntities gmrDb = new GmrEntities())
        {
            var player = gmrDb.Users.FirstOrDefault(u => u.AuthKey == authKey);
            if (player != null)
            {
                var turn = player.Turns.FirstOrDefault(t => t.TurnID == turnId);
                if (turn != null)
                {
                    byte[] saveFileBytes = null;

                    using (MemoryStream tempStream = new MemoryStream())
                    {
                        var task = this.Request.Content.CopyToAsync(tempStream);
                        task.Wait();

                        saveFileBytes = tempStream.ToArray();
                        tempStream.Close();
                    }

                    if (saveFileBytes.Length != this.Request.Content.Headers.ContentLength.Value)
                    {
                        throw new Exception(string.Format("Byte array length ({0}) not equal to HTTP content-length header ({1}). This is not good!",
                                    saveFileBytes.Length, this.Request.Content.Headers.ContentLength.Value));
                    }

                    worked = GameManager.SubmitTurn(turn, saveFileBytes, gmrDb);

                    if (worked)
                    {
                        gameId = turn.Game.GameID;

                        gmrDb.SaveChanges();
                    }
                }
            }
        }


        return SubmitTurnResult.OK;
    }
    catch (Exception exc)
    {
        DebugLogger.WriteExceptionWithComments(exc, string.Format("Diplomacy: Sumbitting turn for turnId: {0}", turnId));

        return SubmitTurnResult.UnexpectedError;
    }
}

【问题讨论】:

  • 这是在 32 位 Windows Server 2003 还是 Windows XP 上?我们在使用 StreamContent 时遇到了同样的行为,但是在流式传输来自 Windows Server 2003 Web API 服务的响应时。它不会在 2008 年重现。此外,我们发现这只重现了 FileStream。将 FileStream 转换为 MemoryStream 绕过了这个问题(当然以内存为代价)。我们发现,当响应流提前终止时,它总是在 4096 字节的边界上,并且达到了大约 3.5MB 的上限。

标签: c# file-upload asp.net-mvc-4 asp.net-web-api


【解决方案1】:

正如我之前的评论中所指出的,我们在 StreamContent 中遇到了同样的行为,但在流式传输来自 Windows Server 2003 Web API 服务的响应时。 它不会在 2008 年重现。 实际上,如果我用少量 RAM (712 MB) 配置 VM,它也可以在 Windows Server 2008 上重现,但如果使用 4 GB 的 RAM,它不会复制品。此外,我们发现这仅适用于 FileStream。将 FileStream 转换为 MemoryStream 绕过了这个问题(当然以内存为代价)。我们发现,当响应流提前终止时,它总是在 4096 字节的边界上,并且达到了大约 3.5MB 的上限。

这是为我解决问题的解决方法,根据您的代码示例量身定制:

public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
    HttpClient client = CreateHttpClient();

    var memoryStream = new MemoryStream((int)fileStream.Length);
    fileStream.CopyTo(memoryStream);
    fileStream.Close();
    memoryStream.Seek(0, SeekOrigin.Begin);
    HttpContent content = new StreamContent(memoryStream);

如果需要,您可以有条件地仅在 Stream 是 FileStream 时进行 MemoryStream 复制。

【讨论】:

  • 感谢您的建议,遗憾的是它并没有为我解决问题。代码的客户端部分运行在从 WinXP 到 Win8.1 的数百台机器上,我已将其更改为使用 MemoryStream 而不是 FileStream。它的服务器部分在 WinServer 2012R2 上运行。我仍然不时遇到这个问题,服务器从内容流中读取的字节数与客户端发送的字节数不匹配=\
  • 这真的很不幸,对于我目前看来对我有用的“解决方案”来说并不是一个好兆头。在您的情况下,最好的办法是在 HTTP 响应不成功时构建客户端重试逻辑。或者向 Microsoft 提交错误报告,最好使用可以重现问题的测试用例,可能通过在循环中重复运行。如果你有一些可以复制的东西,你可以打电话给微软产品支持服务,给他们你的信用卡,如果他们确认这确实是他们的错误,他们就不会收费。这很耗时,但它确实有效。祝你好运!
猜你喜欢
  • 2016-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-03
  • 2014-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多