• [开源应用]利用HTTPHandler+resumableJs+HTML5实现拖拽上传[大]文件


    前言:

    大文件传输一直是技术上的一大难点。文件过大时,一些性提交所有的内容进内存是不现实的。大文件带来问题还有是否支持断点传输和多文件同时传输。

    本文以resumableJs为例,介绍了如何在ASP.NET中实现大文件传输。同时本文利用了Html5的新特性:支持拖拽。

    本文的主要技术点在于:如何接收resumableJs的传送内容(官网不太清楚)和如何合并文件,难度并不高。如果要改为MVC中的Controller处理文件传输,方法也大同小异。

    注:原博客中,此文章在原站点个人代码备份所用,注释不多,如有不懂,请在评论中给出。

    效果

    ASPX File:

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Resumable.js Test</title>
    </head>
    <body>
        <form id="form1" runat="server">     
        <div id="container" style="300px;height:200px;background-color:lightgray">        </div>
        </form>
         <span id="info">welcome</span>
        <script src="scripts/resumable.js" type="text/javascript"></script>
        <script  type="text/javascript">
            var showInfo = function (msg) {
                document.getElementById("info").innerHTML = msg;
            }
    
            showInfo("Test begin");
    
            var r = new Resumable({
                target: 'FileHandler.ashx',
            });
    
            r.assignBrowse(document.getElementById('container'));
            r.assignDrop(document.getElementById('container'));
    
            if (!r.support) showInfo("not support");
    
            r.on('fileAdded', function (file, event) {
                r.upload();
            });
            r.on('filesAdded', function (array) {
                for (var i = 0; i < array.length; i++) {
                    var html = document.getElementById("info").innerHTML;
                    html += "<br>"+array[i].name;
                }
            });
    
            r.on('uploadStart', function () {
                showInfo('start');
            });
            r.on('complete', function () {
                r.files.pop(); 
              //if want to upload one file multiple times, you should remove it from r.files after completing.
              //pop后,才可再次重新拖拽上传此文件。此机制可避免一次上传多个文件时重复添加,但拖拽上传时不用检测。
            });
            r.on('progress', function (e) {
                showInfo(r.progress());
            });
        </script>
    </body>
    </html>
    

    FileHandler

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web;
    
    namespace UploadTest
    {
        /// <summary>
        /// Summary description for FileHandler
        /// </summary>
        public class FileHandler : IHttpHandler
        {
            string _tempFolder;
            object _lock = new object();
    
            public void ProcessRequest(HttpContext context)
            {
                _tempFolder = context.Server.MapPath("~/temp");
    
                var method = context.Request.HttpMethod;
                if (method.Equals("GET"))
                {
                    HandleGet(context);
                }
                if (method.Equals("POST"))
                {
                    HandlePost(context);
                }
            }
    
            private  void HandlePost(HttpContext context)
            {
                var queryString = context.Request.Form;
                if (queryString.Count == 0) return;
    
                try
                {
                    // Read parameters
                    var uploadToken = queryString.Get("upload_Token");
                    int resumableChunkNumber = int.Parse(queryString.Get("resumableChunkNumber"));
                    var resumableTotalChunks = int.Parse(queryString.Get("resumableTotalChunks"));
                     var resumableTotalSize = long.Parse(queryString.Get("resumableTotalSize"));                
                    var resumableFilename = queryString.Get("resumableFilename");
    
                    // Save File
                    if (context.Request.Files.Count == 0)
                    {
                        context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; 
                    }
                    else
                    {
                        var filePath = string.Format("{0}/{1}/{1}.part{2}", _tempFolder, resumableFilename, resumableChunkNumber.ToString("0000"));
    
                        var directory = Path.GetDirectoryName(filePath);
                        if (File.Exists(directory))
                        {
                            File.Delete(directory);
                        }
                        if (!Directory.Exists(directory))
                        {
                            Directory.CreateDirectory(directory);
                        }
                        if (!System.IO.File.Exists(filePath))
                        {
                            context.Request.Files[0].SaveAs(filePath);
                        }
    
                        if (IsCompleted(directory,resumableTotalChunks,resumableTotalSize))
                        {
                            MergeFiles(directory);
                        }
                    }
                }
                catch (Exception exception)
                {
                    throw exception;
                }
            }
    
            private void HandleGet(HttpContext context)
            {
                var queryString = context.Request.QueryString;
                if (queryString.Count == 0) return;
    
                try
                {
                    // Read parameters
                    var uploadToken = queryString.Get("upload_Token");
                    int resumableChunkNumber = int.Parse(queryString.Get("resumableChunkNumber"));
                    var resumableFilename = queryString.Get("resumableFilename");
                    var resumableChunkSize = long.Parse(queryString.Get("resumableChunkSize")); 
    
                    var filePath = string.Format("{0}/{1}/{1}.part{2}", _tempFolder,
                         resumableFilename, resumableChunkNumber.ToString("0000")); 
    
                    // Check for existance and chunksize 
                    if (System.IO.File.Exists(filePath) && new FileInfo(filePath).Length == resumableChunkSize)
                    {
                        context.Response.Status = "200 OK";
                        context.Response.StatusCode = 200; 
                    }
                    else
                    {
                        context.Response.Status = "404 Not Found";
                        context.Response.StatusCode = 404;
                    }
                          
                }
                catch (Exception exception)
                {
                    throw exception;
                }
            }
    
            private bool IsCompleted(string directory,int numChunks, long totalSize )
            {
                var physicalFolder = Path.Combine(_tempFolder, directory);
                var files = Directory.GetFiles(physicalFolder);
    
                //numbers
                if (files.Length != numChunks)
                    return false;
    
                //files all exisit
                var fileName = Path.GetFileName(directory);
                for (int i = 1; i <= numChunks; i++)
                {
                    var filePath = string.Format("{0}/{1}.part{2}", directory, fileName, i.ToString("0000"));
                    if (!File.Exists(filePath))
                    {
                        return false;
                    }
                }
    
                //size 
                long tmpSize = 0;
                foreach (var file in files)
                {
                    tmpSize += new FileInfo(file).Length;
                } 
                return totalSize==tmpSize;
            }
    
            private void MergeFiles(string directoryPath)
            {      
                    lock (_lock)
                    {
                        if (Directory.Exists(directoryPath))
                        {
                            var fileName = Path.GetFileName(directoryPath);
                            var folder = Path.GetDirectoryName(directoryPath);
                            var tempPath = Path.Combine(directoryPath + ".tmp");
    
                            var files = Directory.GetFiles(directoryPath);
                            files = files.OrderBy(f => f).ToArray();
    
                            FileStream wholeStream = new FileStream(tempPath, FileMode.Append, FileAccess.Write);
                            for(int i=0;i<files.Length;i++)
                            {    
                                FileStream parcialStream = new FileStream(files[i], FileMode.Open);
                                BinaryReader parcialReader = new BinaryReader(parcialStream);
                                byte[] buffer = new byte[parcialStream.Length];
                                buffer = parcialReader.ReadBytes((int)parcialStream.Length);
                                BinaryWriter parcialWriter = new BinaryWriter(wholeStream);
                                parcialWriter.Write(buffer);
    
                                parcialStream.Close();
                            }
                            wholeStream.Close();
                            Directory.Delete(directoryPath,true);
                            File.Move(tempPath, directoryPath);                     
                        }
                    }                             
            }
    
            public bool IsReusable
            {
                get
                {
                    return false;
                }
            } 
        }
    }
    

    附录:

    1 技术难点

      a. 文件过大。修改webconfig无用。

      b. 断点续传。

      c. 多文件上传。

    2 resumable.js

        API: http://www.resumablejs.com/

        工作流程:

            拖文件至DIV -> 开始上传,uploadStart -> 反复触发progress事件 -> compete   

        主要参数:

    Get:

    resumableChunkNumber=1&
    resumableChunkSize=1048576&
    resumableCurrentChunkSize=1048576&
    resumableTotalSize=27778318&
    resumableType=&
    resumableIdentifier=27778318-Samples7z&
    resumableFilename=Samples.7z&
    resumableRelativePath=Samples.7z&
    resumableTotalChunks=26

    Post:

    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableChunkNumber”

    140
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableChunkSize”

    1048576
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableCurrentChunkSize”

    1048576
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableTotalSize”

    171309601
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableType”

    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableIdentifier”

    171309601-sample7z
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableFilename”

    sample.7z
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableRelativePath”

    sample.7z
    —————————–111061030216033
    Content-Disposition: form-data; name=”resumableTotalChunks”

    163
    —————————–111061030216033
    Content-Disposition: form-data; name=”file”; filename=”blob”
    Content-Type: application/octet-stream

    XXXCONTENT
    —————————–309022088923579–

  • 相关阅读:
    清除陷入CLOSE_WAIT的进程
    Eclipse
    远程连接elasticsearch遇到的问题
    Linux环境Nginx安装
    CentOS安装mysql
    py2exe使用方法
    Python3.4如何读写Excel
    getPhysicalNumberOfCells 与 getLastCellNum的区别
    浅析MySQL中exists与in的使用
    【MongoDB for Java】Java操作MongoDB
  • 原文地址:https://www.cnblogs.com/caption/p/3966463.html
Copyright © 2020-2023  润新知