通过ASHX处理程序支持可恢复的HTTP下载吗?

如何解决通过ASHX处理程序支持可恢复的HTTP下载吗?

| 我们正在通过ASP.NET中的ASHX处理程序提供应用程序设置的下载。 一位客户告诉我们,他使用了一些第三方下载管理器应用程序,而我们提供文件的方式目前不支持他的下载管理器应用程序的“恢复”功能。 我的问题是: 恢复下载的基本原理是什么?是否有某些HTTP GET请求告诉我开始的偏移量?     

解决方法

        恢复下载通常可以通过HTTP“ 0”头进行。例如,如果客户端只需要文件的第二个千字节,则它可能会发送标头“ 1”。 有关更多信息,请参见RFC的HTTP / 1.1页139。     ,        感谢icktoofay让我入门,这是一个完整的示例,可以节省其他开发人员一些时间: 磁盘示例
/// <summary>
/// Writes the file stored in the filesystem to the response stream without buffering in memory,ideal for large files. Supports resumable downloads.
/// </summary>
/// <param name=\"filename\">The name of the file to write to the HTTP output.</param>
/// <param name=\"etag\">A unique identifier for the content. Required for IE9 resumable downloads,must be a strong etag which means begins and ends in a quote i.e. \"\\\"6c132-941-ad7e3080\\\"\"</param>
public static void TransmitFile(this HttpResponse response,string filename,string etag)
{
    var request = HttpContext.Current.Request;
    var fileInfo = new FileInfo(filename);
    var responseLength = fileInfo.Exists ? fileInfo.Length : 0;
    var buffer = new byte[4096];
    var startIndex = 0;

    //if the \"If-Match\" exists and is different to etag (or is equal to any \"*\" with no resource) then return 412 precondition failed
    if (request.Headers[\"If-Match\"] == \"*\" && !fileInfo.Exists ||
        request.Headers[\"If-Match\"] != null && request.Headers[\"If-Match\"] != \"*\" && request.Headers[\"If-Match\"] != etag)
    {
        response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
        response.End();
    }

    if (!fileInfo.Exists)
    {
        response.StatusCode = (int)HttpStatusCode.NotFound;
        response.End();
    }

    if (request.Headers[\"If-None-Match\"] == etag)
    {
        response.StatusCode = (int)HttpStatusCode.NotModified;
        response.End();
    }

    if (request.Headers[\"Range\"] != null && (request.Headers[\"If-Range\"] == null || request.Headers[\"IF-Range\"] == etag))
    {
        var match = Regex.Match(request.Headers[\"Range\"],@\"bytes=(\\d*)-(\\d*)\");
        startIndex = Parse<int>(match.Groups[1].Value);
        responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? fileInfo.Length) - startIndex;
        response.StatusCode = (int)HttpStatusCode.PartialContent;
        response.Headers[\"Content-Range\"] = \"bytes \" + startIndex + \"-\" + (startIndex + responseLength - 1) + \"/\" + fileInfo.Length;
    }

    response.Headers[\"Accept-Ranges\"] = \"bytes\";
    response.Headers[\"Content-Length\"] = responseLength.ToString();
    response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output
    response.Cache.SetETag(etag); //required for IE9 resumable downloads
    response.TransmitFile(filename,startIndex,responseLength);
}

public void ProcessRequest(HttpContext context)
{
    var id = Parse<int>(context.Request.QueryString[\"id\"]);
    var version = context.Request.QueryString[\"v\"];
    var db = new DataClassesDataContext();
    var filePath = db.Documents.Where(d => d.ID == id).Select(d => d.Fullpath).FirstOrDefault();

    if (String.IsNullOfEmpty(filePath) || !File.Exists(filePath))
    {
        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
        context.Response.End();
    }

    context.Response.AddHeader(\"content-disposition\",\"filename=\" + Path.GetFileName(filePath));
    context.Response.ContentType = GetMimeType(filePath);
    context.Response.TransmitFile(filePath,version);
}
数据库实例
/// <summary>
/// Writes the file stored in the database to the response stream without buffering in memory,ideal for large files. Supports resumable downloads.
/// </summary>
/// <param name=\"retrieveBinarySql\">The sql to retrieve the binary data of the file from the database to be transmitted to the client. Parameters can be reffered to by {0} the index in the supplied parameter array.</param>
/// <param name=\"retrieveBinarySqlParameters\">The parameters used in the sql query. Specify null if no parameters are required.</param>
/// <param name=\"connectionString\">The connectring string for the sql database.</param>
/// <param name=\"contentLength\">The length of the content in bytes.</param>
/// <param name=\"etag\">A unique identifier for the content. Required for IE9 resumable downloads,must be a strong etag which means begins and ends in a quote i.e. \"\\\"6c132-941-ad7e3080\\\"\"</param>
/// <param name=\"useFilestream\">If the binary data is stored using Sql\'s Filestream feature set this to true to stream the file directly.</param>
public static void TransmitFile(this HttpResponse response,string retrieveBinarySql,object[] retrieveBinarySqlParameters,string connectionString,int contentLength,string etag,bool useFilestream)
{
    var request = HttpContext.Current.Request;
    var responseLength = contentLength;
    var buffer = new byte[4096];
    var startIndex = 0;

    //if the \"If-Match\" exists and is different to etag (or is equal to any \"*\" with no resource) then return 412 precondition failed
    if (request.Headers[\"If-Match\"] == \"*\" && contentLength == 0 ||
        request.Headers[\"If-Match\"] != null && request.Headers[\"If-Match\"] != \"*\" && request.Headers[\"If-Match\"] != etag)
    {
        response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
        response.End();
    }

    if (contentLength == 0)
    {
        response.StatusCode = (int)HttpStatusCode.NotFound;
        response.End();
    }

    if (request.Headers[\"If-None-Match\"] == etag)
    {
        response.StatusCode = (int)HttpStatusCode.NotModified;
        response.End();
    }

    if (request.Headers[\"Range\"] != null && (request.Headers[\"If-Range\"] == null || request.Headers[\"IF-Range\"] == etag))
    {
        var match = Regex.Match(request.Headers[\"Range\"],@\"bytes=(\\d*)-(\\d*)\");
        startIndex = Parse<int>(match.Groups[1].Value);
        responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? contentLength) - startIndex;
        response.StatusCode = (int)HttpStatusCode.PartialContent;
        response.Headers[\"Content-Range\"] = \"bytes \" + startIndex + \"-\" + (startIndex + responseLength - 1) + \"/\" + contentLength;
    }

    response.Headers[\"Accept-Ranges\"] = \"bytes\";
    response.Headers[\"Content-Length\"] = responseLength.ToString();
    response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output
    response.Cache.SetETag(etag); //required for IE9 resumable downloads
    response.BufferOutput = false; //don\'t load entire data into memory (buffer) before sending

    if (!useFilestream)
    {
        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();
            var command = new SqlCommand(retrieveBinarySql,connection);

            for (var i = 0; retrieveBinarySqlParameters != null && i < retrieveBinarySqlParameters.Length; i++)
            {
                command.Parameters.AddWithValue(\"p\" + i,retrieveBinarySqlParameters[i]);
                command.CommandText = command.CommandText.Replace(\"{\" + i + \"}\",\"@p\" + i);
            }

            var reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
            if (!reader.Read())
            {
                response.StatusCode = (int)HttpStatusCode.NotFound;
                response.End();
            }

            for (var i = startIndex; i < contentLength; i += buffer.Length)
            {
                var bytesRead = (int)reader.GetBytes(0,i,buffer,buffer.Length);
                response.OutputStream.Write(buffer,bytesRead);
            }
        }
    }
    else
    {
        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();
            var tran = connection.BeginTransaction(IsolationLevel.ReadCommitted);
            var command = new SqlCommand(Regex.Replace(retrieveBinarySql,@\"select \\w+ \",v => v.Value.TrimEnd() + \".PathName(),GET_FILESTREAM_TRANSACTION_CONTEXT() \"),connection);
            command.Transaction = tran;

            for (var i = 0; retrieveBinarySqlParameters != null && i < retrieveBinarySqlParameters.Length; i++)
            {
                command.Parameters.AddWithValue(\"p\" + i,\"@p\" + i);
            }

            var reader = command.ExecuteReader();
            if (!reader.Read())
            {
                response.StatusCode = (int)HttpStatusCode.NotFound;
                response.End();
            }

            var path = reader.GetString(0);
            var transactionContext = (byte[])reader.GetValue(1);

            using (var fileStream = new SqlFileStream(path,transactionContext,FileAccess.Read,FileOptions.SequentialScan,0))
            {
                fileStream.Seek(startIndex,SeekOrigin.Begin);
                int bytesRead;
                do
                {
                    bytesRead = fileStream.Read(buffer,buffer.Length);
                    response.OutputStream.Write(buffer,bytesRead);
                }
                while (bytesRead == buffer.Length);
            }

            tran.Commit();
        }
    }
}

public void ProcessRequest(HttpContext context)
{
    var id = Parse<int>(context.Request.QueryString[\"id\"]);
    var db = new DataClassesDataContext();
    var doc = db.Documents.Where(d => d.ID == id).Select(d => new { d.Data.Length,d.Filename,d.Version }).FirstOrDefault();

    if (doc == null)
    {
        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
        context.Response.End();
    }

    context.Response.AddHeader(\"content-disposition\",\"filename=\" + doc.Filename);
    context.Response.ContentType = GetMimeType(doc.Filename);
    context.Response.TransmitFile(\"select data from documents where id = {0}\",new[] { id },db.Connection.ConnectionString,doc.Length,doc.Version,false);
}
辅助方法
public static T Parse<T>(object value)
{
    //convert value to string to allow conversion from types like float to int
    //converter.IsValid only works since .NET4 but still returns invalid values for a few cases like NULL for Unit and not respecting locale for date validation
    try { return (T)System.ComponentModel.TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(value.ToString()); }
    catch (Exception) { return default(T); }
}

public string GetMimeType(string fileName)
{
    //note use version 2.0.0.0 if .NET 4 is not installed,in .NET 4.5 this method has now been made public,this method apparently stores a list of mime types which would be more complete then using registry
    return (string)Assembly.Load(\"System.Web,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a\")
        .GetType(\"System.Web.MimeMapping\")
        .GetMethod(\"GetMimeMapping\",BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
        .Invoke(null,new object[] { fileName });
}
这说明了一种从磁盘或数据库中读取部分文件并作为响应输出而不是将整个文件加载到内存中的方法,如果在下载过程中途暂停或恢复下载,则会浪费资源。 编辑:添加了etag以在IE9中启用可恢复的下载,这要感谢EricLaw为使它在IE9中正常工作所提供的帮助。     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res