直接贴代码啦
概述
不管是下载还是上传,断点的时候,就需要对文件流进行精确的操作。
1、下载断开了,已经下载的数据保存到文件,再次继续下载的时候需要从文件的尾巴继续追加数据;
2、同理上传也是一样,http通信中有可能断开或者丢包的情况,就需要重传指定的文件片;
实现原理
http协议知识
当请求一个html页面时我们会看到请求页面如下:
第一眼看到上面Accept中的参数时我是懵逼的,之前也就看看缓存cookie等常见的头信息,于是借此机会也学习下这部分内容。
我们知道Accept是指客户端允许请求返回的内容类型,那为何这里面参数有如此之多呢?在学习WebAPi时,我们在服务端未进行过滤时既可以返回xml,也可以返回json,此时如上图一样,text/html未匹配上,接着匹配xml类型,匹配后则进行相应格式内容返回,所以客户端接受如此多类型内容,也是为了服务端那边未设置特定内容响应,此时则根据客户端设置的内容进行最合适的匹配。
上面给出了客户端能够接受响应的内容类型,自然就有最合适的匹配,此时就用到了q这个参数,在此我将q翻译为quality即权重的意思,应该是比较合适的,它用来表示我们期待接受内容偏爱的程度即所占的权重。它的范围是0-1,其默认值为1,这就类似质检部门对产品合格判断的一种介质。例如当我们需要返回视频资源时,我们客户端设置为如下:
Accept: audio/*; q=0.2, audio/basic
此时我们将上述翻译如下:
audio/basic; q=1
audio/*; q=0.2
我们更加期待返回的是audio/basic类型的资源,因为其权重为1大于audio/类型的资源,若为匹配到则继续匹配下一个资源,audio/则表示属于audio类型的所有子类型资源。
接下来,我们再来看一个例子:
Accept: text/plain; q=0.5, text/html,text/x-dvi; q=0.8, text/x-c
此时我们则可以翻译为如下:
Accept:
text/html;q=1或者 text/x-c;q=1
text/x-dvi; q=0.8
text/plain; q=0.5
倾向于返回text/html或者text/x-c类型资源,若都不存在,则返回权重为0.8的text/x-dvi,最终还是不存在则返回text/plain。
Accept-Ranges
在响应头中添加此字段允许服务端来显示表明对资源范围的接受。如果服务端接受一个字节范围的资源的请求则此时变成如下:
Accept-Ranges: bytes
如果服务端不接受任何范围的请求资源此时则在响应头添加如下来告诉客户端不要发送范围请求的资源:
Accept-Ranges: none
Content-Range
当在响应头中添加接受字节范围的资源时,此时若客户端请求资源文件比较大时即只是返回部分数据时,此时则返回状态码为206的部分内容,在Content-Range响应头信息中实时显示当前数据的进度。比如如下:
复制代码
//开始500个字节数据
Content-Range: bytes 0-499/1234
//第二个500个字节数据
Content-Range: bytes 500-999/1234
//除了开始500个字节之外的数据
Content-Range: bytes 500-1233/1234
//最后500个字节数据(表示数据最终传输完毕)
Content-Range: bytes 734-1233/1234
复制代码
如果客户端请求资源到达所给资源的界限此时则返回416的状态码。
using BLL;
using BLL.Caller;
using BLL.Proc;
using BlueWave.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
namespace WebApi.JessyXu.Controllers
{
/// <summary>
/// 文件上传
/// </summary>
public class FileController : BaseController
{
string RootPath = System.Configuration.ConfigurationManager.AppSettings.Get("BaseFilePath");
string PrintRelativePath = Path.Combine("PrintManage", "PrintFiles", DateTime.Now.ToString("yyyy-MM-dd"));
string BurnRelativePath = Path.Combine("BurnManage", "BurnFiles", DateTime.Now.ToString("yyyy-MM-dd"));
string ScanRelativePath = Path.Combine("PrintManage", "ScanFiles", DateTime.Now.ToString("yyyy-MM-dd"));
string CopyRelativePath = Path.Combine("PrintManage", "CopyFiles", DateTime.Now.ToString("yyyy-MM-dd"));
[HttpGet]
public HttpResponseMessage GetResumFile()
{
string filepath = string.Empty;
var md5str = HttpContext.Current.Request.QueryString["md5str"];
//用于获取当前文件是否是续传。和续传的字节数开始点。
filepath = Path.Combine(RootPath, md5str);
if (System.IO.File.Exists(filepath))
{
var fs = System.IO.File.OpenWrite(filepath);
var fslength = fs.Length.ToString();
fs.Close();
return new HttpResponseMessage { Content = new StringContent(fslength, System.Text.Encoding.UTF8, "text/plain") };
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
/// <summary>
/// 保存文件的方法
/// </summary>
/// <returns></returns>
[HttpPost]
public HttpResponseMessage Rsume()
{
var file = HttpContext.Current.Request.InputStream;
var filename = HttpContext.Current.Request.QueryString["filename"];
var appType = HttpContext.Current.Request.QueryString["AppType"];
string realPath = string.Empty;
switch (appType)
{
case "0":
realPath = Path.Combine(RootPath, PrintRelativePath);//打印
HttpContext.Current.Response.AddHeader("AttcPath", Path.Combine( PrintRelativePath,filename));
break;
case "1":
realPath = Path.Combine(RootPath, BurnRelativePath);//刻录
HttpContext.Current.Response.AddHeader("AttcPath", Path.Combine(BurnRelativePath, filename));
break;
case "2":
realPath = Path.Combine(RootPath, ScanRelativePath);//扫描
HttpContext.Current.Response.AddHeader("AttcPath", Path.Combine(ScanRelativePath, filename));
break;
case "3":
realPath = Path.Combine(RootPath, CopyRelativePath);//复印
HttpContext.Current.Response.AddHeader("AttcPath", Path.Combine(CopyRelativePath, filename));
break;
default:
return new HttpResponseMessage(HttpStatusCode.Forbidden);
}
if (!Directory.Exists(realPath))
{
Directory.CreateDirectory(realPath);
}
this.SaveAs(Path.Combine(realPath, filename), file);
HttpContext.Current.Response.StatusCode = 200;
// For compatibility with IE's "done" event we need to return a result as well as setting the context.response
return new HttpResponseMessage(HttpStatusCode.OK);
}
/// <summary>
/// 保存到真实的文件
/// </summary>
/// <param name="saveFilePath"></param>
/// <param name="stream"></param>
private void SaveAs(string saveFilePath, System.IO.Stream stream)
{
long lStartPos = 0;
int startPosition = 0;
int endPosition = 0;
var contentRange = HttpContext.Current.Request.Headers["Content-Range"];
//bytes 10000-19999/1157632
if (!string.IsNullOrEmpty(contentRange))
{
contentRange = contentRange.Replace("bytes", "").Trim();
contentRange = contentRange.Substring(0, contentRange.IndexOf("/"));
string[] ranges = contentRange.Split('-');
startPosition = int.Parse(ranges[0]);
endPosition = int.Parse(ranges[1]);
}
System.IO.FileStream fs;
if (System.IO.File.Exists(saveFilePath))
{
fs = System.IO.File.OpenWrite(saveFilePath);
lStartPos = fs.Length;
}
else
{
fs = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create);
lStartPos = 0;
}
if (lStartPos > endPosition)
{
fs.Close();
return;
}
else if (lStartPos < startPosition)
{
lStartPos = startPosition;
}
else if (lStartPos > startPosition && lStartPos < endPosition)
{
lStartPos = startPosition;
}
fs.Seek(lStartPos, System.IO.SeekOrigin.Current);
byte[] nbytes = new byte[512];
int nReadSize = 0;
nReadSize = stream.Read(nbytes, 0, 512);
while (nReadSize > 0)
{
fs.Write(nbytes, 0, nReadSize);
nReadSize = stream.Read(nbytes, 0, 512);
}
fs.Close();
return;
}
}
}