直传文件到Azure Storage的Blob服务中

题记:为了庆祝获得微信公众号赞赏功能,忙里抽闲分享一下最近工作的一点心得:如何直接从浏览器中上传文件到Azure Storage的Blob服务中。

为什么

如果你的Web应用程序利用了云存储(比如Azure Storage)来存储用户的资源文件(比如图片、视频等等)。通常的做法,是用户访问你的Web前端,上传文件到你的Web后端应用,然后在后端程序中使用云存储的SDK把文件再转传到云存储中。架构如下图所示:

这种模式下,虽然简单方便。但是,由于上传文件的过程,需要以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的。

获取SAS访问地址

根据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》

集成WebUploader

在获得SAS并设置了CORS之后,最后的事情,就是如何把文件提交到SAS Url了。虽然我们可以采用Ajax或HTML5原生的方式来发送文件(如上两篇文章中所演示的),但是使用一个现有的文件上传组件,应该是最方便(对用户也是如此)的方式了。

我尝试了把Baidu WebUploader集成,实现了多图片上传。在集成的过程,一些注意的地方有:

  • 初始化uploader对象的时候,不要设置server属性,因为server地址需要动态获取(要获得SAS Url),且每个文件的SAS Url不一样(因为文件名不一样)
  • 我是先预先获取SAS的token,然后在uploadStart事件中为每个文件生成元数据信息,和各自的server地址
  • 在uploadBeforeSend事件中,来配置Azure所需的header信息
  • 在uploadSuccess事件中,把文件的元数据传递给后端服务器

具体的代码可以查看我分享的代码片段:http://git.oschina.net/ike/codes/7edc84bio2zplhunyxvkr

扩:

Windows Azure Blob Storage 编程简单入门

Windows Azure上的Storage服务看起来非常牛逼,它是用来在云端存储数据和文件的服务,比如Azure里的虚拟机就是保存在Storage中的。当然我们可以用Storage做更多的事,比如有些数据并不适合存放在关系型数据库中,就像图片和附件,我们就可以使用Storage服务。

今天介绍的是Storage里的一种,Blob Storage。Windows Azure Storage还有其他两种存储:Table和Queue,但是我只开荒过Blob Storage,所以其他两个就只能以后介绍了。

使用Blob存储首先得建立一个Storage Account,Account中包含的是Container,这类似于文件夹,最后你的文件会存放在Container下,也就是Blob。

一、在Azure管理页面中建立Storage Account

1. 在Windows Azure管理页面中点击工具栏上的New按钮,如图,新建一个Storage Account。Geo-Replication可以不选,它是用来做异地备份的,但会要你掏额外的钱。

2. 创建好之后,我们可以直接在Azure管理页面里创建Container,当然,也可以通过编程方式来实现,不过不是这次介绍的范围。

ACCESS的意思是这个Container的访问权限,根据自己需要设置。创建完成之后就像这样:

URL是我们访问这个Container的地址,非常重要。

二、通过编程方式访问Blob Storage

首先要说明的是, 你并不需要安装Azure SDK,也并不需要建立Cloud Service的项目 。任何一个普通的.NET项目,比如Console Application,都可以通过安装NuGet包的方式得到Windows Azure的API。

这个例子里我用的就是Console Application。

1. 通过NuGet安装“ Windows Azure Storage ”,安装时候会把其他几个包自动带上,我们仅需要这些就可以了。

2. 配置Storage Account的连接字符串。

在App.config中加一个新的appSettings,名为diaospublicblob,稍候代码里会读这个连接字符串。

<appSettings>
    <add key="diaospublicblob" value="DefaultEndpointsProtocol=https;AccountName=eas01;AccountKey=×××××××××;BlobEndpoint=http://eas01.blob.core.windows.net/" /></appSettings>

AccountKey可以在Azure管理界面上找到. 在你的Storage Account的页面下方的工具栏上,点击Manage Keys就有了。这里我们只需要Primary Access Key。

BlobEndPoint的地址同样在Azure的管理页面中可以找到:

3. 对Blob的任何操作都必须先获得Container的实例,代码如下:

// connect to azure storagevar storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("diaospublicblob"));var blobClient = storageAccount.CreateCloudBlobClient();var container = blobClient.GetContainerReference("diaostainer");

CloudConfigurationManager.GetSetting(string)的作用就是从刚才的App.config中读取Blob Storage的连接字符串。

必要的API都在这些命名空间里:

using Microsoft.WindowsAzure;using Microsoft.WindowsAzure.Storage;using Microsoft.WindowsAzure.Storage.Blob;

4. 向Container中上传本地文件:

private static void UploadFileToContainer(CloudBlobContainer container, string filePath, string uplodedFileName)
{    var blockBlob = container.GetBlockBlobReference(uplodedFileName);    using (var fileStream = System.IO.File.OpenRead(filePath))
    {
        blockBlob.UploadFromStream(fileStream);
    }
}
// upload fileUploadFileToContainer(container, @"E:\Fuck.txt", "Fuck.txt");

上传之后可以直接在Azure的管理界面里看到这个文件。也可以直接下载。

如果碰到重名的话,本地文件将会替换Azure上的文件。

5. 显示Container中的文件列表

private static void ListBlobsInContainer(CloudBlobContainer container)
{    foreach (var item in container.ListBlobs(null, false))
    {        if (item is CloudBlockBlob)
        {            var blob = (CloudBlockBlob) item;

            Console.WriteLine("Block blob of length {0}: {1}", blob.Properties.Length, blob.Uri);
        }        else if (item is CloudPageBlob)
        {            var pageBlob = (CloudPageBlob) item;

            Console.WriteLine("Page blob of length {0}: {1}", pageBlob.Properties.Length, pageBlob.Uri);
        }        else if (item is CloudBlobDirectory)
        {            var directory = (CloudBlobDirectory) item;
            Console.WriteLine("Directory: {0}", directory.Uri);
        }
    }
}

6. 下载文件到本地硬盘

private static void DownloadBlobFromContainer(CloudBlobContainer container, string blobName, string downloadFilePath)
{    var blockBlob = container.GetBlockBlobReference(blobName);    using (var fileStream = System.IO.File.OpenWrite(downloadFilePath))
    {
        blockBlob.DownloadToStream(fileStream);
    }
}
// download fileDownloadBlobFromContainer(container, "Fuck.txt", @"E:\Fucked.txt");

7. 删除文件

private static void DeleteBlobFromContainer(CloudBlobContainer container, string blobName)
{
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
    blockBlob.Delete();
}
// delete file
DeleteBlobFromContainer(container, "Fuck.txt");

原文发布于微信公众号 - 我为Net狂(dotNetCrazy)

原文发表时间:2016-02-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏魏艾斯博客www.vpsss.net

如何创建.htaccess 文件

关于.htaccess 文件,一般用于虚拟主机中,使用 VPS 建站的可以忽略了。对于使用虚拟主机建站的朋友来说.htaccess 文件可以用作伪静态化设置和 ...

4508
来自专栏张善友的专栏

实现WebSocket和WAMP协议的开源库WampSharp

Websocket Application Messaging Protocol 协议:https://github.com/wamp-proto/wamp-p...

2197
来自专栏依乐祝

Net Core集成Exceptionless分布式日志功能以及全局异常过滤

这篇文章有一部分内容翻译自官方文档,[点我阅读][https://github.com/exceptionless/Exceptionless.Net/wiki...

1012
来自专栏别先生

一脸懵逼学习Nginx及其安装,Tomcat的安装

1:Nginx的相关概念知识:   1.1:反向代理:     反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然...

24510
来自专栏Seebug漏洞平台

使用 XML 内部实体绕过 Chrome 和 IE 的 XSS 过滤器

来源:BypassingXSSFiltersusingXMLInternalEntities 原作者:DavidLitchfield (david@davidl...

39610
来自专栏大魏分享(微信公众号:david-share)

用Ansible自动供应vmware虚拟机--构建数据中心一体化运维平台第二篇

1.1 简述 一直以来,打开邮箱被ticket糊一脸的事情时有发生。我一直在想,能不能以一种简单的方案(不花老板的钱)来供应(provisioning)虚拟机呢...

6792
来自专栏张善友的专栏

实现WebSocket和WAMP协议的开源库WampSharp

2045
来自专栏AILearning

Apache Zeppelin 中 JDBC通用 解释器

概述 JDBC解释器允许您无缝地创建到任何数据源的JDBC连接。 在运行每个语句后,将立即应用插入,更新和升级。 到目前为止,已经通过以下测试: ...

3037
来自专栏NetCore

Identity Service - 解析微软微服务架构eShopOnContainers(二)

接上一篇,众所周知一个网站的用户登录是非常重要,一站式的登录(SSO)也成了大家讨论的热点。微软在这个Demo中,把登录单独拉了出来,形成了一个Service,...

2625
来自专栏草根专栏

使用Identity Server 4建立Authorization Server (1)

本文内容基本完全来自于Identity Server 4官方文档: https://identityserver4.readthedocs.io/ 官方文档很详...

49610

扫码关注云+社区

领取腾讯云代金券