PAYATHON 2026

AWS SDK for PHP 访问 S3 模拟器时 URL 格式为何不同?

数据小陈

日志中的 URL 使用 S3 支持的 virtual-hosted-style,格式没有问题。本地模拟器返回 NoSuchBucket,应先检查寻址方式是否匹配,以及目标 bucket 是否已在当前模拟器实例中创建。仅凭这段日志无法确定具体原因。

如果本地服务按 path-style 解析请求,可以在 AWS SDK for PHP v3 的 S3Client 配置中设置 use_path_style_endpoint => true。对应的 URL 通常应为:

http://localhost:9999/customer-artifacts/some-path

bucket 名不需要在路径中重复两次。

两种 URL 分别如何表示 bucket 和对象?

S3 的两种常见寻址方式中,bucket 名的位置不同。AWS 对普通 bucket 的访问文档说明了这两种格式:AWS S3 寻址说明。

假设:

Bucket = customer-artifacts
Key    = some-path/documents/421e49d410d9fef81740.png

virtual-hosted-style 将 bucket 放在主机名中:

http://customer-artifacts.localhost:9999/some-path/documents/421e49d410d9fef81740.png

path-style 将 bucket 放在路径的第一段:

http://localhost:9999/customer-artifacts/some-path/documents/421e49d410d9fef81740.png

问题中猜测的地址:

http://localhost:9999/customer-artifacts/customer-artifacts/some-path

按照通常的 path-style 规则,会被解释为:

Bucket = customer-artifacts
Key    = customer-artifacts/some-path

只有对象 key 本来就以 customer-artifacts/ 开头时,这个重复才符合预期。

为什么生产环境正常,本地却找不到 bucket?

原代码调用了:

$s3Client->upload($bucket, $filePath, $content, $privacy, $options);

这里的 $filePath 用作 S3 对象的 key。SDK 根据端点配置和寻址规则,将 $bucket 和 key 组合成请求 URL。

日志表明,这次请求将 customer-artifacts 放在了主机名中。真实 AWS S3 支持这种寻址方式,所以生产环境可以正常工作。

本地环境还需要满足几个条件:

  • 模拟器能够从请求的主机名识别 bucket,或者客户端改用模拟器支持的 path-style。
  • customer-artifacts.localhost 能从运行 PHP 的环境解析并连接到正确的服务。
  • customer-artifacts 已存在于当前连接的模拟器实例及其存储状态中。

收到 NoSuchBucket 说明已经获得了服务端错误响应,但不能据此确定请求是否到达了预期实例,也不能确定服务端是否正确识别了 bucket。仅凭现有材料,无法断定 S3 Ninja、S3Mock 和 LocalStack 都不支持这种寻址方式。

如何配置 PHP SDK 使用 path-style?

以 AWS SDK for PHP v3 为例,S3Client 提供了 use_path_style_endpoint 选项,设置为 true 后默认使用 path-style。参见 S3Client 配置文档。

use Aws\S3\S3Client;

$s3Client = new S3Client([
    'version' => 'latest',
    'region' => 'us-east-1',
    'endpoint' => 'http://localhost:9999',
    'use_path_style_endpoint' => true,
    'credentials' => [
        'key' => 'local-access-key',
        'secret' => 'local-secret-key',
    ],
]);

示例中的 region 和凭证需要按模拟器的要求调整。占位凭证仅适用于接受这类凭证的本地服务。endpoint 填写服务的基础地址,不要自行附加 bucket 名。

配置完成后,可以保留原来的 upload() 调用。先确认实际发出的 URL 已变为:

http://localhost:9999/customer-artifacts/some-path/documents/421e49d410d9fef81740.png

本地端点和 path-style 配置应放在本地环境配置中。生产环境继续使用适合真实 AWS S3 的配置。

改完配置后仍报错,应该检查什么?

1. 确认 bucket 已在本地创建

生产环境中存在这个 bucket,并不意味着模拟器中也存在。向 bucket 上传对象也不能替代创建 bucket。

可以使用同一个客户端检查:

$s3Client->headBucket([
    'Bucket' => 'customer-artifacts',
]);

如果确认尚未创建,再按模拟器要求创建。例如,接受以下参数的本地服务可以使用:

$s3Client->createBucket([
    'Bucket' => 'customer-artifacts',
]);

不要将所有 headBucket() 异常都当作 bucket 不存在,还应区分权限、连接和其他服务端错误。

2. 检查 endpoint 与运行位置

如果 PHP 在容器中运行,localhost 指向 PHP 所在的容器。应根据部署方式填写模拟器的服务名、主机地址和端口。

同时确认创建 bucket 和上传对象使用的是同一个服务实例。模拟器重启后是否保留 bucket,取决于其持久化配置。

3. 使用最小上传请求隔离问题

确认 bucket 可访问后,可以先上传一个简单对象:

$s3Client->putObject([
    'Bucket' => 'customer-artifacts',
    'Key' => 'some-path/probe.txt',
    'Body' => 'probe',
]);

这一步用于确认端点、bucket 和基本上传链路。成功后,再恢复原调用中的 Metadata、ChecksumAlgorithm => 'SHA256' 等参数,并测试 tagging。

模拟器对附加功能的支持需要分别确认。当前日志首先指向 bucket 查找问题,没有证据说明 SHA256 或 tagging 就是此次错误的原因。

备注:内容仅供参考。