TL; DR:如何使用WCF流式传输已知大小的大文件,并仍向最终用户(网络浏览器)显示进度(Content-Length
)?
我有一个WCF REST服务,可以下载然后将非常大的文件(1-20Gb)提供给Web浏览器。为简化起见,请将我的服务视为代理。这要求我在Binding上设置TransferMode = Streamed
或TransferMode = StreamedResponse
,或者在实际下载开始之前,最终客户端必须等待源文件下载到Web服务器。此外,缓冲传输模式会杀死服务器以获取大文件(RAM使用情况)。中间磁盘存储不是一种选择。来自TransferMode手册页:
(...)缓冲传输将整个消息保存在内存缓冲区中,直到 转移完成。
但是,在将TransferMode
设置为Streamed
或StreamedResponse
时,WCF不再将标头Content-length
返回给客户端,并添加了新的标头Transfer-Encoding: chunked
。这与分块传输的wikipedias article一致:
(...)使用Content-Length的Transfer-Encoding HTTP标头到位 标题(...)
但是我总是知道要预先传输的数据的大小,对于最终用户来说,不知道下载的大小是非常令人沮丧的。所以:
(如何)我可以配置WCF绑定以使用“流”传输模式(更具体地,不在发送之前缓冲整个消息)并仍使用Content-Length
标头?
一些线索:
This q/a声明http标准在同一条消息中不允许Transfer-Encoding
和Content-length: 123456
,所以我猜这不是一个选项?
我尝试使用IDispatchMessage.BeforeSendReply中的检查器修改标头,但此时尚未删除Content-Length
标头,并且尚未设置Transfer-Encoding
,所以它“太早了”。我后来读到,分块传输编码是在TCP级别上进行的,所以即使我可以,此时更改标头可能也不会工作。
我尝试设置aspNetCompatibilityEnabled ="true"
,将wcf传输模式设置为缓冲输出(默认值),然后设置System.Web.HttpContext.Current.Response.BufferOutput = false;
。但这被wcf忽略,消息显然是缓冲的。
根据this link,这似乎是一个缺失的功能。但某处可能还有一个古怪的解决方法..
答案 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)
}
}