【问题标题】:Transferring large files: combining streamed transfer and content-length传输大文件:结合流传输和内容长度
【发布时间】:2014-04-08 20:54:38
【问题描述】:

TL;DR:如何使用 WCF 流式传输已知大小的大文件,并且仍向最终用户(网络浏览器)显示进度 (Content-Length)?

我有一个 WCF REST 服务,它下载然后将非常大的文件 (1-20Gb) 提供给 Web 浏览器。为简化起见,将我的服务视为代理。这迫使我在绑定上设置TransferMode = StreamedTransferMode = StreamedResponse,否则最终客户端将不得不等待源文件下载到网络服务器,然后才能开始实际下载。此外,缓冲传输模式会杀死服务器以获取大文件(RAM 使用)。中间磁盘存储不是一种选择。来自TransferMode 手册页:

(...) 缓冲传输将整个消息保存在内存缓冲区中,直到 传输完成。

但是当将TransferMode设置为StreamedStreamedResponse时,WCF不再向客户端返回标头Content-length,而是添加了一个新的标头Transfer-Encoding: chunked。这与分块传输上的wikipedias article 一致:

(...) 在 Content-Length 的位置使用 Transfer-Encoding HTTP 标头 标题(...)

但我总是事先知道要传输的数据的大小,而对于最终用户来说,不知道下载的大小是非常令人沮丧的。所以:

(如何)我可以将 WCF 绑定配置为使用“流式”传输模式(更具体地说,在发送前缓冲整个消息)并且仍然使用Content-Length 标头吗?

一些线索:

  • This q/a 声明 http 标准不允许在同一条消息中同时使用 Transfer-EncodingContent-length: 123456,所以我想这不是一个选项?

  • 我曾尝试在IDispatchMessage.BeforeSendReply 中使用检查器修改标头,但此时Content-Length 标头尚未删除,Transfer-Encoding 尚未设置,因此“为时过早” .我后来读到分块传输编码是在 TCP 级别上,所以即使我可以,此时更改标头也可能不起作用。

  • 我尝试设置aspNetCompatibilityEnabled="true",将wcf传输模式设置为缓冲输出(默认),然后设置System.Web.HttpContext.Current.Response.BufferOutput = false;。虽然 wcf 忽略了这一点,但消息显然已被缓冲。

  • 根据this link,这似乎是一个缺失的功能。但是在某个地方可能仍然有一个古怪的解决方法..

【问题讨论】:

    标签: c# wcf http browser http-headers


    【解决方案1】:

    这是一个经过测试的解决方法,仅适用于 IIS 下的 WCF - 我还没有找到任何自托管服务的解决方案。

    简而言之 - 打开aspNetCompatibility,它使您可以在运行时访问System.Web.HttpContext.Current

    Web.config:

    (...)
        <system.serviceModel>
          <bindings>
              <webHttpBinding>
                  <binding transferMode="Streamed">
                  </binding>
              </webHttpBinding>
          </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    (...)
    </system.serviceModel>
    

    在您的返回 Stream 的服务函数中:

    HttpContext.Current.Response.Headers.Add("Content-Length", 
    contentLength.ToString());
    

    以下任何内容都将被忽略:

    罢工>

    WebOperationContext.Current.OutgoingResponse.Headers["Content-Length"] = 
            contentLength.ToString();
    

    就这么简单!信用转到Uffe Lausen's question on Msdn

    【讨论】:

    • 嗨@Tewr,您找到自托管服务的解决方案了吗?
    • 我从来没有这样做过。并不意味着没有——促使我写这个问题的项目在大约一年后迁移到了 WebAPI。 WebAPI 没有这个问题。 IMO 因为这实际上只是当您的客户端是浏览器时才会出现的问题,所以无论如何 WebAPI 都应该是首选解决方案。
    【解决方案2】:

    如果您可以控制客户端代码,则可以将数据长度写为响应流中的第一个字节,这就是我传输大文件的方法,但客户端必须知道第一个字节只是大小且不包含任何数据。

    @server 示例:

    public Stream GetBigData()
    {
        byte[] bigData = GetBigDataFromSomeWhere();
        var ms = new MemoryStream();
        var lengthInfo = BitConverter.GetBytes(bigData.Length);
        ms.Write(lengthInfo, 0, lengthInfo.Length);
        ms.Write(bigData , 0, bigData .Length);
        ms.Flush();
        ms.Position = 0;
        return ms;
    }
    

    @client 示例:

    using (var respStream = yourService.GetBigData())
    using (var resultStream = new MemoryStream())
    {
        //read first 4 bytes 
        var lengthBuffer = new byte[4];
        respStream.Read(lengthBuffer, 0, 4);
        var totalCount = BitConverter.ToInt32(lengthBuffer, 0);
        resultStream.Capacity = totalCount;
    
        //read rest
        var readcount = 0;
        var buffer = new byte[1024 * 32];
        while ((readcount = respStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            resultStream.Write(buffer, 0, readcount);
            UpdateProgress(readcount, totalCount)
        }
    }
    

    【讨论】:

    • 不幸的是,正如问题中所述(我会稍微强调一下,以便更清楚),客户端是一个网络浏览器。因此,客户端的任何解决方法都必须使用 javascript 或任何其他浏览器可用的技巧。虽然如果有人对我的问题回答“否”,我可能不得不考虑使用自定义下载器,它可能看起来像你的答案。
    猜你喜欢
    • 2022-07-02
    • 2020-09-19
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    • 1970-01-01
    • 2022-11-17
    • 2014-05-04
    • 1970-01-01
    相关资源
    最近更新 更多