内容


使用 PHP 实现云计算,第 1 部分

结合使用 Amazon S3 和 Zend Framework

使用 Zend Framework 将数据移入和移出云

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 PHP 实现云计算,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 PHP 实现云计算,第 1 部分

敬请期待该系列的后续内容。

云计算承诺为用户和应用程序提供不受限制的磁盘空间。在理想的世界中,访问这类存储将和访问本地硬盘一样简便。不幸的是,大多数云存储服务的基本 API 要求程序员考虑协议和配置细节,而不是简单地处理数据。本文将探查 Zend Framework 中的类,它们将使 Amazon 的 S3 云存储服务变成一个真正无限大的硬盘。

连接格式和云计算

构建在云中使用的应用程序的主要挑战就是与服务本身的接口。大多数服务提供了 REST 或 SOAP 接口(S3 同时提供了两者)。REST 的主要优势就是它不是特定于语言的。这意味着您可以从所喜欢的任意编程语言中调用服务。劣势就是在使用 REST 或 SOAP 时,您必须考虑请求的细节问题,而不是关心您要使用的数据。举个例子,所有发送给 S3 的请求都必须包含一个由您的 Amazon 访问密匙和签名值组成的身份验证令牌。这表示您的请求必须包含这样一个值:0PN5J17HBGZHT7JJ3X82:frJIUN8DYpKDtOLCwo//yllqDzg=

显然,让您能够专注于数据而不是签名和其他细节的更高级的方法可以带来显著的生产力改进。这就是 Zend_Service_Amazon_S3 类发挥作用的地方。该类允许您关注数据,而不是关注 HTTP 头部结构、SOAP 信封或其他无关细节。

入门

如果您还没有安装 Zend Framework,那么请从 zend.com/community/downloads 下载并安装完整的包。该包将在您的机器上安装 Zend Framework、PHP 和 Apache Web 服务器。完成安装后,请访问 http://localhost/ZendServer/。参考 Zend Framework 安装文档获得所有细节。如果可以登录到 ZendServer 控制台,那么您就准备就绪了。

要执行本文后面介绍的练习,您需要在 Amazon 中建立一个帐户(参见 参考资料 获得帐户管理页面的链接)。建立好帐户后,需要对您的凭证进行管理。Amazon 为您提供了一个访问密匙和秘密密匙。在使用 S3 时,您的 PHP 页面需要这些值。管理这些信息的一种方法就是将这些值放到您的代码里。

清单 1. 在 PHP 代码中存储凭证
// Credentials for Amazon - Don't do this!

$awsKey = "0123456789ABCDEFGHIJ";
$awsSecret = "0123456789abcdefghiABCDEFGHI1234567890AB";

这种方法是有效的,但是您必须将代码放入到每一个有需要的 PHP 文件中。更好的做法是将这些值放入到一个 PHP .ini 文件,该文件类似清单 2 所示。

清单 2. 在 PHP .ini 文件中存储凭证
; Configuration file to hold secret keys, account numbers and other useful
; strings for Amazon and other cloud accounts.

[amazon]
accessKey=0123456789ABCDEFGHIJ
secretKey=0123456789abcdefghiABCDEFGHI1234567890AB
ownerId=123456789012

[nirvanix]
username=jane_doe
password=XXXXXXXX
appKey=01234567-89ab-cdef-0123-456789abcdef

一个简单的 PHP 类可以轻松地处理这些值。

清单 3. 用于检索凭证的简单 PHP 类
<?php
// Simple class to retrieve credentials from an .ini file

class Credentials
{
  var $key_array;

  function Credentials() {
    $this->key_array = parse_ini_file("..\conf\cloud.ini", true);
  }

  function getCredential($group, $key) {
    return $this->key_array[$group][$key];
  }
}
?>

该类使用 PHP parse_ini_file() 函数读取 .ini 文件格式的值。此函数的第一个参数为文件的名称,第二个参数告诉 PHP 将文件解析为不同的部分。这意味着数组 $key_array 是一个 2-D 数组。数组键为第一维的 amazonnirvanix,以及第二维的 accessKeysecretKeyappKey 等。Credentials 类提供了 getCredential() 方法来从 .ini 文件检索值。与将凭证硬编码到每一个 PHP 文件相反,我们将对示例使用类似如下所示的代码:

清单 4. 创建和使用一个 Credentials 对象
<?php
require_once 'Credentials.php';

$creds = new Credentials;
$s3 = new Zend_Service_Amazon_S3($creds->getCredential('amazon', 'accessKey'),
                                 $creds->getCredential('amazon', 'secretKey'));

使用这种方法需要花多一点时间来设置您的代码,但是一旦完成了此项工作,您就在一个位置一次性地定义了您的凭证。如果需要修改它们,您就不必在每个 PHP 文件中进行修改。

需要注意几点:

  • 注意 .ini 文件并不在 Web 服务器的文档根中。它位于 conf 目录,这是与文档根同级别的目录。考虑到一些显而易见的原因,您并不希望将凭证文件放到某个可被未授权用户访问的位置。
  • 另一方面,本文的所有 PHP 文件都存储 在 Web 服务器的文档根中。
  • 记住,.ini 文件中的注释以一个分号开头;PHP 样式的注释是无效的。

关于样例应用程序

作为首批被称为云计算的服务的一员,Amazon 的 S3 是一个提供了不受限在线存储的分布式文件系统。S3 数据模型包含两个概念:bucket 和对象。bucket 可以包含无限多个对象,每个对象包含数据和元数据。一个 bucket 不能包含另一个 bucket。在创建 bucket 时,bucket 的名称必须在所有 S3 用户中是惟一的。对象一旦创建以后,只能被替换或删除;您不能对其进行修改。当创建一个对象时,可以对其设置访问控制参数。默认情况下,对象是私有的,但是如果您愿意的话,也可以将其共享。

我们的样例应用程序是一个基于 Web 的面向 S3 的文件管理器。通过使用 Zend_Service_Amazon_S3 类,您可以创建能够完成以下操作的 PHP 页面:

  • 查看您的 S3 帐户中的所有 bucket
  • 创建新的 bucket
  • 查看 bucket 中的所有对象
  • 创建新对象
  • 删除一个对象
  • 删除一个 bucket

应用程序被编写为两个 PHP 文件:s3.php 和 bucketlist.php。s3.php 文件显示帐户中的所有 bucket,并允许您创建新的 bucket 和删除已有 bucket。bucketlist.php 文件显示给定 bucket 中的所有对象。它允许您创建新的对象并删除已有对象。bucketlist.php 文件显示有关每个对象的元数据并提供 Amazon 中的对象的直接链接。(如您所料,如果您无权访问该对象,就会得到一条错误消息)。

创建一个 Zend_Service_ Amazon_S3 对象

如您所料,第一步是创建一个 Zend_Service_Amazon_S3 对象。构造器函数包含两个参数:您的 Amazon 访问密匙和秘密密匙。使用前面讨论的 Credentials 类。

清单 5. 创建一个 Zend_Service_Amazon_S3 对象
<?php
require_once 'Zend/Service/Amazon/S3.php';
require_once 'Credentials.php';

// Initialize our credentials and create an S3 object
$creds = new Credentials;
$s3 = new Zend_Service_Amazon_S3($creds->getCredential('amazon', 'accessKey'),
                                 $creds->getCredential('amazon', 'secretKey'));
                                 ?>

两个 PHP 文件都以这几行代码开头。不管您是查找 bucket 还是查找对象,这里创建的 $s3 对象将完成大部分工作。

列出 S3 帐户中的所有 bucket

当用户加载 s3.php 时,他将看到其 Amazon 帐户中的所有 bucket 的清单。屏幕布局十分直观。

图 1. 帐户中的 buckets 的列表
帐户中的 buckets 的列表
帐户中的 buckets 的列表

getBuckets() 方法返回一组 bucket 名称。每个 bucket 名称被格式化为一个链接;单击链接将把用户带到 bucketlist.php 页面。每个 bucket 名称旁边有一个 Delete 按钮,该按钮允许用户完整地删除该 bucket。(处理 bucket 删除的代码将在稍后讨论)。下面展示了表行是如何生成的。

清单 6. 创建 bucket 列表
<p>Here are your buckets:</p>
<table border='1' cellpadding='5'>
  <?php

  // Create a table row for each bucket.
  $list = $s3->getBuckets();
  foreach($list as $bucket) {
    echo "<tr><td>&nbsp;<a href='bucketlist.php?bucketname=$bucket'>$bucket</a>";
    echo "</td><td>";

    $contents = $s3->getObjectsByBucket($bucket);
    if (count($contents)) {
      echo "<form action='$PHP_SELF' method='post' ";
      echo "onsubmit='return confirm(\"Bucket $bucket is not empty! Do you ";
      echo "really want to delete it?\");'>";
      echo "<input type='hidden' name='deleteeverything' value='1'/>";
    }
    else {
      echo "<form action='$PHP_SELF' method='post' ";
      echo "onsubmit='return confirm(\"Do you really want to delete ";
      echo "bucket $bucket?\");'>";
    }
    echo "<input type='hidden' name='buckettodelete' value='$bucket'/>";
    echo "<input type='submit' value='Delete'>";
    echo "</form></td></tr>";
  }
  ?>
  </table>

注意,链接将 bucket 名称传递给 bucketlist.php 文件。

创建一个新的 bucket

创建新 bucket 有一些麻烦,因为 Amazon 对 bucket 名称有一些限制:

  • bucket 名称必须在 3 到 63 个字符之间。
  • 只能够包含小写字母、数字、句点和小横线。早期版本的 S3 允许在 bucket 名称中使用下划线。如果您访问的是早期的 S3 帐户,那么很可能会发现某些 bucket 命名违背了这一原则。
  • 必须以数字或字母开头
  • 不能是 IP 地址(10.14.14.107 是不允许的)。
  • 不能使用小横线结尾。
  • 不能在句点的旁边使用小横线(doug-.tidwell 是不允许的)。

Zend_Service_Amazon_S3 类提供了一个名为 _validBucketName() 的方法,它对 bucket 名称执行一定程度的检验。不幸的是,此方法的代码没有与最新的 Amazon 命名约定同步。bucket 名称可能会通过 _validBucketName() 测试,但是它在请求发向 Amazon 时仍然会失败。

新 bucket 名称的表单非常简单。

清单 7. 创建新 bucket 的表单
<h2>Create a new bucket</h2>
<form action="<?= $PHP_SELF ?>" method="post">
  <p>Enter a name for your new bucket: 
    <br/><br/>
    <input type="text" name="newbucketname" size="63"/>
    <br/><br/>
    <i>A bucket name can contain only lowercase letters, 
    periods and dashes, <br/>it should start with a
    letter or digit, and it can't be an IP address.</i>
    <br/><br/>
    <input type="submit" value="Create bucket"/>
  </p>
  </form>

注意表单将新的 bucket 名称提交给它本身。单击 Create bucket 按钮将使 PHP 文件通过新的 bucket 名称调用其自身。

图 2. 创建新 bucket 的表单
创建新 bucket 的表单
创建新 bucket 的表单

用于创建新 bucket 的 PHP 代码将检查名称是否有效。如果该名称通过由 Zend_Service_Amazon_S3 类提供的测试,那么代码将调用 createBucket() 方法。非零响应代码意味着 bucket 已被成功创建;0 表示 bucket 已经存在。任何更为严重的错误将被作为异常抛出,并尽可能优雅地处理。

清单 8. 新建一个 bucket
<?php

if (array_key_exists('newbucketname', $_POST) &&
    strlen($_POST['newbucketname']) > 0) {
  try {
    if ($s3->_validBucketName($_POST['newbucketname'])) {
      $responseCode = $s3->createBucket($_POST['newbucketname']);
      if ($responseCode)
        echo "The bucket ".$_POST['newbucketname']." was created successfully.";
      else
        echo "The bucket ".$_POST['newbucketname']." already exists.";
    }

    else
      echo "Sorry, but ".$_POST['newbucketname']." isn't a valid bucket name.";
  }

  catch (Zend_Service_Amazon_S3_Exception $s3e) {
    ...
  }

  catch (Zend_Uri_Exception $urie) {
    ...
  }
  }

列出 bucket 中的所有对象

单击 s3.php 中的 bucket 名称将链接到 bucketlist.php 文件。该文件显示 bucket 中的所有对象,同时显示每个对象的元数据和与其内容的链接。

图 3. bucket 中的对象的列表
bucket 中的对象的列表
bucket 中的对象的列表

Zend 提供了极其有用的 getObjectsByBucket() 方法。对于给定的 bucket 名称,getObjectsByBucket() 将返回一组对象名称。要显示元数据,代码将针对 bucket 中的每一项调用 getInfo() 方法。大量调用 getInfo() 将使效率变得非常低下,千万不要在生产应用程序中这样做。

清单 9. 检索和显示元数据
$stuff = $s3->getObjectsByBucket($bucketName);
    
if (count($stuff)) { 
?>
  <table border='1' cellpadding='5'>
  ...
  <?php  
  foreach ($stuff as $name) {
  ?>
    <tr>
      <?php
        echo "<td>&nbsp;<a href='$s3_url/$bucketName/$name'>$name</a></td>";
        $metadata = $s3->getInfo($bucketName."/".$name);
      ?>
      <td style='text-align: right;'>&nbsp;
        <?= number_format($metadata['size']) ?></td>
      <td>&nbsp;<?= $metadata['type'] ?></td>
      <td>&nbsp;<?= date("j M Y - H:i", $metadata['mtime']) ?></td>
      <td>
      ?>

创建新对象

这是目前为止本例中最复杂的一个任务。要创建一个新对象,对象名必须遵守 Amazon 的命名规则,并且对象的数据必须是可用的。表单将要求用户为对象选择一个文件、一个访问策略和一个可选名称。

图 4. 用于创建新对象的表单
用于创建新对象的表单
用于创建新对象的表单

代码接受通过 Browse 按钮选择的文件,并结合使用对象名(如果有的话)和基文件名创建新对象的名称。例如, 如果所选的文件为 c:\Documents and Settings\My Documents\My Pictures\doug.jpg,并且对象名为 pictures,那么 PHP 代码将尝试创建一个名为 pictures/doug.jpg 的对象。

要成功地上传一个文件,HTML 表单必须使用 POST 方法,并且它必须将其 enctype 属性设置为 multipart/form-data

清单 10. 用于创建新对象的表单
<h2>Add an object to this bucket</h2>
<form action='<?= $PHP_SELF ?>' method='POST' enctype='multipart/form-data'>
  <input type='hidden' name='bucketname' value='<?= $bucketName ?>'/>
  <input type='file' name='objecttoadd'/>
  <p>Enter text to be appended to the object name (optional): 
  <input type='text' name='objectname'/> 
  <br/><i>Example: pictures/2009</i>
  <br/><br/>Who can see this object:
  <input type='radio' name='permissions' value='private'>Just me</input> &nbsp;
  <input type='radio' name='permissions' value='public' checked>Anybody</input>
  <br/><br/>
  <input type='submit' value='Add this object'/>
  </p>
  </form>

用于创建新对象的代码类似于清单 11 所示。

清单 11. 创建一个新对象
try {
    $baseFileName = basename($_FILES['objecttoadd']['name']);
    if (strlen($_POST['objectname'])) 
      $newFileName = $_POST['objectname']."/".$baseFileName;
    else
      $newFileName = $baseFileName;
    $escapedFileName = str_replace(array("\\", "_", ":"), "-", $newFileName);

    if ($_POST['permissions'] == 'private')
      $permissions = array(Zend_Service_Amazon_S3::S3_ACL_HEADER 
                           => Zend_Service_Amazon_S3::S3_ACL_PRIVATE);
    else
      $permissions = array(Zend_Service_Amazon_S3::S3_ACL_HEADER 
                           => Zend_Service_Amazon_S3::S3_ACL_PUBLIC_READ);
          
    $s3->putObject($bucketName."/".$escapedFileNam
e,  file_get_contents($_FILES['objecttoadd']['tmp_name']
),                   $permissions);
    echo "The object $escapedFileName was created successfully.";
  }

  catch (Zend_Service_Amazon_S3_Exception $s3e) {
    ...
  }

  catch (Zend_Http_Client_Exception $hce) {
    ...
    }

代码使用 PHP basename()str_replace() 函数检索基文件名并使用小横线替换任何反斜杠、下划线或句点。来自表单的 permissions 值用于判断对新对象的访问策略。如果用户选择 “Just me”,那么对象被标记为私有;否则,该对象是公开可用的。(S3 和 Zend Framework 都支持另外两个访问策略选项:一个用于只和特定的经过身份验证的 S3 用户共享对象;另一个用于使用特定的方式共享对象 —— Amazon 向对象请求者收取传递对象所需的带宽费用。)

删除对象

如果用户单击对象旁边的 Delete 按钮,那么将询问用户确认选择。

图 5. 删除对象
删除对象
删除对象

在创建 bucket 清单时,将生成 Delete 按钮以及消息框中的文本。用于生成 Delete 按钮的 PHP 代码类似清单 12 所示。

清单 12. 确认是否删除对象的表单
<form action='<?= $PHP_SELF ?>' method='post'
  onsubmit='return confirm("Do you really want to delete this object?");'>
  <input type='hidden' name='objecttodelete' value='$name'/>
  <input type='hidden' name='bucketname' value='$bucketName'/>
  <input type='submit' value='Delete'/>
  </form>

和所有用于创建或删除 bucket 或对象的表单一样,该表单的操作就是调用它本身。如果用户单击 Cancel,JavaScript confirm() 函数将取消操作。假设用户选择继续删除对象,那么用于执行删除的代码将非常简单。

清单 13. 删除对象
try {
  $s3->removeObject($bucketName."/".$_POST['objecttodelete']);
}

catch (Zend_Service_Amazon_S3_Exception $s3e) {
  ...
  }

页面将重新加载,更新显示 bucket 中的任何内容。

注意:Amazon S3 是一个分布式文件系统,旨在应对单个设备的多个故障。当添加或删除对象时,随着更改被反映到整个系统,有时会出现一个传播延迟。在某些情况下,在重新加载后的页面中,被删除的对象仍然位于 bucket 中,其大小通常为 0。重新加载页面通常为 S3 提供了足够的时间来重新同步其自身。

删除 bucket

在我们的例子中,最激烈的操作就是删除 bucket。这是因为 bucket 的名称在整个 S3 内必须是惟一的,因此可能出现这样一种情况,用户删除了一个 bucket,然后尝试稍后重新创建它,然而发现另外一个用户在同一时间创建了一个具有相同名称的 bucket。要使示例变得更强大(或更危险),代码将允许用户删除非空 bucket。S3 没有提供删除非空 bucket 的能力。该功能需要借助 Zend 提供的一个方便的方法实现。

如果用户对一个空的 bucket 单击 Delete 按钮,将要求他确认自己的选择。

图 6. 删除空 bucket
删除空 bucket
删除空 bucket

如果用户对一个非空 bucket 单击 Delete 按钮,将向他显示一个警告意味更强的消息。

图 7. 删除一个非空 bucket
删除一个非空 bucket
删除一个非空 bucket

某个给定 bucket 是否为空,是在创建 bucket 列表时决定的。下面的 PHP 代码就是用来创建 bucket 列表的。

清单 14. 为非空 bucket 生成警告消息
  $list = $s3->getBuckets();
  foreach($list as $bucket) {
    ...

    $contents = $s3->getObjectsByBucket($bucket);
    if (count($contents)) {
      echo "<form action='$PHP_SELF' method='post' ";
      echo "onsubmit='return confirm(\"Bucket $bucket is not empty! Do you ";
      echo "really want to delete it?\");'>";
      echo "<input type='hidden' name='deleteeverything' value='1'/>";
    }
    else {
      echo "<form action='$PHP_SELF' method='post' ";
      echo "onsubmit='return confirm(\"Do you really want to delete ";
      echo "bucket $bucket?\");'>";
    }
    echo "<input type='hidden' name='buckettodelete' value='$bucket'/>";
    echo "<input type='submit' value='Delete'>";
    echo "</form></td></tr>";
  }
  ?>
  </table>

当 PHP 代码创建列出用户帐户中所有 bucket 的表格时,它使用 getObjectsByBucket() 获得每个 bucket 中的所有对象名的数组。如果数组不是空的,那么代码将生成一条严厉的确认消息。

要实际删除 bucket,代码需要执行多个步骤。首先,它将查看 bucket 名称是否有效。如果是的话,它将检查 bucket 的内容以查看其是否为空。如果为空,那么将删除此 bucket。如果不为空,那么代码将查看 deleteeverything 参数。如果该参数被设置为 true,那么代码将删除该 bucket。代码使用 Zend Framework 提供的 cleanBucket() 方法。cleanBucket() 方法获取 bucket 中所有对象的列表,然后逐一删除它们,直到 bucket 为空。当 bucket 最后为空时,它也将被删除。

清单 15. 删除 bucket
try {
  if ($s3->_validBucketName($_POST['buckettodelete'])) {
    $stuff = $s3->getObjectsByBucket($_POST['buckettodelete']);
    if (!count($stuff)) 
      $responseCode = $s3->removeBucket($_POST['buckettodelete']);
    else 
      if (array_key_exists('deleteeverything', $_POST)) {
        $s3->cleanBucket($_POST['buckettodelete']);
        $responseCode = $s3->removeBucket($_POST['buckettodelete']);
      }
      ...

结束语

本文介绍了一个围绕 Zend_Service_Amazon_S3 类构建的 bucket 浏览器。存储在云中的对象可以被查看、删除或替换,并且可以轻松地上传新对象。最妙的是,不需要执行任何 REST 或 SOAP 调用。使用 Zend Framework 的程序员不需要计算签名或考虑 HTTP 响应代码;他们只需要处理他们的数据。本系列后续文章将演示其他可以简化云计算的 Zend 类。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Web development, Cloud computing
ArticleID=435606
ArticleTitle=使用 PHP 实现云计算,第 1 部分: 结合使用 Amazon S3 和 Zend Framework
publish-date=10142009