← Cloudflare R2 / r2 / objects
Загрузка объектов
Загрузить объекты в R2 можно несколькими способами. Выбор подхода зависит от размера ваших объектов и требований к производительности.
Выбор способа загрузки
Одиночная загрузка (PUT) |
Составная загрузка | |
|---|---|---|
| Подходит для | Небольшие и средние файлы (менее ~100 МБ) | Большие файлы или случаи, когда необходимы параллельная передача и возможность её возобновления |
| Максимальный размер объекта | 5 ГиБ | 5 ТиБ (до 10,000 частей) |
| Размер части | N/A | от 5 МиБ до 5 ГиБ на часть |
| Возобновляемая загрузка | Нет: необходимо перезапустить всю загрузку | Да: повторной отправке подлежат только неудавшиеся части |
| Параллельная загрузка | Нет | Да: части можно загружать одновременно |
| Когда использовать | Быстрая и простая загрузка небольших объектов | Видео, резервные копии, наборы данных и любые файлы, для которых важна надёжность |
Загрузка через панель управления
Чтобы загрузить объекты в бакет из панели управления Cloudflare:
-
На панели управления Cloudflare перейдите к разделу Объектное хранилище R2 страницу.
Перейдите в Обзор ↗ -
Выберите свой бакет.
-
Выберите Загрузить.
-
Перетащите файл в область загрузки или выбрать с компьютера.
После успешной загрузки вы получите подтверждающее сообщение.
Создание папки
Вы также можете создавать папки в панели управления, выбрав Создание папки. При этом создаётся объект нулевого размера с ключом, оканчивающимся на / который действует как заполнитель. Подробнее о том, как работают папки в R2, см. Префиксы и папки.
Загрузка через Workers API
Используйте R2 привязки в Workers для загрузки объектов на стороне сервера. См. Использование R2 в Workers для инструкций по настройке R2 binding.
Одиночная загрузка
Используйте put() , чтобы загрузить объект одним запросом. Это самый простой способ для объектов малого и среднего размера.
export default {
async fetch(request, env) {
try {
const object = await env.MY_BUCKET.put("image.png", request.body, {
httpMetadata: {
contentType: "image/png",
},
});
if (object === null) {
return new Response("Precondition failed or upload returned null", {
status: 412,
});
}
return Response.json({
key: object.key,
size: object.size,
etag: object.etag,
});
} catch (err) {
return new Response(`Upload failed: ${err}`, { status: 500 });
}
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
const object = await env.MY_BUCKET.put("image.png", request.body, {
httpMetadata: {
contentType: "image/png",
},
});
if (object === null) {
return new Response("Precondition failed or upload returned null", {
status: 412,
});
}
return Response.json({
key: object.key,
size: object.size,
etag: object.etag,
});
} catch (err) {
return new Response(`Upload failed: ${err}`, { status: 500 });
}
},
} satisfies ExportedHandler<Env>;Составная загрузка
Используйте createMultipartUpload() и resumeMultipartUpload() для больших файлов или когда вам нужно загружать части параллельно. Каждая часть должна быть не менее 5 MiB (кроме последней части).
export default {
async fetch(request, env) {
const key = "large-file.bin";
// Create a new multipart upload
const multipartUpload = await env.MY_BUCKET.createMultipartUpload(key);
try {
// In a real application, these would be actual data chunks.
// Each part except the last must be at least 5 MiB.
const firstChunk = new Uint8Array(5 * 1024 * 1024); // placeholder
const secondChunk = new Uint8Array(1024); // placeholder
const part1 = await multipartUpload.uploadPart(1, firstChunk);
const part2 = await multipartUpload.uploadPart(2, secondChunk);
// Complete the upload with all parts
const object = await multipartUpload.complete([part1, part2]);
return Response.json({
key: object.key,
etag: object.httpEtag,
});
} catch (err) {
// Abort on failure so incomplete uploads do not count against storage
await multipartUpload.abort();
return new Response(`Multipart upload failed: ${err}`, { status: 500 });
}
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = "large-file.bin";
// Create a new multipart upload
const multipartUpload = await env.MY_BUCKET.createMultipartUpload(key);
try {
// In a real application, these would be actual data chunks.
// Each part except the last must be at least 5 MiB.
const firstChunk = new Uint8Array(5 * 1024 * 1024); // placeholder
const secondChunk = new Uint8Array(1024); // placeholder
const part1 = await multipartUpload.uploadPart(1, firstChunk);
const part2 = await multipartUpload.uploadPart(2, secondChunk);
// Complete the upload with all parts
const object = await multipartUpload.complete([part1, part2]);
return Response.json({
key: object.key,
etag: object.httpEtag,
});
} catch (err) {
// Abort on failure so incomplete uploads do not count against storage
await multipartUpload.abort();
return new Response(`Multipart upload failed: ${err}`, { status: 500 });
}
},
} satisfies ExportedHandler<Env>;В большинстве случаев состояние составной загрузки (то есть uploadId и ETag загруженных частей) отслеживается клиентом, отправляющим запросы к вашему Worker. В следующем примере показан HTTP API, который клиентское приложение может вызывать для создания составной загрузки, загрузки её частей и её завершения:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const action = url.searchParams.get("action");
if (!key || !action) {
return new Response("Missing key or action", { status: 400 });
}
switch (action) {
// Step 1: Client calls POST /<key>?action=mpu-create
case "mpu-create": {
const upload = await env.MY_BUCKET.createMultipartUpload(key);
return Response.json({ key: upload.key, uploadId: upload.uploadId });
}
// Step 2: Client calls PUT /<key>?action=mpu-uploadpart&uploadId=...&partNumber=...
case "mpu-uploadpart": {
const uploadId = url.searchParams.get("uploadId");
const partNumber = Number(url.searchParams.get("partNumber"));
if (!uploadId || !partNumber || !request.body) {
return new Response("Missing uploadId, partNumber, or body", {
status: 400,
});
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
try {
const part = await upload.uploadPart(partNumber, request.body);
return Response.json(part);
} catch (err) {
return new Response(String(err), { status: 400 });
}
}
// Step 3: Client calls POST /<key>?action=mpu-complete&uploadId=...
case "mpu-complete": {
const uploadId = url.searchParams.get("uploadId");
if (!uploadId) {
return new Response("Missing uploadId", { status: 400 });
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
const body = await request.json();
try {
const object = await upload.complete(body.parts);
return new Response(null, {
headers: { etag: object.httpEtag },
});
} catch (err) {
return new Response(String(err), { status: 400 });
}
}
// Abort an in-progress upload
case "mpu-abort": {
const uploadId = url.searchParams.get("uploadId");
if (!uploadId) {
return new Response("Missing uploadId", { status: 400 });
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
try {
await upload.abort();
} catch (err) {
return new Response(String(err), { status: 400 });
}
return new Response(null, { status: 204 });
}
default:
return new Response(`Unknown action: ${action}`, { status: 400 });
}
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const action = url.searchParams.get("action");
if (!key || !action) {
return new Response("Missing key or action", { status: 400 });
}
switch (action) {
// Step 1: Client calls POST /<key>?action=mpu-create
case "mpu-create": {
const upload = await env.MY_BUCKET.createMultipartUpload(key);
return Response.json({ key: upload.key, uploadId: upload.uploadId });
}
// Step 2: Client calls PUT /<key>?action=mpu-uploadpart&uploadId=...&partNumber=...
case "mpu-uploadpart": {
const uploadId = url.searchParams.get("uploadId");
const partNumber = Number(url.searchParams.get("partNumber"));
if (!uploadId || !partNumber || !request.body) {
return new Response("Missing uploadId, partNumber, or body", {
status: 400,
});
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
try {
const part = await upload.uploadPart(partNumber, request.body);
return Response.json(part);
} catch (err) {
return new Response(String(err), { status: 400 });
}
}
// Step 3: Client calls POST /<key>?action=mpu-complete&uploadId=...
case "mpu-complete": {
const uploadId = url.searchParams.get("uploadId");
if (!uploadId) {
return new Response("Missing uploadId", { status: 400 });
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
const body = await request.json<{ parts: R2UploadedPart[] }>();
try {
const object = await upload.complete(body.parts);
return new Response(null, {
headers: { etag: object.httpEtag },
});
} catch (err) {
return new Response(String(err), { status: 400 });
}
}
// Abort an in-progress upload
case "mpu-abort": {
const uploadId = url.searchParams.get("uploadId");
if (!uploadId) {
return new Response("Missing uploadId", { status: 400 });
}
const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
try {
await upload.abort();
} catch (err) {
return new Response(String(err), { status: 400 });
}
return new Response(null, { status: 204 });
}
default:
return new Response(`Unknown action: ${action}`, { status: 400 });
}
},
} satisfies ExportedHandler<Env>;Полный справочник по Workers API см. Справочник Workers API.
Presigned URL (Workers)
Если клиентам (браузерам, мобильным приложениям) нужно загружать данные напрямую в R2 без проксирования через Worker, создайте presigned URL на стороне сервера и передайте его клиенту:
import { AwsClient } from "aws4fetch";
export default {
async fetch(request, env) {
const r2 = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
});
// Generate a presigned PUT URL valid for 1 hour
const url = new URL(
"https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/image.png",
);
url.searchParams.set("X-Amz-Expires", "3600");
const signed = await r2.sign(new Request(url, { method: "PUT" }), {
aws: { signQuery: true },
});
// Return the signed URL to the client — they can PUT directly to R2
return Response.json({ url: signed.url });
},
};import { AwsClient } from "aws4fetch";
interface Env {
R2_ACCESS_KEY_ID: string;
R2_SECRET_ACCESS_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const r2 = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
});
// Generate a presigned PUT URL valid for 1 hour
const url = new URL(
"https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/image.png",
);
url.searchParams.set("X-Amz-Expires", "3600");
const signed = await r2.sign(new Request(url, { method: "PUT" }), {
aws: { signQuery: true },
});
// Return the signed URL to the client — they can PUT directly to R2
return Response.json({ url: signed.url });
},
} satisfies ExportedHandler<Env>;Полную документацию по подписанным URL-адресам, включая GET, PUT и рекомендации по безопасности, см. в Presigned URLs.
Загрузка через S3 API
Для загрузки объектов используйте S3-совместимые SDK. Вам потребуется ID аккаунта и R2 API-токен.
Одиночная загрузка
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const fileContent = await readFile("./image.png");
const response = await S3.send(
new PutObjectCommand({
Bucket: "my-bucket",
Key: "image.png",
Body: fileContent,
ContentType: "image/png",
}),
);
console.log(`Uploaded successfully. ETag: ${response.ETag}`);import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const fileContent = await readFile("./image.png");
const response = await S3.send(
new PutObjectCommand({
Bucket: "my-bucket",
Key: "image.png",
Body: fileContent,
ContentType: "image/png",
}),
);
console.log(`Uploaded successfully. ETag: ${response.ETag}`);import boto3
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
with open("./image.png", "rb") as f:
response = s3.put_object(
Bucket="my-bucket",
Key="image.png",
Body=f,
ContentType="image/png",
)
print(f"Uploaded successfully. ETag: {response['ETag']}")Составная загрузка
Большинство SDK для S3 автоматически выполняют составную загрузку, когда размер файла превышает настраиваемый порог. В примерах ниже показаны как автоматический (высокоуровневый), так и ручной (низкоуровневый) подходы.
Автоматическая составная загрузка
SDK разбивает файл на части и загружает их параллельно.
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const upload = new Upload({
client: S3,
params: {
Bucket: "my-bucket",
Key: "large-file.bin",
Body: createReadStream("./large-file.bin"),
},
// Upload parts in parallel (default: 4)
leavePartsOnError: false,
});
upload.on("httpUploadProgress", (progress) => {
console.log(`Uploaded ${progress.loaded ?? 0} bytes`);
});
const result = await upload.done();
console.log(`Upload complete. ETag: ${result.ETag}`);import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const upload = new Upload({
client: S3,
params: {
Bucket: "my-bucket",
Key: "large-file.bin",
Body: createReadStream("./large-file.bin"),
},
leavePartsOnError: false,
});
upload.on("httpUploadProgress", (progress) => {
console.log(`Uploaded ${progress.loaded ?? 0} bytes`);
});
const result = await upload.done();
console.log(`Upload complete. ETag: ${result.ETag}`);import boto3
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
# upload_file automatically uses multipart for large files.
# For better throughput with large objects, use the manual multipart example below.
s3.upload_file(
Filename="./large-file.bin",
Bucket="my-bucket",
Key="large-file.bin",
)Ручная составная загрузка
Используйте низкоуровневый API, если вам нужен полный контроль над размером частей или порядком загрузки.
import {
S3Client,
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand,
type CompletedPart,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part
// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);
try {
const fileSize = statSync("./large-file.bin").size;
const partCount = Math.ceil(fileSize / partSize);
const parts: CompletedPart[] = [];
// Step 2: Upload each part
for (let i = 0; i < partCount; i++) {
const start = i * partSize;
const end = Math.min(start + partSize, fileSize);
const { ETag } = await S3.send(
new UploadPartCommand({
Bucket: bucket,
Key: key,
UploadId,
PartNumber: i + 1,
Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
ContentLength: end - start,
}),
);
parts.push({ PartNumber: i + 1, ETag });
}
// Step 3: Complete the upload
await S3.send(
new CompleteMultipartUploadCommand({
Bucket: bucket,
Key: key,
UploadId,
MultipartUpload: { Parts: parts },
}),
);
console.log("Multipart upload complete.");
} catch (err) {
// Abort on failure to clean up incomplete parts
try {
await S3.send(
new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
);
} catch (_abortErr) {
// Best-effort cleanup — the original error is more important
}
throw err;
}import {
S3Client,
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part
// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);
try {
const fileSize = statSync("./large-file.bin").size;
const partCount = Math.ceil(fileSize / partSize);
const parts = [];
// Step 2: Upload each part
for (let i = 0; i < partCount; i++) {
const start = i * partSize;
const end = Math.min(start + partSize, fileSize);
const { ETag } = await S3.send(
new UploadPartCommand({
Bucket: bucket,
Key: key,
UploadId,
PartNumber: i + 1,
Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
ContentLength: end - start,
}),
);
parts.push({ PartNumber: i + 1, ETag });
}
// Step 3: Complete the upload
await S3.send(
new CompleteMultipartUploadCommand({
Bucket: bucket,
Key: key,
UploadId,
MultipartUpload: { Parts: parts },
}),
);
console.log("Multipart upload complete.");
} catch (err) {
// Abort on failure to clean up incomplete parts
try {
await S3.send(
new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
);
} catch (_abortErr) {
// Best-effort cleanup — the original error is more important
}
throw err;
}import boto3
import math
import os
from concurrent.futures import ThreadPoolExecutor
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
bucket = "my-bucket"
key = "large-file.bin"
file_path = "./large-file.bin"
part_size = 16 * 1024 * 1024 # 16 MiB per part
max_workers = 10 # Number of parallel upload threads
# Step 1: Create the multipart upload
upload_id = None
mpu = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = mpu["UploadId"]
def upload_part(part_number, data):
response = s3.upload_part(
Bucket=bucket,
Key=key,
UploadId=upload_id,
PartNumber=part_number,
Body=data,
)
return {"PartNumber": part_number, "ETag": response["ETag"]}
try:
file_size = os.path.getsize(file_path)
part_count = math.ceil(file_size / part_size)
# Step 2: Upload parts in parallel
with ThreadPoolExecutor(max_workers=max_workers) as pool:
futures = []
with open(file_path, "rb") as f:
for i in range(part_count):
data = f.read(part_size)
futures.append(pool.submit(upload_part, i + 1, data))
parts = [future.result() for future in futures]
# Step 3: Complete the upload
s3.complete_multipart_upload(
Bucket=bucket,
Key=key,
UploadId=upload_id,
MultipartUpload={"Parts": parts},
)
print("Multipart upload complete.")
except Exception:
# Abort on failure to clean up incomplete parts
if upload_id:
try:
s3.abort_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id)
except Exception:
pass # Best-effort cleanup — the original error is more important
raisePresigned URL (S3 API)
Для загрузок на стороне клиента, когда пользователи загружают файлы напрямую в R2, минуя сервер, создавайте подписанный URL для PUT-запроса. Сервер создаёт URL, а клиент выполняет загрузку по этой ссылке: учётные данные API клиенту не передаются.
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const presignedUrl = await getSignedUrl(
S3,
new PutObjectCommand({
Bucket: "my-bucket",
Key: "user-upload.png",
ContentType: "image/png",
}),
{ expiresIn: 3600 }, // Valid for 1 hour
);
console.log(presignedUrl);
// Return presignedUrl to the clientimport { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
const presignedUrl = await getSignedUrl(
S3,
new PutObjectCommand({
Bucket: "my-bucket",
Key: "user-upload.png",
ContentType: "image/png",
}),
{ expiresIn: 3600 }, // Valid for 1 hour
);
console.log(presignedUrl);
// Return presignedUrl to the clientimport boto3
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
presigned_url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": "my-bucket",
"Key": "user-upload.png",
"ContentType": "image/png",
},
ExpiresIn=3600, # Valid for 1 hour
)
print(presigned_url)
# Return presigned_url to the clientПолную документацию по подписанным URL-адресам см. в Presigned URLs.
Подробнее см. в R2 Документация по S3 API для всех поддерживаемых методов S3 API.
Загрузка через CLI
Rclone
Rclone ↗ это инструмент командной строки для управления файлами в облачном хранилище. Rclone хорошо подходит для загрузки нескольких файлов с локального компьютера или копирования данных от других провайдеров облачного хранения.
Чтобы использовать rclone, установите его на своем компьютере согласно официальной документации: Установите rclone ↗.
Загрузите файлы с помощью команды rclone copy команда:
# Upload a single file
rclone copy /path/to/local/image.png r2:bucket_name
# Upload everything in a directory
rclone copy /path/to/local/folder r2:bucket_nameПроверьте загрузку с помощью rclone ls:
rclone ls r2:bucket_nameДополнительные сведения см. в нашем пример rclone.
Wrangler
Используйте Wrangler для загрузки объектов. Выполните r2 object put команда:
wrangler r2 object put test-bucket/image.png --file=image.pngМожно задать Content-Type (тип MIME), Content-Disposition, Cache-Control и другие метаданные HTTP-заголовков через необязательные флаги.
Сведения о составной загрузке
Ограничения на размер части
- Минимальный размер части: 5 MiB (кроме последней части)
- Максимальный размер части: 5 GiB
- Максимальное количество частей: 10,000
- Все части, кроме последней, должны быть одинакового размера
Жизненные циклы незавершённых загрузок
Незавершённые составные загрузки по умолчанию автоматически отменяются через 7 дней. Это можно изменить с помощью настройка пользовательской политики жизненного цикла.
ETags
Значения ETag для объектов, загруженных через multipart, отличаются от значений для объектов, загруженных одним PUT. ETag каждой части представляет собой MD5-хеш её содержимого. ETag завершённого составного объекта представляет собой хеш объединённых двоичных сумм MD5 всех частей, после которого следуют дефис и количество частей.
Например, если при двухчастной загрузке значения ETag частей равны bce6bf66aeb76c7040fdd5f4eccb78e6 и 8165449fc15bbf43d3b674595cbcc406, ETag завершённого объекта будет f77dc0eecdebcd774a2a22cb393ad2ff-2.