HTTP 下载文件的一些记录

  最近用 HttpWebRequest 做了个文件下载, HTTP 可以提供一些头信息的返回, 不过本身不带MD5之类的文件效验信息, 在做下载前可以跟本地文件进行一个对比, 如果跟本地文件相同的话就不需要下载了.

  HTTP的头返回中有两个有用信息 : ContentLength(文件大小) 和 "Last-Modified"(上次操作文件的时间).

  ContentLength : 如果跟本地文件的长度不一样, 那就是不同了, 可以直接下载.

  Last-Modified : 文件上次操作的时间, 这个不是很有用, 比如说有两个版本的文件 [ABC] 的1.0和2.0版本, 它们的长度相同, 然后放到服务器上的时间也相同, 它们的 Last-Modified 是一样的, 这样如果有版本控制需要按顺序下载的话, 1.0的先下载下来了, 本地文件的FileInfo中不管是 LastAccessTime 还是 CreationTime 都是本地写入文件的时间, 然后在更新2.0版本的时候时间对比就失效了...

  所以上面两个属性的对比都是不靠谱的, 只能作为辅助, 我们最需要的是文件的MD5, 之前想的是写一个接口能够计算并返回文件MD5, 然后用PHP之类的配置很简单就能用的话, 不管哪种系统都能方便用起来了不是, 可是我们想要最简洁的方案, 能不配环境就不配, 能不写代码就不写, 只需添加一个版本MD5文件就行了, 包含该版本所有文件的MD5信息, 跟Unity的AssetBundleManifest一样, 当版本不同的时候就先下载这个MD5列表, 然后跟本地文件对比, 不同的就下载即可.

  -- 代码 --

     public static async Task<FileUpdateInfo> CheckFileNeedUpdate(string url, string localPath, int minGapSec = 10, string md5 = null,
            CatchExceptionEnum catchExceptionEnum = CatchExceptionEnum.None)
        {
            var info = new FileUpdateInfo() { needUpdate = false, url = url, fileLength = 0, acceptRanges = false, remoteMD5 = md5 };

            // check MD5
            if(string.IsNullOrEmpty(info.remoteMD5) == false)
            {
                info.localMD5 = GetMD5HashFromFile(localPath);
                if(string.IsNullOrEmpty(info.localMD5) == false && string.Equals(info.localMD5, info.remoteMD5, StringComparison.OrdinalIgnoreCase))
                {
                    return info;
                }
            }

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            try
            {
                request.ServicePoint.Expect100Continue = false;//解决第二次请求失败问题
                request.Method = "HEAD";
                var response = await request.GetResponseAsync() as HttpWebResponse;
                if(response.StatusCode == HttpStatusCode.OK)
                {
                    info.fileLength = response.ContentLength;               // get length
                    var acceptRanges = response.Headers.Get(AcceptRanges);  // get can use ranged
                    info.acceptRanges = acceptRanges.Equals("bytes", StringComparison.OrdinalIgnoreCase);

                    FileInfo fileInfo = new FileInfo(localPath);
                    if(fileInfo.Exists == false || fileInfo.Length != response.ContentLength)
                    {
                        info.needUpdate = true;
                        return info;
                    }
                    var date = response.Headers.Get(LastModified);
                    var serverFileWriteTime = System.DateTime.Parse(date);
                    var span = serverFileWriteTime - fileInfo.CreationTime;
                    if(span.TotalSeconds >= minGapSec)
                    {
                        info.needUpdate = true;
                        return info;
                    }
                    // last check MD5
                    if(string.IsNullOrEmpty(info.remoteMD5))
                    {
                        info.needUpdate = true;
                        return info;
                    }
                    if(string.Equals(string.IsNullOrEmpty(info.localMD5) ? (info.localMD5 = GetMD5HashFromFile(localPath)) : info.localMD5, info.remoteMD5, StringComparison.OrdinalIgnoreCase) == false)
                    {
                        info.needUpdate = true;
                        return info;
                    }
                }
            }
            catch(System.Exception ex)
            {
                if((catchExceptionEnum & CatchExceptionEnum.ShowCached) != 0)
                {
                    Common.Debug.LogError(ex.Message);
                }
                if((catchExceptionEnum & CatchExceptionEnum.Catch) != 0)
                {
                    throw ex;
                }
            }
            finally
            {
                request.Abort();
            }

            return info;
        }

  这里还是有几个注意点的, request.ServicePoint.Expect100Continue = false; 和 request.Abort(); 才能保证发起多次请求不被服务器卡住, 如果不加的话只能从服务器下载一个文件, 然后之后的请求都没有响应了, 很危险.

  首先是MD5对比, 如果传入了服务器上的文件的MD5, 跟本地的对比, 如果文件一致的话就不用下载了.

  然后是获取HEAD信息, 因为我使用的是分段下载的方式, 需要获得服务器是否支持分段传输, 头信息中有个 "Accept-Ranges" 信息, 如果类型是bytes就是支持分段传输了, 断点续传就是基于它的.

  之后的长度对比和文件操作时间对比, 也能算是文件对比了.

  最后的MD5对比, 证明确实需要更新文件...

  就这样如果是需要更新的文件, 会返回得到新文件的长度信息, 可以按照需求进行下载了.

 

  Unity 的 WWW.LoadFromCacheOrDownload() 是怎样实现远程文件比对的呢, 好奇

 

HTTP 下载文件的一些记录

上一篇:ASP.Net Core管道和注册中间件精简版


下一篇:tp5上传阿里云oss并对接阿里云检测人脸(DetectFace)