(此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注。)前端
题记:为了庆祝得到微信公众号赞扬功能,忙里抽闲分享一下最近工做的一点心得:如何直接从浏览器中上传文件到Azure Storage的Blob服务中。git
若是你的Web应用程序利用了云存储(好比Azure Storage)来存储用户的资源文件(好比图片、视频等等)。一般的作法,是用户访问你的Web前端,上传文件到你的Web后端应用,而后在后端程序中使用云存储的SDK把文件再转传到云存储中。架构以下图所示:windows
这种模式下,虽然简单方便。可是,因为上传文件的过程,须要以Web后端程序做为代理,若是上传文件巨大频繁,会给后端程序的托管服务或托管服务器形成较大运算压力和流量压力。因此,还有一种模式,是让用户直接在浏览器中把文件上传给云存储服务。我所熟知的云平台(Azure ,AWS,Aliyun)都提供了相似的特性,只是实现方式或名称上有所不一样。另外,在这种方式下,为了保证安全性,通常不会直接把云存储的访问Key暴露给Web前端,因此都会提供一种折中方式,让你能够生成限时失效权限有限的共享访问Key,把这个共享Key给到前端来得到访问能力。在Azure中,这个特性称之为共享访问签章(Shared Access Signatures,SAS),而整个架构就变为下面这样:后端
在这个架构中,你先从后端服务器得到SAS Url,而后直接上传文件给Azure Storage,上传文件成功后,若是须要再把一些文件元数据传递给后端服务器(其实Azure的文件也能够额外保存元数据的,你本身均可以不保存元数据)。其实这种架构不只能够运用于文件存储服务,在Azure中还能够在前端直接访问Azure Storage Table、Queue等服务。关于SAS模型,微软官方的文档《Shared Access Signatures, Part 1: Understanding the SAS Model》(http://t.cn/R4OQona)讲的很清楚,上面的图就是引自这篇文章。跨域
固然,在不少时候,咱们是须要混用这两种模式的,在须要更多安全控制和流量可控的状况,使用代理转传模式;在安全可隔离流量不可控的状况下,使用直传模式。我本身的实践当中,也是两种模式混用,在须要用户上传文件到公共存储帐号的时候,使用代理模式,在用户上传文件到用户独有存储帐号的时候,使用直传模式。浏览器
因为我当前使用的云平台是Azure,因此下面演示的代码也是基于Azure Storage SDK的。安全
根据Azure的文档《Shared Access Signatures, Part 2: Create and use a SAS with Blob storage》(http://t.cn/R4OQeBd)所述,获取SAS其实也很是简单。服务器
首先实例化CloudStorageAccount、CloudBlobClient和CloudBlobContainer,以下:微信
//Parse the connection string and return a reference to the storage account. CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString")); //Create the blob client object. CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); //Get a reference to a container to use for the sample code, and create it if it does not exist. CloudBlobContainer container = blobClient.GetContainerReference("sascontainer"); container.CreateIfNotExists();
而后建立一个临时的策略,调用CloudBlobContainer的GetSharedAccessSignature方法来生成访问token,以下:架构
//Set the expiry time and permissions for the container. //In this case no start time is specified, so the shared access signature becomes valid immediately. SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy(); sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24); sasConstraints.Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List; //Generate the shared access signature on the container, setting the constraints directly on the signature. string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
所生成的token实际就是一个包含有多个策略规则的查询字符串,而后把这个token经过Web后端的一个Api调用(固然是验证用户权限后)传递给Web前端。前端为要上传的文件构造这样一个Url:存储容器的Uri+要上传的文件名(包括所在文件夹)+SAS Token,而后把文件流HTTP PUT到这个Url就能够实现上传。
上述代码生成的是一个存储容器的SAS Url,其实也能够针对一个Blob对象生成SAS Url。
另外须要注意的是,你也能够在存储容器上设定一个固定的共享访问策略(而非临时设置在SAS token上),这样你能够更加方便和系统的控制SAS的有效性。详情可参考上面提到的文档。
直传文件实际上调用的是Azure Storage REST API,在最初的时候,Azure Storage是不支持跨域访问的(CORS),在这种状况下,只有设置Storage容器的自定义域和Web应用程序的域一致。不过如今有了CORS的支持,就很简单了,经过下面的方法能够设置CORS(:
public static async Task AddCorsRuleAsync(CloudBlobClient blobClient) { //First get the service properties from storage to ensure we're not adding the same CORS rule again. var serviceProperties =await blobClient.GetServicePropertiesAsync(); var corsSettings = serviceProperties.Cors; var corsRule = corsSettings.CorsRules.FirstOrDefault( o => o.AllowedOrigins.Contains("http://localhost:3904"));//设置你本身的服务器地址 if (corsRule == null) { //Add a new rule. corsRule = new CorsRule() { AllowedHeaders = new List<string> { "x-ms-*", "content-type", "accept" }, AllowedMethods = CorsHttpMethods.Put,//Since we'll only be calling Put Blob, let's just allow PUT verb AllowedOrigins = new List<string> { "http://localhost:3904" },//This is the URL of our application. MaxAgeInSeconds = 1 * 60 * 60,//Let the browswer cache it for an hour }; corsSettings.CorsRules.Add(corsRule); //Save the rule await blobClient.SetServicePropertiesAsync(serviceProperties); } }
对于CORS更加详细的解释,能够参考Azure Storage的MSDN Blog上的这篇文章《Windows Azure Storage: Introducing CORS》和Gaurav Mantri的这篇博文《Windows Azure Storage and Cross-Origin Resource Sharing (CORS) – Lets Have Some Fun》
在得到SAS并设置了CORS以后,最后的事情,就是如何把文件提交到SAS Url了。虽然咱们能够采用Ajax或HTML5原生的方式来发送文件(如上两篇文章中所演示的),可是使用一个现有的文件上传组件,应该是最方便(对用户也是如此)的方式了。
我尝试了把Baidu WebUploader集成,实现了多图片上传。在集成的过程,一些注意的地方有:
具体的代码能够查看我分享的代码片断:http://git.oschina.net/ike/codes/7edc84bio2zplhunyxvkr
最后,若是这篇文章对你有用,欢迎打赏。