详细图解Google Storage的开通试用及其API的简单应用Google云存储技术

前几天终于收到Google的邮件,说是之前申请的Google Storage for Developers终于通过了!

(申请方法其实很简单:就是到http://gs-signup-redirect.appspot.com/填写注册邮箱及其他一些必要信息,到时收信就偶看了。不过在国内不能访问需要proxy啊,呵呵)。

 

clip_image001

Google Sotrage(以下简称GS)相关的中文资料在博客园甚至整个墙内都不多

有一篇“Google Storage for Developers初体验”(大家可自行搜索) 但也只是介绍了GSUtil Tool——GS的命令行管理工具(了解GS

但是开发的时候一般也不会在后台去调用这个GSUtil Tool来操作GS吧,因此API才是开发过程中的主角,不过在此之前还是先看看GS是个什么东西以及如何开通吧。

介绍

GS是Google提供的一项类似Amazon S3的收费服务,而GS For Developers是提供给开发人员的免费版本,申请需翻墙,申请通过后墙内可用(目前)

简单的来说GS提供一块极其稳定和快速的空间用来存储数据,而这些数据可以通过Google提供的一套“RESTful”的API来访问和管理,也可直接外联分享

开通试用

收到邀请信之后基本跟着 “invite link” 说的做空间就激活了,成功之后进到管理页面选择“Create Access Key” 会生成一串Access Key和一串对应的“Secret”

这一套密钥用来确认你的身份(使用API管理文件的时候通必须过密钥获取权限),一个账号目前最多可以创建五套这样的密钥。

clip_image002

点左边的“Google Storage Manager”访问WEB版的“文件管理器” 。

第一次进去应该是如下的界面,选择右上角的"New Bucket"新建一个Bucket(用户的任何数据都必须所属一个Bucket,也就是说根目录下无法直接存放文件)

clip_image003

此时你可能会发现无论Bucket的名字输入什么都会提示“invalid bucket name”,这是因为Bucket的名字是对应域名的,必须与域名同名,免费域名诸如.tkco.cc均可

具体步骤如下:

1. 输入域名,不要加"http://"等,这里我申请了一个免费的tevins.co.cc域名用来演示(上面的两个域名我已经确认了,无视即可),
提示The bucket you tired to create requires domain ownership verification.

clip_image004

也就是说这个域名虽然合法但是google并不知道这个域名是否为你所有,点击后面的Verify now认证域名的所有权。

2. 有四种验证域名所有权的方法,分别是添加DNS记录、添加域名主页meta标签、上传HTML文件、关联到Google Analytics账号
我们选择最简单的添加DNS记录,这个方法既不需要虚拟主机也不需要Google Analytics账号,刚申请的裸域名都可以。
再下面有一个下拉框用来选择域名注册商,co.cc和tk没有在内,没关系,直接选择Other
clip_image005

3. 必要关闭此网页按照上图的说明,为tevins.co.cc添加一条TXT记录值为框内的字符串
clip_image006
我的域名使用了DNSPOD的服务,以上为DNSPOD的设置方法,其他的大同小异(主机记录留空或填“@”)

4. 等刚才设置的DNS记录生效,选择Verify就完成了,成功的话会转到如下页面
clip_image007
这里显示了所有已经验证通过了的域名。

5. 至此域名验证过程已完成,回到GS的管理界面,再次添加名为“tevins.co.cc”的Bucket就能成功了。
clip_image008

双击进入创建好的Bucket

clip_image009

根据提示拖入(IE不支持多文件上传)文件即可上传了

新建一个名为Public的文件夹进入,然后拖入一个文件,图为上传界面

clip_image010

上传完成,点击最右边的灰色对钩(Share Publicly),稍等片刻对钩变绿说明文件已经可以外联,右边会出现一个名为“Link”的超链接clip_image011,即为外联地址,大家可以测试一下速度

clip_image012

API的使用

Google所称的“RESTful interface”并非是指"you can program with the API restfully",no!no!no!验证部分可以把人搞疯掉(下面的演示代码大部分都是为了验证)

这里的"RESTful"指的是一种设计风格(虽然不是一种标准,但是这样理解可能更容易些),因此在开篇我就直接引用了单词“RESTful”。

GS API的实现依赖标准的HTTP方法,诸如如DELETE, GET, HEAD, 还有 PUT,熟悉web service的话应该很容易上手。

废话不多说,下面来看看这个RESTful的API到底是个什么东西。

上文提到了一个概念“用户存储的任何数据(文件)都所属于某个Bucket之下”。

一个用户可以有多个Bucket,但是Bucket的命名是严格限制的。

说到这里Bucket的概念跟Windows里的驱动器盘符就很相似了,要对GS内的数据进行操作(除了创建一个Bucket)首先要知道这个数据的所在的Bucket

因此我们先从列出所有的Bucket开始。

Google的文档告诉我们使用GET Service可以获取所有的Buckets

因此我们按照Google的要求发送一个符合条件的HTTP请求即可。

这个请求有如下头域(header)

· Authorization 验证字符串

· Content-Length     HTTP请求内容的长度

· Date HTTP请求的日期

· Host Google Storage服务器的URI

每个header都是必须的

Authorization 这个问题最复杂,等下说
Content-Length 在此例中应该为0,因为发送的HTTP请求只包含若干header,body部分为空
Date 请求发送的日期,看起来很简单但是设置这个Date还真是耗了我不少时间(见代码注释)
Host 确实很简单,Google有提供,一串URI就行了

这就是全部了,发送一个包含这些headers的请求然后等着Google的回应就行了。

从服务器返回的response是一个XML文档,包含所请求的信息,本例中如下图所示:

clip_image013

可以看到,所有Buckets的信息都在里面了,取得了这个XML文档之后进行解析就能得到我们想要的数据了。

还是蛮简单的。

代码如下:

//GoogleStorage账号的一套密钥
string AccessKey = @"你的AccessKey";
string secretKey = @"你的secretKey ";
//声明一个http请求
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(@"https://commondatastorage.googleapis.com/");
request.ContentType = @"application/xml";
request.Method = "GET";
request.ContentLength = 0;
request.Headers.Add("Data", DateTime.UtcNow.ToString(@"ddd, dd MMM yyyy HH’:'mm’:'ss ‘GMT’", CultureInfo.GetCultureInfo("EN-US")));
request.KeepAlive = false;
//Google要求的时间格式
string timeStamp = DateTime.UtcNow.ToString(@"ddd, dd MMM yyyy HH’:'mm’:'ss ‘GMT’", CultureInfo.GetCultureInfo("EN-US"));
request.Date = DateTime.UtcNow; //试了很多办法依旧没找到.Net 4.0以下版本为HttpWebRequest设置Date属性的办法,知道的朋友请指教 !
string canonicalHeaders = string.Format("{0}\n{1}\n{2}\n{3}\n", request.Method, string.Empty, request.ContentType, timeStamp);
string canonicalExtensionHeaders = string.Empty;
string canonicalResource = "/";
string signedMessage = string.Format("{0}{1}{2}", canonicalHeaders, canonicalExtensionHeaders, canonicalResource);
string signature = Convert.ToBase64String(new HMACSHA1(Encoding.UTF8.GetBytes(secretKey)).ComputeHash(Encoding.UTF8.GetBytes(signedMessage)));
string authString = string.Format("GOOG1 {0}:{1}", AccessKey, signature);
request.Headers.Add("Authorization", authString);
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
MessageBox.Show(response.StatusCode.ToString());
//发送HTTP请求并获取回应(XML文档)
StreamReader sr = new StreamReader(response.GetResponseStream());
string xml = sr.ReadToEnd();
sr.Close();
//格式化XML文档
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
MemoryStream ms = new MemoryStream();
doc.Save(ms);
xml = Encoding.UTF8.GetString(ms.GetBuffer());
ms.Close();
//格式化XML文档
tbxOut.Text = xml; //显示XML文档
}
catch (WebException ex)
{
MessageBox.Show(ex.Message);
Stream stRes = ex.Response.GetResponseStream();
StreamReader srRes = new StreamReader(stRes);
string xml = srRes.ReadToEnd();
tbxOut.Text = xml;
}

好了,看完了代码,再来讨论这个严峻的验证问题吧

官方的文档里头在验证上花的笔墨也不少,这是因为大多数GS API的操作都需要验证,唯一的例外就是操作允许匿名访问的资源了,比如上文的测试连接就是。

验证的过程本身并不复杂,只要Authorization这个header的值(验证字符串)跟Google服务器计算出的相匹配就验证通过。

但是这个验证字符串来之不易:

上文提到每个用户有一到多套密钥,也就是一个AccessKey和一个相对应的SecretKey

他们就是用来组成和计算验证字符串的

验证字符串的语法如下

Authorization: GOOG1 google_storage_access_key:signature

黑体部分是固定的,也就是验证字符串必须放在“Authorization”这个header内,并以冒号后面的字符串为值。

这里的GOOG1就是字符串“GOOG1”,他(Signature identifier)用来标识所使用的验证算法和版本,Google Storage的Signature identifie就是“GOOG1”

跟着后面的是一个空格,然后google_storage_access_key指代的是密钥的AccessKey

然后是一个冒号(当然是半角)再跟着的是Signature(下面说明)。

Signature是一串使用HMAC-SHA1计算而得的哈希值,计算需要用到两个参数,一个是密钥内的SecretKey还有一个是需要被签名的信息(HTTP请求)。

伪代码如下:

Signature = Base64-Encoding-Of(HMAC-SHA1(UTF-8-Encoding-Of(YourGoogleStorageSecretKey, MessageToBeSigned)))

现在又有一个未知量——MessageToBeSigned,它由三个字符串连接依次而成,伪代码如下:

MessageToBeSigned = UTF-8-Encoding-Of(CanonicalHeaders + CanonicalExtensionHeaders + CanonicalResource)

这三个字符串分别是 CanonicalHeaders、CanonicalExtensionHeaders和CanonicalResource

CanonicalHeaders

CanonicalHeaders = HTTP 方法 + "\n" +
HTTP请求内容的MD5值 + "\n" +
请求内容类型  + "\n" +
日期 + "\n"

CanonicalExtensionHeaders

本例中为空

CanonicalResource

本例中为“/”

按照以上的方法一个验证字符串就诞生了,最后的三个字符串我就不在这里当翻译了,大家可以自行到google的文档里头去看。

题外话:

其实最后的三个字符串,包括所有组成MessageToBeSigned的元素全部都来自于即将发出的HTTP请求本身,目的就在于标识这个HTTP请求,Google之所以这样做是为了确保HTTP请求在网络上传送过程的安全性,虽然GS支持HTTPS连接,但是本质上HTTPSHTTP一样,也是不安全的。也就是说通过HTTP/HTTPS在网络上传输的信息有被截取的可能,大家想想,如果你的密码直接以明文方式传送有多么危险,当然了,即使是MD5之类的不可逆算法加密过后的密文也无济于事,因为虽然算法不可逆,但是服务器验证的是MD5的密文,也就是说只要知道密码的MD5值就可以通过验证了,根本不需要知道原密码,因此HMAC SHA1这类验证方式应运而生,这种验证方式在客户端使用一串密码(仅用户和服务器持有)和发送的信息本身(包括时间)混合起来进行高强度的不可逆加密,得到一串密文;服务器端使用收到的信息和密码使用同样的方式计算出密文,两串密文相同即通过验证。

使用这种方式即使这串密文被截取了也没有办法通过服务器验证,因为每次请求的信息是不完全相同的(即使完全相同时间也不同)因此一串密文只能验证一个时间点的某一个请求。

GS API就先介绍到这里吧,如果有朋友感兴趣我再发点东西上来。

附:Google Storage Manager 不能单独使用IE访问,可以使用下面版本的浏览器访问:

  • Google Chrome 4.1+
  • Chrome Frame for IE
  • Firefox 3.6+
  • Safari 4.0+

这主要原因可能是IE原生不支持多文件的选取与上传。

GSUtil Tool

clip_image002
GSUtil Tool是更强大的命令行管理工具,可以安装在Linux和Mac OS上,也可以通过Cygwin安装在Windows上。第一次运行GSUtil Tool时候需要进行简单的配置,如果还没有手工配置直接输入命令就会出现提示,输入授权ID和访问KEY重新执行刚才的命令就可以了。
clip_image004

通过GSUtil Tool可以直接像操作本地文件一样操作远程文件,它提供一些类似本地shell的命令:
创建存储库

gsutil mb gs://cats gs://dogs

上传文件

gsutil cp *.jpg gs://dogs

列出存储库或文件

gsutil ls
gsutil ls gs://dogs

移动或改名

gsutil mv gs://dogs/*.jpg gs://cats/
gsutil mv gs://cats/poodle.jpg gs://cats/siamese.jpg

下载

gsutil cp gs://cats/*.jpg file://pets/

改变授权

gsutil setacl acls.txt gs://cats/siamese.jpg
gsutil setacl bucket-owner-full-control gs://cats/siamese.jpg

删除文件或者存储库

gsutil rm gs://cats/collie.jpg
gsutil rb gs://dogs

Python Library

Python Library是提供的一组Python API用于访问Google Storage,它源自boto(AS3的访问接口)。使用Python Library访问Google Storage仍然需要依赖GSUtil。目前Google App Engine已经支持使用Python Library访问Google Storage。

此外Google Storage API提供了非常RESTful的接口用于访问Google Storage,除了使用上面的工具,你还可以自己动手通过API访问Google Storage。

我在GAE上放了一个DEMO用 于遍历存储库中文件及提供下载链接,欢迎测试速度

题外话:

Google Storage的出现弥补了Google App Engine文件存储的缺陷,但是GAE文件执行时间的问题导致还是无法利用GAE来传送大文件到GS。虽然网站上已经对资源的标了价格,但目前 Google还没有提供查询资源使用情况及付费的入口。另一方面GS比S3吸引力更大的地方是有免费配额,无疑为在项目初期选择它又多了一道砝码。

完整程序源码