在下载文件时,通过使用条件如最后修改时间或ETag来限制下载,可以确保只有在文件有更新时才执行下载。如果条件不满足,系统将返回错误而不会下载文件。这种方法可以避免重复下载未更改的文件,从而减少网络流量和资源使用,提高下载效率。
前提条件
已上传文件到OSS。具体操作,请参见上传文件。
如果要下载归档存储类型的Object,请确保该Object已进入解冻状态或Bucket已开启归档直读。具体操作,请参见解冻Object和归档直读。
如果要下载冷归档存储或者深度冷归档存储类型的Object,请确保该Object已进入解冻状态。具体操作,请参见解冻Object。
已具有
oss:GetObject
权限。具体操作,请参见RAM Policy常见示例。
使用场景
应用资源更新:应用仅在检测到OSS文件ETag变更或最后修改时间更新时下载新资源,最小化数据使用,优化用户体验。
数据同步与增量备份:基于OSS的最后修改时间或ETag,确保只同步或备份更改过的文件,节省带宽,保障数据的时效性和一致性。
文件共享和同步:使用ETag或最后修改时间进行验证,仅在必要时同步文件,加速协作,降低数据消耗。
限定条件
OSS支持的限定条件如下:
参数 | 描述 |
If-Modified-Since | 如果指定的时间早于实际修改时间或指定的时间不符合规范,则直接返回Object,并返回200 OK;如果指定的时间等于或者晚于实际修改时间,则返回304 Not Modified。 时间格式:GMT,例如 默认值:无 |
If-Unmodified-Since | 如果指定的时间等于或者晚于Object实际修改时间,则正常传输Object,并返回200 OK;如果指定的时间早于实际修改时间,则返回412 Precondition Failed。 时间格式:GMT,例如 If-Modified-Since和If-Unmodified-Since可以同时使用。 默认值:无 |
If-Match | 如果传入的ETag和Object的ETag匹配,则正常传输Object,并返回200 OK;如果传入的ETag和Object的ETag不匹配,则返回412 Precondition Failed。 Object的ETag值用于验证数据是否发生了更改,您可以基于ETag值验证数据完整性。 默认值:无 |
If-None-Match | 如果传入的ETag值和Object的ETag不匹配,则正常传输Object,并返回200 OK;如果传入的ETag和Object的ETag匹配,则返回304 Not Modified。 If-Match和If-None-Match可以同时使用。 默认值:无 |
OSS控制台、命令行工具ossutil不支持限定条件下载。
操作步骤
使用阿里云SDK
以下仅列举部分SDK使用GetObject
接口限定条件下载的代码示例。关于其他限定条件下载的代码示例,请参见SDK简介。
Java
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.GetObjectRequest;
import java.io.File;
import java.util.Date;
public class Demo {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写不包含Bucket名称在内的Object完整路径,例如testfolder/exampleobject.txt。
String objectName = "testfolder/exampleobject.txt";
String pathName = "D:\\localpath\\examplefile.txt";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
GetObjectRequest request = new GetObjectRequest(bucketName, objectName);
// 假设Object最后修改时间为2023年9月26日13:27:04,当填写早于该时间的Data对象时(例如Tue Sep 25 13:27:04 CST 2023),将满足If-Modified-Since的限定条件,并触发下载行为。
request.setModifiedSinceConstraint(new Date("Tue Sep 25 13:27:04 CST 2023"));
// 下载OSS文件到本地文件。
ossClient.getObject(request, new File(pathName));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
PHP
<?php
if (is_file(__DIR__ . '/../autoload.php')) {
require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以杭州为例,其它Region请按实际情况填写。
$endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 填写Bucket名称。
$bucket= "yourBucketName";
// 填写Object名称。Object名称为不包含Bucket名称在内的Object的完整路径,例如exampledir/exampleobject.txt。
$object = "yourObjectName";
// 填写本地文件的完整路径。例如D:\\localpath\\examplefile.txt。
$localfile = "yourLocalFile";
try{
$options = array(
OssClient::OSS_HEADERS => array(
// 指定下载实际修改时间晚于Fri, 9 Apr 2021 14:47:53 GMT的Object。
OssClient::OSS_IF_MODIFIED_SINCE => "Fri, 9 Apr 2021 14:47:53 GMT",
// 指定下载实际修改时间早于或者等于Wed, 13 Oct 2021 14:47:53 GMT的Object。
OssClient::OSS_IF_UNMODIFIED_SINCE => "Fri, 13 Oct 2021 14:47:53 GMT",
// 指定下载与传入的ETag值不匹配的Object。
OssClient::OSS_IF_NONE_MATCH => '"5B3C1A2E0563E1B002CC607C****"',
// 指定下载与传入的ETag值匹配的Object。
OssClient::OSS_IF_MATCH => '"fba9dede5f27731c9771645a3986****"',
OssClient::OSS_FILE_DOWNLOAD => $localfile
)
);
$config = array(
"provider" => $provider,
"endpoint" => $endpoint,
);
$ossClient = new OssClient($config);
$ossClient->getObject($bucket, $object, $options);
} catch(OssException $e) {
printf(__FUNCTION__ . ": FAILED\n");
printf($e->getMessage() . "\n");
return;
}
Node.js
const OSS = require('ali-oss');
const client = new OSS({
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: 'yourRegion',
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
accessKeyId: process.env.OSS_ACCESS_KEY_ID,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
// 填写Bucket名称。
bucket: 'examplebucket'
});
async function main() {
try {
// 向目标Bucket上传名为exampleobject.txt的文件,文件内容自定义。
await client.put("exampleobject.txt", Buffer.from("contenttest"));
// 在请求头If-Modified-Since中指定时间,如果指定的时间早于文件实际修改时间,则下载文件。
let result = await client.get("exampleobject.txt", {
headers: {
"If-Modified-Since": new Date("1970-01-01").toGMTString(),
},
});
console.log(result.content.toString() === "contenttest");
console.log(result.res.status === 200);
// 如果指定的时间等于或者晚于文件实际修改时间,则返回304 Not Modified。
result = await client.get("exampleobject.txt", {
headers: {
"If-Modified-Since": new Date().toGMTString(),
},
});
console.log(result.content.toString() === "");
console.log(result.res.status === 304);
} catch (e) {
console.log(e.code === "Not Modified");
}
}
main();
Python
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
# 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
# 填写Bucket名称,例如examplebucket。
bucket = oss2.Bucket(auth, 'https://oss-cn-hangzhou.aliyuncs.com', 'examplebucket')
# 填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。关于Object名称命名规范的更多信息,请参见Object。
object_name = 'exampledir/exampleobject.txt'
headers = dict()
# 如果指定的时间早于Object实际修改时间,则正常下载Object,否则返回错误304 Not modified。
headers['If-Modified-Since'] = 'Mon, 13 Dec 2021 14:47:53 GMT'
# 如果指定的时间等于或者晚于Object实际修改时间,则正常下载Object,否则返回错误412 Precondition failed。
# headers['If-Unmodified-Since'] = 'Mon, 13 Dec 2021 14:47:53 GMT'
# 如果传入的ETag和Object的ETag匹配,则正常下载Object,否则返回错误412 Precondition failed。
# headers['If-Match'] = 'DC21493F505BA3739562D8CC452C****'
# 如果传入的ETag值和Object的ETag不匹配,则正常下载Object,否则返回错误304 Not modified。
# headers['If-None-Match'] = 'DC21493F505BA3739562D8CC452C****'
object_stream = bucket.get_object(object_name, headers=headers)
print(object_stream.read())
Browser.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<button id='upload'>上传</button>
<button id='download'>下载</button>
<!--导入SDK文件-->
<script type="text/javascript" src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.16.0.min.js"></script>
<script type="text/javascript">
const client = new OSS({
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
region: 'yourRegion',
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
accessKeyId: 'yourAccessKeyId',
accessKeySecret: 'yourAccessKeySecret',
// 从STS服务获取的安全令牌(SecurityToken)。
stsToken: 'yourSecurityToken',
// 填写Bucket名称,例如examplebucket。
bucket: "examplebucket",
});
const download = document.getElementById('download')
const upload = document.getElementById('upload')
// 上传文件。
upload.addEventListener('click', () => {
// 指定待上传的文件内容。
const file = new Blob(['examplecontent'])
// 指定待上传文件的完整路径,例如exampledir/exampleobject.txt。
const fileName = 'exampledir/exampleobject.txt'
const result = client.put(fileName, file).then(r => console.log(r))
})
// 下载文件。
download.addEventListener('click', () => {
// 指定待下载的字节范围。
const start = 1, end = 5
client.get('exampledir/exampleobject.txt', {
headers: {
// 在请求头If-Modified-Since中指定时间,如果指定的时间早于文件实际修改时间,则下载文件。如果指定的时间等于或者晚于文件实际修改时间,则返回304 Not Modified。
"If-Modified-Since": new Date("1970-01-01").toGMTString()
// 在请求头If-Unmodified-Since中指定时间,如果指定的时间等于或者晚于文件实际修改时间,则下载文件。如果指定的时间早于文件实际修改时间,则返回412 Precondition Failed。
//"If-Unmodified-Since": new Date(1970-01-01).toGMTString()
// 在请求头If-Match中传入ETag,如果传入的ETag和文件的ETag匹配,则下载文件。如果传入的ETag和文件的ETag不匹配,则返回412 Precondition Failed。
//"If-Match": '5B3C1A2E0563E1B002CC607C****'
// 在请求头If-None-Match中传入ETag,如果传入的ETag和文件的ETag不匹配,则下载文件。如果传入的ETag和文件的ETag匹配,则返回304 Not Modified。
//"If-None-Match": '5B3C1A2E0563E1B002CC607C****'
},
}).then(r => {
if (r.content.length > 0) {
const newBlob = new Blob([r.content], { type: r.res.headers['content-type'] });
const link = document.createElement('a')
link.href = window.URL.createObjectURL(newBlob)
link.download = 'foo.txt'
link.click()
window.URL.revokeObjectURL(link.href)
} else {
console.log('错误码', r.res.status)
console.log('没有符合条件的下载项')
}
})
})
</script>
</body>
</html>
Android
// 依次填写Bucket名称(例如examplebucket)、Object完整路径(例如exampledir/exampleobject.txt)。
// Object完整路径中不能包含Bucket名称。
String bucketName = "examplebucket";
String objectKey = "exampledir/exampleobject.txt";
// 构造下载文件请求。
Map<String, String> headers = new HashMap<>();
// 如果指定的时间早于实际修改时间,则正常传输文件,否则返回错误(304 Not modified)。
headers.put(OSSHeaders.GET_OBJECT_IF_MODIFIED_SINCE, "Fri, 13 Nov 2015 14:47:53 GMT");
// 如果指定的时间等于或者晚于文件实际修改时间,则正常传输文件,否则返回错误(412 Precondition failed)
// headers.put(OSSHeaders.GET_OBJECT_IF_UNMODIFIED_SINCE, "Fri, 13 Nov 2015 14:47:53 GMT");
// 如果指定的ETag和OSS文件的ETag匹配,则正常传输文件,否则返回错误(412 Precondition failed)
// headers.put(OSSHeaders.GET_OBJECT_IF_MATCH, "5B3C1A2E0563E1B002CC607C*****");
// 如果指定的ETag和OSS文件的ETag不匹配,则正常传输文件,否则返回错误(304 Not modified)
// headers.put(OSSHeaders.GET_OBJECT_IF_NONE_MATCH, "5B3C1A2E0563E1B002CC607C*****");
GetObjectRequest get = new GetObjectRequest(bucketName, objectKey);
get.setRequestHeaders(headers);
OSSAsyncTask task = oss.asyncGetObject(get, new OSSCompletedCallback<GetObjectRequest, GetObjectResult>() {
@Override
public void onSuccess(GetObjectRequest request, GetObjectResult result) {
// 请求成功。
InputStream inputStream = result.getObjectContent();
byte[] buffer = new byte[2048];
int len;
try {
while ((len = inputStream.read(buffer)) != -1) {
// 处理下载的数据。
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(GetObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
// 请求异常。
if (clientExcepion != null) {
// 本地异常如网络异常等。
clientExcepion.printStackTrace();
}
if (serviceException != null) {
// 服务异常。
Log.e("ErrorCode", serviceException.getErrorCode());
Log.e("RequestId", serviceException.getRequestId());
Log.e("HostId", serviceException.getHostId());
Log.e("RawMessage", serviceException.getRawMessage());
}
}
});
Go
package main
import (
"fmt"
"os"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
)
func main() {
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
provider, err := oss.NewEnvironmentVariableCredentialsProvider()
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 创建OSSClient实例。
// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// yourBucketName填写存储空间名称。
bucket, err := client.Bucket("yourBucketName")
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 假设Object最后修改时间为2023年11月21日18:43:02,则填写的UTC早于该时间时,将满足IfModifiedSince的限定条件,并触发下载行为。
date := time.Date(2023, time.November, 21, 10, 40, 02, 0, time.UTC)
// 不满足限定条件,不下载文件。
// yourObjectName填写不包含Bucket名称在内的Object的完整路径。
err = bucket.GetObjectToFile("yourObjectName", "LocalFile", oss.IfUnmodifiedSince(date))
if err == nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 满足限定条件,下载文件。
err = bucket.GetObjectToFile("yourObjectName", "LocalFile", oss.IfModifiedSince(date))
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
}
iOS
OSSGetObjectRequest *get = [OSSGetObjectRequest new];
// 填写Bucket名称。关于Bucket名称命名规范的更多信息,请参见Bucket。
get.bucketName = @"examplebucket";
// 填写不包含Bucket名称在内的Object完整路径。关于Object名称命名规范的更多信息,请参见Object。
get.objectKey = @"exampledir/exampleobject.txt";
NSMutableDictionary *headerFields = [NSMutableDictionary dictionary];
// 指定下载实际修改时间晚于Fri, 9 Apr 2021 14:47:53 GMT的Object。
[headerFields setValue:@"Fri, 13 Oct 2021 14:47:53 GMT" forKey:@"If-Modified-Since"];
// 指定下载实际修改时间早于或者等于当前时间的Object。
[headerFields setValue:[[NSDate new] oss_asStringValue] forKey:@"If-Unmodified-Since"];
// 指定下载与传入的ETag值不匹配的Object。
[headerFields setValue:@"5B3C1A2E0563E1B002CC607C****" forKey:@"If-None-Match"];
// 指定下载与传入的ETag值匹配的Object。
[headerFields setValue:@"fba9dede5f27731c9771645a3986****" forKey:@"If-Match"];
get.headerFields = headerFields;
[[[_client getObject:get] continueWithBlock:^id _Nullable(OSSTask * _Nonnull task) {
if (!task.error) {
NSLog(@"get object success!");
} else {
NSLog(@"get object error: %@", task.error);
}
return nil;
}] waitUntilFinished];
使用REST API
如果您的程序自定义要求较高,您可以直接发起REST API请求。直接发起REST API请求需要手动编写代码计算签名。更多信息,请参见GetObject。
相关文档
如何在开启了版本控制的Bucket中下载文件,请参见开启版本控制下Object的操作。
如何在暂停了版本控制的Bucket中下载文件,请参见暂停版本控制下Object的操作。
为了防止第三方未经授权从您的Bucket里下载数据,OSS提供了Bucket和Object级别的访问权限控制。更多信息,请参见访问控制概述。
如果您希望直接下载文件,您可以使用简单下载。具体操作,请参见简单下载。
如果您希望在下载大文件过程中,从下载中断的位置继续下载未完成的部分,您可以使用断点续传。具体操作,请参见断点续传下载。
如果您希望将私有Bucket的Object提供给第三方进行下载,请通过STS临时访问凭证或签名URL的方式授权第三方下载文件。具体操作,请参见授权给第三方下载。