关于上传文件到Azure Storage没有什么可讲的,不论我们使用哪种平台、语言,上传流程都如下图所示:
从上图我们可以了解到从客户端上传文件到Storage,是需要先将文件上传到应用服务上,然后再通过应用服务器上的后端代码将文件上传到Storage,在这里应用服务器就是起到一个代理的作用。
当然,这是最常规的做法,但是确有一个致命的弱点,那就是我们上传文件到Storage中,是要占用应用服务器的计算、网络、IO等资源的,这对于一些对性能要求很高的应用场景是很难接受的。
那么,我们是否有解决方案?答案是肯定的,我们可以跳过应用服务器从客户端将文件直传到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) }); }