范围下载可用于下载对象中的部分数据,可使用该特性实现大对象下载,其原理是将原始对象切分成多个分片分别依次下载,并在目标侧(例如本地文件系统)生成完整的数据。
按照 HTTP 的 Range 请求头域规范,范围下载时指定的范围区间满足从 0 开始的左闭右闭规则,例如 bytes=0-1,代表下载对象的第一个字节和第二个字节,总共两个字节。
范围下载的示例代码如下:
<?php // 假设使用 composer 安装 require_once __DIR__ . '/vendor/autoload.php'; use Tos\TosClient; use Tos\Exception\TosClientException; use Tos\Exception\TosServerException; use Tos\Model\HeadObjectInput; use Tos\Model\GetObjectInput; $file = null; $output = null; try { $client = new TosClient([ 'region' => 'your region', 'endpoint' => 'your endpoint', // 从环境变量中获取访问密钥 'ak' => getenv('TOS_ACCESS_KEY'), 'sk' => getenv('TOS_SECRET_KEY'), ]); // 获取对象元数据 $headObjectOutput = $client->headObject(new HeadObjectInput('bucket-test', 'key-test')); echo $headObjectOutput->getRequestId() . PHP_EOL; // 假设按照 20MB 切分大文件 $partSize = intval(5 * 1024 * 1024); // 通过对象大小切分下载的范围 $objectSize = $headObjectOutput->getContentLength(); $partCount = intval($objectSize / $partSize); if ($objectSize % $partSize !== 0) { $partCount++; } // 假设下载到本地大文件路径为 local_big_file_path $localBigFilePath = 'local_big_file_path'; $file = fopen($localBigFilePath, 'w'); // 通过范围下载依次下载每个分片,并写入本地文件 for ($i = 0; $i < $partCount; $i++) { $input = new GetObjectInput('bucket-test', 'key-test'); // 设置下载对象的起始位置 $input->setRangeStart($i * $partSize); // 注意 RangeEnd 的取值区间 if ($i === $partCount - 1) { // 处理最后一个分片 $input->setRangeEnd($objectSize - 1); } else { $input->setRangeEnd(($i + 1) * $partSize - 1); } // 设置写入本地文件的起始位置,一般同 RangeStart fseek($file, $partSize * $i, 0); $output = $client->getObject($input); echo $headObjectOutput->getRequestId() . PHP_EOL; while ($buf = $output->getContent()->read(65536)) { fwrite($file, $buf, strlen($buf)); } $output->getContent()->close(); echo sprintf('get object by range %d succeed', $i) . PHP_EOL; } } catch (TosClientException $ex) { echo $ex->getMessage() . PHP_EOL; } catch (TosServerException $ex) { echo $ex->getRequestId() . PHP_EOL; echo $ex->getStatusCode() . PHP_EOL; echo $ex->getErrorCode() . PHP_EOL; } finally { if (is_resource($file)) { fclose($file); } if ($output) { $output->getContent()->close(); } }