一个紧凑、无依赖的Amazon S3 API客户端,实现了最常用的功能

2.3.2 2024-02-19 10:08 UTC

This package is auto-updated.

Last update: 2024-08-29 12:53:34 UTC


README

一个紧凑、无依赖的Amazon S3 API客户端,实现了最常用的功能。

这个库旨在与Amazon S3正常工作,以及S3兼容的服务,如但不限于Wasabi、Google存储、Synology C2、ExoScale等。

为什么重造轮子

在与Amazon的基于Guzzle的AWS SDK遇到许多难以调试的问题后,我们决定为Amazon S3制作自己的连接器。这绝对不是一个完整的实现,只是S3所需功能的子集。设计目标是简单性、无外部依赖和低内存占用。

此代码最初基于由Donovan Schonknecht编写的S3.php,该代码在类似BSD的许可下可用。此存储库不再反映原始作者的代码,不应与之混淆。

此软件根据GNU通用公共许可证版本3分发,或者根据您的选择,任何由自由软件基金会(FSF)发布的后续版本。简而言之,它是GPL-3.0或后续版本,如composer.json中所述。

关于版本2的重要说明

自2.0版本开始支持PHP版本

Akeeba Amazon S3连接器2.0已放弃对PHP 5.3至7.0版本的支持。

此版本中最显著的变化是所有方法都使用参数和返回值的标量类型提示。这 可能 会破坏依赖于隐式类型转换的现有消费者。

自2.3版本起命名空间更改

直到包括2.2版本,库的命名空间是\Akeeba\Engine\Postproc\Connector\S3v4。从库的2.3版本开始,命名空间已更改为\Akeeba\S3

库自动注册旧类的新别名,从而确保更新库不会引入向后不兼容的更改。这就是为什么这不是一个主要版本更新。别名将至少保留到库的3.0版本。

使用连接器

在使用或引用库中的任何类之前,您需要定义一个常量

defined('AKEEBAENGINE') or define('AKEEBAENGINE', 1);

所有库文件都有一行类似以下内容

defined('AKEEBAENGINE') or die();

以防止直接访问库文件。这是故意的。此库的主要用例是大规模分发的软件,该软件安装在网络根目录的公开可访问子目录中。此行可以防止在配置错误的服务器上直接访问这些文件时,从PHP错误消息中意外泄露路径。

如果您正在编写Joomla扩展,特别是插件或模块,请始终在定义之前检查是否已定义该常量。谢谢!

获取连接器对象

$configuration = new \Akeeba\S3\Configuration(
	'YourAmazonAccessKey',
	'YourAmazonSecretKey'
);

$connector = new \Akeeba\S3\Connector($configuration);

如果您在Amazon EC2实例内部运行,您可以使用附加到EC2实例的IAM角色从实例的元数据服务器获取临时凭据。在这种情况下,您需要这样做(169.254.169.254是托管实例元数据缓存服务的固定IP)

$role = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/');
$jsonCredentials = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $role);
$credentials = json_decode($jsonCredentials, true);
$configuration = new \Akeeba\S3\Configuration(
	$credentials['AccessKeyId'],
	$credentials['SecretAccessKey'],
	'v4',
	$yourRegion
);
$configuration->setToken($credentials['Token']);

$connector = new \Akeeba\S3\Connector($configuration);

其中 $yourRegion 是您的存储桶的AWS区域,例如 us-east-1。请注意,我们将安全令牌($credentials['Token'])传递给配置对象。这是必需的。元数据服务返回的临时凭据没有它是无法工作的。

另一个值得注意的点是有临时凭证不会永久有效。请检查 $credentials['Expiration'] 以查看它们何时将到期。亚马逊建议你在缓存的凭证即将到期前 10 分钟从元数据服务重新获取新凭证。元数据服务保证在此时间之前提供新鲜的临时凭证。

列出存储桶

$listing = $connector->listBuckets(true);

返回一个类似这样的数组

array(2) {
  'owner' =>
  array(2) {
    'id' =>
    string(64) "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    'name' =>
    string(8) "someUserName"
  }
  'buckets' =>
  array(3) {
    [0] =>
    array(2) {
      'name' =>
      string(10) "mybucket"
      'time' =>
      int(1267730711)
    }
    [1] =>
    array(2) {
      'name' =>
      string(10) "anotherbucket"
      'time' =>
      int(1269516249)
    }
    [2] =>
    array(2) {
      'name' =>
      string(11) "differentbucket"
      'time' =>
      int(1354458048)
    }
  }
}

列出存储桶内容

$listing = $connector->getBucket('mybucket', 'path/to/list/');

如果你想列出“子目录”,你需要这样做

$listing = $connector->getBucket('mybucket', 'path/to/list/', null, null, '/', true);

最后一个参数(公共前缀)控制“子目录”的列出

上传(小型)文件

从文件

$input = \Akeeba\S3\Input::createFromFile($sourceFile);   
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');

从字符串

$input = \Akeeba\S3\Input::createFromData($sourceString);   
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');

从流资源

$input = \Akeeba\S3\Input::createFromResource($streamHandle, false);   
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');

在所有情况下,文件的全部内容都必须加载到内存中。

使用多部分(分块)上传上传大文件

文件以 5Mb 的块上传。

$input = \Akeeba\S3\Input::createFromFile($sourceFile);
$uploadId = $connector->startMultipart($input, 'mybucket', 'mypath/movie.mov');

$eTags = array();
$eTag = null;
$partNumber = 0;

do
{
	// IMPORTANT: You MUST create the input afresh before each uploadMultipart call
	$input = \Akeeba\S3\Input::createFromFile($sourceFile);
	$input->setUploadID($uploadId);
	$input->setPartNumber(++$partNumber);
	
	$eTag = $connector->uploadMultipart($input, 'mybucket', 'mypath/movie.mov');

	if (!is_null($eTag))
	{
		$eTags[] = $eTag;
	}
}
while (!is_null($eTag));

// IMPORTANT: You MUST create the input afresh before finalising the multipart upload
$input = \Akeeba\S3\Input::createFromFile($sourceFile);
$input->setUploadID($uploadId);
$input->setEtags($eTags);

$connector->finalizeMultipart($input, 'mybucket', 'mypath/movie.mov');

只要你能跟踪上传 ID、PartNumber 和 ETags,你就可以在每个单独的页面加载中调用每个 uploadMultipart,以防止超时。

获取预签名 URL

允许浏览器直接下载文件,而无需暴露你的凭证,也不需要通过你的服务器

$preSignedURL = $connector->getAuthenticatedURL('mybucket', 'path/to/file.jpg', 60);

最后一个参数控制此 URL 将在未来多少秒内有效。

下载

到具有绝对路径 $targetFile 的文件

$connector->getObject('mybucket', 'path/to/file.jpg', $targetFile);

到字符串

$content = $connector->getObject('mybucket', 'path/to/file.jpg', false);

删除一个对象

$connector->deleteObject('mybucket', 'path/to/file.jpg');

测试一个对象是否存在

try
{
    $headers = $connector->headObject('mybucket', 'path/to/file.jpg');
    $exists  = true;
}
catch (\Akeeba\S3\Exception\CannotGetFile $e)
{
    $headers = [];
    $exists  = false;
}

变量 $headers 包含由 [HeadObject(https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) API 调用返回的 S3 头部信息。头部键总是小写。请注意,并非 所有亚马逊在其文档中描述的头部信息都会在每次请求中返回。

配置选项

配置选项具有可选方法,可用于在连接器中启用一些有用的功能。

你需要在对连接器构造函数传递之前,针对配置对象执行这些方法。例如

$configuration = new \Akeeba\S3\Configuration(
	'YourAmazonAccessKey',
	'YourAmazonSecretKey'
);

// Use v4 signatures and Dualstack URLs
$configuration->setSignatureMethod('v4');
$configuration->setUseDualstackUrl(true);

$connector = new \Akeeba\S3\Connector($configuration);

HTTPS 与普通 HTTP

不建议使用普通 HTTP 连接到亚马逊 S3。然而,如果你没有其他选择,你可以告诉配置对象使用普通 HTTP URL

$configuration->setSSL(false);

注意:HTTPS 仅当 PHP 可以验证端点的 TLS 证书时才能正常工作。这可能在使用本地测试服务(例如 LocalStack)或一些名称中包含点的存储桶时不是这样。此外,如果你在 Windows 上,请注意 PHP 不包含证书颁发机构缓存,也没有系统范围的 CA 缓存;你需要下载它并配置 PHP,或者在 composer.json 文件中使用 composer/ca-bundle

自定义端点

你可以通过更改端点 URL 使用 Akeeba Amazon S3 连接器库与 S3 兼容的 API,例如 DigitalOcean 的 Spaces。

请注意,如果 S3 兼容的 API 使用 v4 签名,你需要在初始化对象时输入特定于区域的端点域名和区域,例如

// DigitalOcean Spaces using v4 signatures
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
$configuration = new \Akeeba\S3\Configuration(
	'532SZONTQ6ALKBCU94OU',
	'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
    'v4',
    'nyc3'
);
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');
$configuration->setRegion('nyc3');
$configuration->setSignatureMethod('v4');

$connector = new \Akeeba\S3\Connector($configuration);

如果你的 S3 兼容的 API 使用 v2 签名,你不需要指定一个区域。

// DigitalOcean Spaces using v2 signatures
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
$configuration = new \Akeeba\S3\Configuration(
	'532SZONTQ6ALKBCU94OU',
	'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
    'v2'
);
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');

$connector = new \Akeeba\S3\Connector($configuration);

注意:设置端点会重置签名版本和区域。这就是为什么你需要在设置端点后 再次 设置它们,就像上面的第一个示例中看到的那样。

旧路径样式的访问

该库执行S3 API调用时,默认将使用子域名风格的访问方式。也就是说,端点将使用存储桶的名称作为前缀。例如,位于eu-west-1区域的名为example的存储桶,将通过端点URL example.s3.eu-west-1.amazonaws.com进行访问。

如果你有包含DNS环境中无效字符(尤其是点和大写字母)的存储桶,这将失败。你需要使用传统的路径风格。在这种情况下,使用的端点是通用的区域特定端点(例如,上面示例中的s3.eu-west-1.amazonaws.com),API URL将使用存储桶名称作为前缀。

你需要做的是

$configuration->setUseLegacyPathStyle(true);

注意事项

  • 如果你使用的是Amazon AWS S3标准版,则此功能不适用于v2签名。如果你使用的是自定义端点,则很可能适用于v2签名。
  • 此选项对预授权(预签名)URL没有影响。默认情况下,这些URL使用传统的路径风格访问。

双栈(IPv4和IPv6)支持

Amazon S3支持双栈URL,这些URL解析为IPv4和IPv6地址。默认情况下,它们不使用。如果你想启用此功能,你需要这样做

$connector->setUseDualstackUrl(true);

注意事项:此选项仅在您使用Amazon S3标准版时才生效。它对自定义端点没有任何影响。Amazon S3已弃用双栈支持。我们强烈建议您不要再使用它。

替代日期格式

默认情况下,此库使用标准日期格式D, d M Y H:i:s O,Amazon错误地将其记录为"ISO 8601"(它不是,请参阅ISO 8601维基百科条目以供参考)。大多数第三方、兼容Amazon S3的服务使用相同的格式,并且可以很好地理解它。

少数服务不理解日期格式末尾的GMT偏移量,而需要格式D, d M Y H:i:s T。你可以设置一个标志来启用此行为,如下所示

$configuration->setAlternateDateHeaderFormat(true);

注意事项:启用此标志会破坏与S3标准版的兼容性。

使用HTTP日期头而不是X-Amz-Date

Amazon指出,你应该使用标准的HTTP Date头,只有在无法使用标准头时才使用X-Amz-Date头,例如创建预授权(已签名)URL或当你的HTTP库不允许你设置标准头时。

不幸的是,一些第三方S3兼容服务(如Wasabi和ExoScale)根本不支持标准的Date头。使用它会导致它们错误地输出签名错误的错误消息。这就是为什么默认情况下,我们通过X-Amz-Date头传递请求日期和时间的原因。

如果你使用的是第三方服务,该服务由于任何原因不理解X-Amz-Date头,你需要设置一个标志,强制使用标准的Date头,如下所示

$configuration->setUseHTTPDateHeader(true);