首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >asp.net web api 下载之断点续传

asp.net web api 下载之断点续传

作者头像
甜橙很酸
发布2018-03-08 11:20:33
1K0
发布2018-03-08 11:20:33
举报
文章被收录于专栏:DOTNETDOTNET

一、基本思想

利用 HTTP 请求的Range标头值,来向服务端传递请求数据的开始位置和结束位置。服务端获得这两个参数后,将指定范围内的数据传递给客户端。当客户端请求暂停或中断之后,待到客户端再次向服务器发起请求,继续下载数据时,客户端传递给服务端的Range值说明了向服务端请求数据的范围,即从上一次中断传输的位置开始直到最后。

二、示例代码

1 DownloadCore:完成下载任务

public class DownloadCore<T>
    {
        private HttpRequestMessage request;
        private IFileProvider<T> fileProvider;
        private T requestModel;
        public DownloadCore(HttpRequestMessage request,IFileProvider<T> fileProvider,T requestModel)
        {
            this.request = request;
            this.fileProvider = fileProvider;
            this.requestModel = requestModel;
        }
        public HttpResponseMessage Download()
        {
            if (requestModel == null)
            {
                //抛出异常
            }
            if (!fileProvider.Exists(requestModel))
            {
                //抛出异常            }
            long fileLength = fileProvider.GetLength(requestModel);
            if (fileLength == 0)
            {
                //抛出异常 
            }
            ContentInfo contentInfo = GetContentInfoFromRequest(fileLength);
            Stream stream = PartialStream.GetPartialStream(fileProvider.Open(requestModel), contentInfo.From, contentInfo.To);
            if (stream == null)
            {
                //抛出异常 
       }
            HttpContent content = new StreamContent(stream, AppSettings.BufferSize);
            return SetResponse(content, contentInfo, fileLength, fileProvider.GetFileName(requestModel));
        }
        private ContentInfo GetContentInfoFromRequest(long entityLength)
        {
            var result = new ContentInfo
            {
                From = 0,
                To = entityLength - 1,
                IsPartial = false,
                Length = entityLength
            };
            RangeHeaderValue rangeHeader = request.Headers.Range;
            if (rangeHeader != null && rangeHeader.Ranges.Count != 0)
            {
                //仅支持一个range
                if (rangeHeader.Ranges.Count > 1)
                {
                    //抛出异常 
                }
                RangeItemHeaderValue range = rangeHeader.Ranges.First();
                if (range.From.HasValue && range.From < 0 || range.To.HasValue && range.To > entityLength - 1)
                {
                    //抛出异常 
                }

                result.From = range.From ?? 0;
                result.To = range.To ?? entityLength - 1;
                result.IsPartial = true;
                result.Length = entityLength;

                if (range.From.HasValue && range.To.HasValue)
                {
                    result.Length = range.To.Value - range.From.Value + 1;
                }
                else if (range.From.HasValue)
                {
                    result.Length = entityLength - range.From.Value;
                }
                else if (range.To.HasValue)
                {
                    result.Length = range.To.Value + 1;
                }
            }
            return result;
        }

        private HttpResponseMessage SetResponse(HttpContent content, ContentInfo contentInfo, long entityLength, string fileNameWithExtend)
        {
            HttpResponseMessage response = new HttpResponseMessage() ;
            response.Headers.AcceptRanges.Add("bytes");
            response.StatusCode = contentInfo.IsPartial ? HttpStatusCode.PartialContent : HttpStatusCode.OK;
            response.Content = content;
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = fileNameWithExtend;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            response.Content.Headers.ContentLength = contentInfo.Length;
            if (contentInfo.IsPartial)
            {
                response.Content.Headers.ContentRange = new ContentRangeHeaderValue(contentInfo.From, contentInfo.To, entityLength);
            }
            return response;
        }
}

网络请求参数解析

public static DownloadUriModel GetDownloadParam(HttpRequestMessage request)
        {
            var uriQuery = request.RequestUri.Query;
            //针对中文重新编码
            uriQuery = HttpUtility.UrlDecode(uriQuery);

            var queryChildren = uriQuery.Substring(1, uriQuery.Length - 1).Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            Dictionary<string, string> queryDict = new Dictionary<string, string>();
            queryChildren.ForEach(item =>
            {
                var child = item.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
                queryDict.Add(child[0], child[1]);
            });          

            string fileName = queryDict.ContainsKey("fileName") ? queryDict["fileName"] : null;
            if (string.IsNullOrWhiteSpace(fileName))
            {
                //抛出异常             
       }
            string extend = Path.GetExtension(fileName);
            if (!string.IsNullOrWhiteSpace(extend))
            {
                fileName = Path.GetFileNameWithoutExtension(fileName);
            }

            string fileType = queryDict.ContainsKey("fileType") ? queryDict["fileType"] : null;
            if (string.IsNullOrWhiteSpace(fileType))
            {
                //抛出异常 
            }

            return new DownloadUriModel
            {
                FileNameNoExtend = fileName,
                FileType = fileType,                
            };
        }

部分流数据

public class PartialStream
    {
        public static Stream GetPartialStream(Stream fileStream, long start, long end)
        {
            if (fileStream == null)
            {
                return null;
            }
            if (start > 0)
            {
                fileStream.Seek(start, SeekOrigin.Begin);
            }

            return fileStream;
        }
}

2 数据

下载数据的来源包括本地磁盘,网络,数据库等,这里只列举待下载数据在本地磁盘和网络的情形。

本地磁盘

public class DiskFileProvider : IFileProvider<DownloadRequestModel>
    {
        /// <summary>
        /// android app 所在文件夹路径
        /// </summary>
        private readonly string filesDirectory;

        public DiskFileProvider()
        {
            filesDirectory = AppSettings.AppLocation;
        }

        public bool Exists(DownloadRequestModel model)
        {
            string searchPattern = string.Format("{0}.{1}",model.FileNameNoExtend, model.FileType);
            string file = Directory.GetFiles(filesDirectory, searchPattern, SearchOption.TopDirectoryOnly)
                    .FirstOrDefault();
            return file != null;
        }

        public Stream Open(DownloadRequestModel model)
        {
            return File.Open(GetFilePath(model), FileMode.Open, FileAccess.Read,FileShare.Delete);
        }

        public long GetLength(DownloadRequestModel model)
        {
            return new FileInfo(GetFilePath(model)).Length;
        }

        private string GetFilePath(DownloadRequestModel model)
        {
            string searchPattern = string.Format("{0}.{1}", model.FileNameNoExtend, model.FileType);
            return Path.Combine(filesDirectory, searchPattern);
        }


        public string GetFileName(DownloadRequestModel model)
        {
            return model.FileNameWithExtend;
        }
}

网络

public class NetFileProvider: IFileProvider<DownloadUriModel>
{
public bool Exists(DownloadUriModel model)
{
//具体实现
}


public Stream Open(DownloadUriModel model)
{
//具体实现
}

......
}

统一的接口

public interface IFileProvider<T>
{
        bool Exists(T model);
        Stream Open(T model);
        long GetLength(T model);
        string GetFileName(T model);
}

3 数据模型

public class DownloadRequestModel
{
        /// <summary>
        /// 文件名(不含扩展名)
        /// </summary>
        public string FileNameNoExtend { get; set; }
        /// <summary>
        /// 文件类型(扩展名)
        /// </summary>
        public string FileType { get; set; }
        /// <summary>
        /// 文件名
        /// </summary>
        public string FileNameWithExtend
        {
            get 
            {
                string extend = FileType.Contains(".") ? FileType.Substring(1) : FileType;
                return string.Format("{0}.{1}", FileNameNoExtend, extend);
            }
        }
        
}

//扩展实现
public class DownloadUriModel:DownloadRequestModel
{
......

}

public class ContentInfo
{
        public long From;
        public long To;
        public bool IsPartial;
        public long Length;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-11-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档