[开源应用]利用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="width: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–

你可能感兴趣的:(handler)