传输大文件:组合流传输和内容长度

时间:2014-03-06 09:53:51

标签: c# wcf http browser http-headers

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

我有一个WCF REST服务,可以下载然后将非常大的文件(1-20Gb)提供给Web浏览器。为简化起见,请将我的服务视为代理。这要求我在Binding上设置TransferMode = StreamedTransferMode = StreamedResponse,或者在实际下载开始之前,最终客户端必须等待源文件下载到Web服务器。此外,缓冲传输模式会杀死服务器以获取大文件(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,所以它“太早了”。我后来读到,分块传输编码是在T​​CP级别上进行的,所以即使我可以,此时更改标头可能也不会工作。

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

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

2 个答案:

答案 0 :(得分:2)

这是经过测试的解决方法,仅适用于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

答案 1 :(得分:0)

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

示例@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)
    }
}