INTEGRITY Документация

Загрузка объектов

Загрузить объекты в R2 можно несколькими способами. Выбор подхода зависит от размера ваших объектов и требований к производительности.

Выбор способа загрузки

Одиночная загрузка (PUT) Составная загрузка
Подходит для Небольшие и средние файлы (менее ~100 МБ) Большие файлы или случаи, когда необходимы параллельная передача и возможность её возобновления
Максимальный размер объекта 5 ГиБ 5 ТиБ (до 10,000 частей)
Размер части N/A от 5 МиБ до 5 ГиБ на часть
Возобновляемая загрузка Нет: необходимо перезапустить всю загрузку Да: повторной отправке подлежат только неудавшиеся части
Параллельная загрузка Нет Да: части можно загружать одновременно
Когда использовать Быстрая и простая загрузка небольших объектов Видео, резервные копии, наборы данных и любые файлы, для которых важна надёжность

Загрузка через панель управления

Чтобы загрузить объекты в бакет из панели управления Cloudflare:

  1. На панели управления Cloudflare перейдите к разделу Объектное хранилище R2 страницу.

    Перейдите в Обзор ↗
  2. Выберите свой бакет.

  3. Выберите Загрузить.

  4. Перетащите файл в область загрузки или выбрать с компьютера.

После успешной загрузки вы получите подтверждающее сообщение.

Создание папки

Вы также можете создавать папки в панели управления, выбрав Создание папки. При этом создаётся объект нулевого размера с ключом, оканчивающимся на / который действует как заполнитель. Подробнее о том, как работают папки в 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
    raise

Presigned 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 client
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 client
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",
)

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-заголовков через необязательные флаги.

Сведения о составной загрузке

Ограничения на размер части

Жизненные циклы незавершённых загрузок

Незавершённые составные загрузки по умолчанию автоматически отменяются через 7 дней. Это можно изменить с помощью настройка пользовательской политики жизненного цикла.

ETags

Значения ETag для объектов, загруженных через multipart, отличаются от значений для объектов, загруженных одним PUT. ETag каждой части представляет собой MD5-хеш её содержимого. ETag завершённого составного объекта представляет собой хеш объединённых двоичных сумм MD5 всех частей, после которого следуют дефис и количество частей.

Например, если при двухчастной загрузке значения ETag частей равны bce6bf66aeb76c7040fdd5f4eccb78e6 и 8165449fc15bbf43d3b674595cbcc406, ETag завершённого объекта будет f77dc0eecdebcd774a2a22cb393ad2ff-2.

Справочник Workers API

Полный справочник по R2 Workers API, включая put(), createMultipartUpload() и другие методы.

Совместимость с S3 API

Поддерживаемые операции S3 API и особенности поведения R2.

Presigned URLs

Создавайте временные URL для загрузки и скачивания на стороне клиента.

Жизненный цикл объектов

Настройте автоматическую очистку незавершённых составных загрузок.