【IT168微软云计算博客征文活动专稿】本文将介绍如何使用Silverlight直接上载Blob到Windows Azure存储中,通过本文引用的解决方案,我们最终实现了可以上载任意大小的文件,突破默认最大只能上载64MB Blob文件的限制,你可以访问 http://slupload.cloudapp.net 试试我们最终的解决方案示例。
共享访问签名
通过共享访问签名( Shared Access Signatures ,SAS),我们可以使用Silverlight直接访问Blob存储,无需使用存储账号密钥,因此不会危及帐户的安全,下面介绍一下在 http://slupload.cloudapp.net 上我们是如何使用共享访问签名的。
为什么要使用共享访问签名
访问Windows Azure存储典型的方法是先构造一个Web请求,然后用存储账号的共享密钥签署这个Web请求,如果使用我们自己信任的代码访问存储时,这个模型可以表现得很好,但我们要将共享密钥用于Silverlight客户端代码就不行了,任何能访问Silverlight应用程序的人都可以提取出这个共享密钥,用它岂不可以完全控制我们的存储帐户了?
为了避免泄露存储密钥,一个可选的方法是用一个Web服务(它可以执行授权)代理来自客户端的所有请求,Web服务是唯一与存储交互的代码,这个方法的缺点是我们需要通过一个Web服务器转发所有请求,但让Silverlight可以直接将数据发送给存储服务,并且不用泄露我们的存储凭据,我们决定一试。
共享访问签名允许我们将签署Web请求的代码独立出来,一个共享访问签名是一串可以附加给为Web请求进行授权的URL的字符串,共享访问签名中包含了哪些操作是允许的策略,还有一个URL创建者的签名。Silverlight取得对共享访问签名的访问后,我们可以通过它将授权传递给客户端,Silverlight就可以直接访问Blob存储了,但只有有限的访问权限。
创建共享访问签名
使用最新的存储客户端库创建共享访问签名非常简单,下面的代码来自 http://slupload.cloudapp.net,创建了一串预授权读取访问的RUL:
{
Permissions = SharedAccessPermissions.Read,
SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10)
};
blobsListView.DataSource =
from CloudBlob blob in container.ListBlobs(new BlobRequestOptions() { UseFlatBlobListing = true })
select new Uri(blob.Uri.AbsoluteUri + blob.GetSharedAccessSignature(readPolicy));
第一条语句是创建策略,它指定了10分钟的只读访问权,第二条语句列出了某个容器中所有的Blob,创建了一串有共享访问签名的URI,我在 View.aspx 页面上用它进行数据绑定。
为了获得特定Blob的写访问权,可以使用上面一样的代码,只需将只读权限改为写入权限即可,但对于 http://slupload.cloudapp.net ,我希望让用户可以上载任意名称任意大小的文件,为了实现这一目标,我创建了一个共享访问签名附加给容器,而不是附加给某个Blob。
var container = blobs.GetContainerReference(ContainerName);
container.Create();
var sas = container.GetSharedAccessSignature(new SharedAccessPolicy()
{
Permissions = SharedAccessPermissions.Write,
SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10)
});
开启跨域访问Blob
前面我们了解了如何通过共享访问签名让客户端无需存储账号密钥直接访问Blob存储,对大多数客户端来说,共享访问签名是实现对Windows Azure Blob存储读/写访问的全部要求,但对Silverlight来说,有一个跨域访问限制,下面我们介绍如何通过Silverlight开启对Blob的无限制访问。
ClientAccessPolicy.xml
当Silverlight程序产生一个跨域调用时,首先要从目标服务器的根取得一个叫做 ClientAccessPolicy.xml 的文件,在我们的例子中,我们的URL是 http://slupload.blob.core.windows.net/… ,因此Silverlight会尝试访问 http://slupload.blob.core.windows.net/ClientAccessPolicy.xml 策略文件。
Windows Azure存储中的每个Blob都驻留在容器中,有一个特殊的根容器允许我们离开根域直接存储Blob, ClientAccessPolicy.xml 就放在这里,下面的代码创建了一个公共的可读的根容器,并在它里面创建了一个名为 ClientAccessPolicy.xml 的Blob。
{
blobs.GetContainerReference("$root").CreateIfNotExist();
blobs.GetContainerReference("$root").SetPermissions(
new BlobContainerPermissions() {
PublicAccess = BlobContainerPublicAccessType.Blob
});
var blob = blobs.GetBlobReference("clientaccesspolicy.xml");
blob.Properties.ContentType = "text/xml";
blob.UploadText(@"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-methods=""*"" http-request-headers=""*"">
<domain uri=""*"" />
<domain uri=""http://*"" />
</allow-from>
<grant-to>
<resource path=""/"" include-subpaths=""true"" />
</grant-to>
</policy>
</cross-domain-access>
</access-policy>");
}
关于 ClientAccessPolicy.xml 有几个重要的事情需要提醒一下:
1、Blob有个 text/xml 内容类型,我发现在某些浏览器中,Silverlight不能接受含有错误内容类型的 ClientAccessPolicy.xml 。
2、我们使用 allow-from http-methods 增加除GET和POST HTTP动词以外的支持。
3、我们使用 allow-from http-request-headers 允许自定义消息头,支持与 x-ms-version 类似的消息头。
4、我们使用 domain uri=”http://*” 允许非SSL客户端基于HTTPS访问Blob存储。
使用Silverlight客户端HTTP堆栈处理
默认情况下,Silverlight使用浏览器HTTP堆栈产生Web请求,它限制了你只能使用GET和POST动词,从Silverlight 3开始,提供了客户端HTTP堆栈,可以使用其它HTTP动词了,如我们创建Blob时要用到的PUT。
为了确保我们使用客户端HTTP堆栈,需要使用 WebRequestCreator.ClientHttp.Client() 方法构造我们的Web请求。
在Silverlight中创建Blob
Windows Azure SDK中的.Net存储客户端库不能用于Silverlight(主要是HTTP堆栈稍微有点不同),因此我们需要自己实现创建Blob的方法,幸运的是,适用于Blob存储的REST API相当简单。下面的Silverlight代码在一个Blob中存储了文本“ Hello World ”(假设URI已经附加了适当的共享访问签名)。
webRequest.Method = "PUT";
webRequest.ContentType = "text/plain";
webRequest.BeginGetRequestStream((ar) =>
{
using (var writer = new StreamWriter(webRequest.EndGetRequestStream(ar)))
{
writer.WriteLine("Hello, World!");
}
webRequest.BeginGetResponse((ar2) =>
{
((HttpWebRequest)ar2.AsyncState).EndGetResponse(ar2);
}, null);
}, null);
使用块 API 处理大文件
处理大文件夹的方法是将其拆分成多个小块数据,然后再处理小块数据。
拆分成块上载
前面我们了解了在单个请求中上载整个Blob的方法,但对于大型Blob,我们需要拆分成小块后再上载,实际上,对于超出64MB的Blob,我们必须使用块API( Put Block 和 Put Block List )进行上载,通过拆分成一系列小块上载有很多好处。
1、允许我们上载大于64MB的Blob。
2、允许我们并行上载Blob。
3、上载失败后,可以重新上载之前上载失败的块,而不用全部重新上载,与断点续传类似。
使用块API
下面的代码都摘自 http://slupload.cloudapp.net ,首先构造一个我们是否使用块的Web请求(当Blob超出64MB时就使用)。
{
// encode the block name and add it to the query string
currentBlockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));
uriBuilder.Query = uriBuilder.Query.TrimStart('?') +
string.Format("&comp=block&blockid={0}", currentBlockId);
}
// with or without using blocks, we'll make a PUT request with the data
HttpWebRequest webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(uriBuilder.Uri);
webRequest.Method = "PUT";
webRequest.BeginGetRequestStream(new AsyncCallback(WriteToStreamCallback), webRequest);
如果我们使用块,当我们上载完所有块后,需要调用 Put Block List 提交块。下面的代码构造Web请求。
new Uri(string.Format("{0}&comp=blocklist", UploadUrl)));
webRequest.Method = "PUT";
webRequest.Headers["x-ms-version"] = "2009-09-19"; // x-ms-version is required for put block list!
webRequest.BeginGetRequestStream(new AsyncCallback(BlockListWriteToStreamCallback), webRequest);
下面的代码创建一个XML文档列出块(我们用它跟踪我们已上载的块),将其作为 Put Block List 调用的主体发送。
Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);
var document = new XDocument(
new XElement("BlockList",
from blockId in blockIds
select new XElement("Uncommitted", blockId)));
var writer = XmlWriter.Create(requestStream, new XmlWriterSettings() { Encoding = Encoding.UTF8 });
document.Save(writer);
writer.Flush();
requestStream.Close();
webRequest.BeginGetResponse(new AsyncCallback(BlockListReadHttpResponseCallback), webRequest);
小结
我们用共享访问签名生成了URL,并实现了跨域访问Blob存储,利用块API实现了大文件的上载,将这些功能糅合到一起,外面加上一层漂亮的UI就完美了,于是我们在 Codeplex 上创建了一个“Silverlight多文件上载项目”,上载页面如下图所示。
图 1 用Silverlight实现的多文件上载页面