从客户端浏览器直传文件到Storage

时间:2022-05-13 17:13:09

从客户端浏览器直传文件到Storage

关于上传文件到Azure Storage没有什么可讲的,不论我们使用哪种平台、语言,上传流程都如下图所示:

从客户端浏览器直传文件到Storage

从上图我们可以了解到从客户端上传文件到Storage,是需要先将文件上传到应用服务上,然后再通过应用服务器上的后端代码将文件上传到Storage,在这里应用服务器就是起到一个代理的作用。

当然,这是最常规的做法,但是确有一个致命的弱点,那就是我们上传文件到Storage中,是要占用应用服务器的计算、网络、IO等资源的,这对于一些对性能要求很高的应用场景是很难接受的。

那么,我们是否有解决方案?答案是肯定的,我们可以跳过应用服务器从客户端将文件直传到Storage中,那么我们如何来构建这套解决方案呢?这就是我们下面要讲的。

 

我们先来看下直传文件到Storage的流程图:

从客户端浏览器直传文件到Storage

这里特别有几点需要注意的地方:

1、  不能使用存储账号和存储密钥来验证身份进行文件上传,因为这会在客户端暴露存储账号和存储密钥等敏感信息。

2、  Storage默认是不能跨域访问的,所以我们需要将应用服务器所在域加入到Storage的跨域规则中。

3、  通过上图,我们可以看到客户端一共发送两次http请求,第一次从应用服务器获取Shared Access Signature(具有写权限),再带着获取到的SAS将文件从客户端上传到Storage。

 

大致流程和思路已经讲清楚了,那么下面我们就来看看用代码是如何实现的。

Index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <meta charset="utf-8" />
    <title>直传文件到Storage</title>
    <script src="~/Scripts/jquery-1.10.2.js"></script>
    <script src="~/Scripts/uploadfile.js"></script>
</head>
<body>
    <input type="file" id="file" name="file" />

    <div id="output">
        <strong>File properties:</strong>
        <br />
        <p>
            Name: <span id="fileName"></span>
        </p>
        <p>
            File size: <span id="fileSize"></span>bytes.
        </p>
        <p>
            File type: <span id="fileType"></span>
        </p>
        <p>
            <input type="button" value="Upload File" id="uploadFile" />
        </p>
    </div>
</body>
</html>

updatefile.js

(function ($) {
    var reader = null,
        requestUri = null,
        selectedFile = null,
        blobName = null;

    function sendAjax(url, dataToSend, beforeSendFunction, successFunction) {
        $.ajax({
            url: url,
            type: "PUT",
            data: dataToSend,
            processData: false,
            beforeSend: beforeSendFunction,
            tryCount: 0,
            success: successFunction,

        });
    }

    function readerOnLoadEnd(evt) {
        if (evt.target.readyState === FileReader.DONE) {
            var uri = requestUri,
                requestData = new Uint8Array(evt.target.result);
            sendAjax(uri, requestData,
                function (xhr) {
                    xhr.setRequestHeader("x-ms-blob-type", "BlockBlob");
                    xhr.setRequestHeader("x-ms-blob-content-type", getContentType(blobName));
                }, function (data, status) {
                    alert("upload success.");
                });
        }
    };

    function handleFileSelect(e) {
        selectedFile = e.target.files[0];
        blobName = selectedFile.name;
        var fileSize = selectedFile.size;
        $('#output').show();
        $('#fileName').text(blobName);
        $('#fileSize').text(fileSize);
        $('#fileType').text(selectedFile.type);

        $.get('{获取Blob SAS的接口地址}', { 'blobName': blobName }, function (data) {
            console.log(data);
            requestUri = data;
        });
    }

    function startUpload() {
        $("#uploadFile").prop('disabled', true);
        $("#file").prop('disabled', true);
        var slice = selectedFile.slice(0, selectedFile.size);
        reader.readAsArrayBuffer(slice);
    }

    function getContentType(blobName) {
        var ext = blobName.substr(blobName.lastIndexOf('.'));
        var contentType = 'application/octet-stream';
        switch (ext) {
            case ".txt":
                contentType = "text/plain";
                break;
            case ".png":
                contentType = "image/png";
                break;
            case ".zip":
                contentType = "application/zip";
                break;
            case ".pptx":
                contentType = "application/vnd.ms-powerpoint";
                break;
            case ".docx":
                contentType = "application/msword";
                break;
            case ".pdf":
                contentType = "application/pdf";
                break;
            case ".jpg":
            case ".jpeg":
                contentType = "image/jpeg";
                break;
            case ".html":
                contentType = "text/html";
                break;
            case ".js":
                contentType = "application/x-javascript";
                break;
            case ".css":
                contentType = "text/css";
                break;
            case ".gif":
                contentType = "image/gif";
                break;
            case ".ico":
                contentType = "image/x-icon";
                break;
            case ".mp4":
                contentType = "video/mpeg4";
                break;
            case ".mp3":
                contentType = "audio/mp3";
                break;
            default:
                break;
        }
        return contentType;
    }

    $(function () {
        $('#output').hide();
        if (window.File && window.FileReader && window.FileList && window.Blob) {
            reader = new FileReader();
            reader.onloadend = readerOnLoadEnd;
        } else {
            alert('The File APIs are not fully supported in this browser.');
            $('#file').prop('disabled', true);
            return;
        }
        $('#file').bind('change', handleFileSelect);
        //上传文件
        $('#uploadFile').bind('click', startUpload);
    });
}(jQuery))

后端提供的Shared Access Signature接口代码

private string _StorageConnectionString = ConfigurationManager.AppSettings["StorageConnectionString"];
private string _AccountName = ConfigurationManager.AppSettings["AccountName"];
private string _AccountKey = ConfigurationManager.AppSettings["AccountKey"];
private string _ContainerName = ConfigurationManager.AppSettings["ContainerName"];

[HttpGet]
public string GetRequestStorageUri(string blobName)
{
    if (string.IsNullOrEmpty(blobName))
    {
          throw new ArgumentNullException();
     }
    if (string.IsNullOrEmpty(_ContainerName)) {
          throw new NullReferenceException("container is null or empty");
     }

CloudBlockBlob blockBlob = GetCloudBlockBlob(_ContainerName, blobName);
    string sas = GetBlobSharedAccessSignature(blockBlob);
    string requestUri = string.Format("https://{0}.blob.core.chinacloudapi.cn/{1}/{2}{3}", _AccountName, _ContainerName, blobName, sas);
    return requestUri;
}

private CloudBlockBlob GetCloudBlockBlob(string containerName,string blobName)
{
    if (string.IsNullOrEmpty(containerName) || string.IsNullOrEmpty(blobName))
    {
        throw new ArgumentNullException();
     }
     if (string.IsNullOrEmpty(_StorageConnectionString))
     {
         throw new NullReferenceException("storage connection string is null or empty");
       }
        CloudStorageAccount account = CloudStorageAccount.Parse(_StorageConnectionString);
        CloudBlobClient blobClient = account.CreateCloudBlobClient();
        ServiceProperties serviceProperties = blobClient.GetServiceProperties();
        serviceProperties.Cors.CorsRules.Clear();
        serviceProperties.Cors.CorsRules.Add(new CorsRule
        {
            AllowedOrigins = new List<string> { "{应用服务器所在域}" },
            AllowedMethods = CorsHttpMethods.Get| CorsHttpMethods.Put| CorsHttpMethods.Post| CorsHttpMethods.Delete,
            AllowedHeaders = new List<string> { "x-ms-*", "content-type", "accept" },
            MaxAgeInSeconds = 1 * 60 * 60
        });
        blobClient.SetServiceProperties(serviceProperties);
        CloudBlobContainer container = blobClient.GetContainerReference(containerName);
        container.CreateIfNotExists();
        CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
        return blockBlob;
}

private string GetBlobSharedAccessSignature(CloudBlob blob) {
    if (blob == null)
    {
        throw new ArgumentNullException("blob is null");
     }
     return blob.GetSharedAccessSignature(new SharedAccessBlobPolicy {
         Permissions = SharedAccessBlobPermissions.Add| SharedAccessBlobPermissions.Create| SharedAccessBlobPermissions.Delete| SharedAccessBlobPermissions.List| SharedAccessBlobPermissions.Read| SharedAccessBlobPermissions.Write,
         SharedAccessStartTime = DateTime.UtcNow,
         SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5)
     });
}