Облачное хранилище
Доступ к S3-совместимому объектному хранилищу. Загрузка, скачивание, перечисление и управление объектами, presigned URL для скачивания, загрузки и частей multipart, а также чтение объектов с произвольным доступом.
Настройку хранилища см. в Cloud Storage.
Загрузка
local cloudstorage = require("cloudstorage")
Получение хранилища
Получить ресурс облачного хранилища по ID реестра:
local storage, err = cloudstorage.get("app.infra:files")
if err then
return nil, err
end
storage:upload_object("data/file.txt", "content")
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
id |
string | ID ресурса хранилища |
Возвращает: Storage, error
Загрузка объектов
Загрузка содержимого из строки или файла:
local storage = cloudstorage.get("app.infra:files")
-- Загрузка строкового содержимого
local ok, err = storage:upload_object("reports/daily.json", json.encode({
date = "2024-01-15",
total = 1234
}))
-- Загрузка из файла
local fs = require("fs")
local vol = fs.get("app:data")
local file = vol:open("/large-file.bin", "r")
storage:upload_object("backups/large-file.bin", file)
file:close()
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ/путь объекта |
content |
string или Reader | Содержимое как строка или файловый reader |
options |
table | Опциональные метаданные и опции условной записи |
Возвращает: boolean, error
Опции загрузки
Прикрепите метаданные или защитите запись с помощью таблицы опций:
storage:upload_object("reports/daily.json", body, {
content_type = "application/json",
cache_control = "max-age=3600",
metadata = { owner = "team-a", run_id = "1234" }, -- stored as x-amz-meta-*
only_if_absent = true -- fail if the key already exists
})
| Опция | Тип | Описание |
|---|---|---|
content_type |
string | MIME-тип |
cache_control |
string | Заголовок Cache-Control |
content_disposition |
string | Заголовок Content-Disposition |
content_encoding |
string | Заголовок Content-Encoding |
metadata |
table | Пользовательские метаданные (строковые ключи/значения), хранятся как x-amz-meta-* |
headers |
table | Дополнительные заголовки запроса (строковые ключи/значения) |
if_match |
string | Записать, только если текущий ETag объекта совпадает |
if_none_match |
string | Записать, только если ни один объект не совпадает с ETag ("*" означает любой) |
only_if_absent |
boolean | Записать, только если ключ не существует (алиас для if_none_match = "*") |
Условная запись, не прошедшая своё предусловие, возвращает ошибку precondition_failed.
Скачивание объектов
Скачать объект в файловый writer:
local storage = cloudstorage.get("app.infra:files")
local fs = require("fs")
local vol = fs.get("app:temp")
local file = vol:open("/downloaded.json", "w")
local ok, err = storage:download_object("reports/daily.json", file)
file:close()
-- Скачивание части (первый 1KB)
local partial = vol:open("/partial.bin", "w")
storage:download_object("backups/large-file.bin", partial, {
range = "bytes=0-1023"
})
partial:close()
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта для скачивания |
writer |
Writer | Файловый writer назначения |
options.range |
string | Диапазон байт (например, "bytes=0-1023") |
options.if_match |
string | Скачать, только если ETag объекта совпадает |
options.if_none_match |
string | Скачать, только если ETag не совпадает |
Возвращает: boolean, error
Непройденное предусловие (if_match/if_none_match) возвращает ошибку precondition_failed.
Перечисление объектов
Список объектов с опциональной фильтрацией по префиксу:
local storage = cloudstorage.get("app.infra:files")
local result, err = storage:list_objects({
prefix = "reports/2024/",
max_keys = 100
})
for _, obj in ipairs(result.objects) do
print(obj.key, obj.size, obj.etag)
end
-- Пагинация для больших результатов
local token = nil
repeat
local result = storage:list_objects({
prefix = "logs/",
max_keys = 1000,
continuation_token = token
})
for _, obj in ipairs(result.objects) do
process(obj)
end
token = result.next_continuation_token
until not result.is_truncated
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
options.prefix |
string | Фильтр по префиксу ключа |
options.max_keys |
integer | Максимум объектов для возврата |
options.continuation_token |
string | Токен пагинации |
options.include_owner |
boolean | Включить owner каждого объекта (id, display_name) |
options.include_versions |
boolean | Перечислить версии объектов; каждый элемент включает version_id |
Возвращает: table, error
Результат содержит objects, is_truncated, next_continuation_token. Каждый объект имеет key, size, etag, storage_class, а также опциональные last_modified, version_id и owner.
content_type всегда пуст — операции списка S3 его не возвращают. Используйте head_object, чтобы прочитать content type и метаданные объекта.
Метаданные объекта
Получить метаданные одного объекта без скачивания его тела:
local storage = cloudstorage.get("app.infra:files")
local meta, err = storage:head_object("reports/daily.json")
if err then
return nil, err
end
print(meta.size, meta.etag, meta.content_type)
for k, v in pairs(meta.metadata) do
print("meta", k, v)
end
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта |
Возвращает: table, error
Поля результата:
| Поле | Тип | Описание |
|---|---|---|
size |
integer | Размер объекта в байтах |
etag |
string | Entity tag |
content_type |
string | MIME-тип |
cache_control |
string | Заголовок Cache-Control |
content_disposition |
string | Заголовок Content-Disposition |
content_encoding |
string | Заголовок Content-Encoding |
storage_class |
string | Класс хранения |
version_id |
string | ID версии (присутствует при включённом версионировании) |
last_modified |
integer | Время последнего изменения (Unix-секунды) |
metadata |
table | Пользовательские метаданные (x-amz-meta-*) |
headers |
table | Сырые заголовки ответа (ключи в нижнем регистре) |
Отсутствующий объект возвращает ошибку not_found.
Удаление объектов
Удалить несколько объектов:
local storage = cloudstorage.get("app.infra:files")
storage:delete_objects({
"temp/file1.txt",
"temp/file2.txt",
"temp/file3.txt"
})
storage:release()
| Параметр | Тип | Описание |
|---|---|---|
keys |
string[] | Массив ключей объектов для удаления |
Возвращает: boolean, error
Попытка выполняется для каждого ключа. Удаление несуществующего ключа ошибкой не является. Если провайдер сообщает о сбоях по отдельным ключам, вызов возвращает одну ошибку, перечисляющую каждый сбойный ключ и код ошибки провайдера.
URL для скачивания
Создать временный URL для скачивания объекта без учётных данных. Полезно для передачи файлов внешним пользователям или отдачи контента через приложение.
local storage, err = cloudstorage.get("app.infra:files")
if err then
return nil, err
end
local url, err = storage:presigned_get_url("reports/quarterly.pdf", {
expiration = 3600
})
storage:release()
if err then
return nil, err
end
-- Вернуть URL клиенту для прямого скачивания
return {download_url = url}
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта |
options.expiration |
integer | Секунд до истечения URL (по умолчанию: 3600) |
Возвращает: string, error
URL для загрузки
Создать временный URL для загрузки объекта без учётных данных. Позволяет клиентам загружать файлы напрямую в хранилище без проксирования через сервер.
local storage, err = cloudstorage.get("app.infra:files")
if err then
return nil, err
end
local url, err = storage:presigned_put_url("uploads/user-123/avatar.jpg", {
expiration = 600,
content_type = "image/jpeg",
content_length = 1024 * 1024
})
storage:release()
if err then
return nil, err
end
-- Вернуть URL клиенту для прямой загрузки
return {upload_url = url}
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта |
options.expiration |
integer | Секунд до истечения URL (по умолчанию: 3600) |
options.content_type |
string | Обязательный content type для загрузки |
options.content_length |
integer | Ожидаемый размер загрузки в байтах |
Возвращает: string, error
Multipart-загрузки
Одиночный presigned PUT ограничивает объект 5 ГиБ. Presigned multipart-загрузка разбивает более крупный объект на части, которые клиент загружает напрямую, а затем собирает их на стороне сервера. Multipart — это возможность провайдера: S3 её реализует, а провайдеры без неё возвращают errors.UNAVAILABLE.
local storage = cloudstorage.get("app.infra:files")
local mp, err = storage:create_multipart_upload("backups/huge.zip", {
content_type = "application/zip",
metadata = { source = "uploader" },
})
if err then return nil, err end
local urls, err = storage:presigned_part_urls("backups/huge.zip", mp.upload_id, {
count = 3,
expiration = 900,
})
if err then
storage:abort_multipart_upload("backups/huge.zip", mp.upload_id)
return nil, err
end
-- Клиент выполняет PUT по каждому url и возвращает ETag из заголовков ответа.
local done, err = storage:complete_multipart_upload("backups/huge.zip", mp.upload_id, {
{ part_number = 1, etag = etag1 },
{ part_number = 2, etag = etag2 },
{ part_number = 3, etag = etag3 },
})
storage:release()
create_multipart_upload
Начинает multipart-загрузку для ключа.
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ итогового объекта |
options |
table | content_type, cache_control, content_disposition, content_encoding, metadata, headers — та же семантика, что и у upload_object |
Возвращает: table, error — таблица несёт upload_id, идентифицирующий загрузку для всех последующих вызовов частей, завершения и отмены.
Условные записи (if_match, if_none_match, only_if_absent) не входят в протокол multipart и здесь не принимаются.
presigned_part_urls
Генерирует presigned PUT URL для частей выполняющейся загрузки. На каждый URL выполняется обычный HTTP PUT; загружающая сторона должна сохранить заголовок ответа ETag каждой части для complete_multipart_upload.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
key |
string | обязательно | Ключ объекта |
upload_id |
string | обязательно | Из create_multipart_upload |
options.parts |
int[] | - | Явные номера частей (1–10000, без дубликатов) |
options.count |
int | - | Подписать части 1..count |
options.headers |
table | - | Заголовки, обязательные для каждого запроса части; они подписываются и должны быть отправлены загружающей стороной |
options.expiration |
int | 3600 | Секунд до истечения URL |
Ровно один из parts или count обязателен, и один вызов подписывает не более 1000 URL — для очень крупных объектов подписывайте постранично.
Возвращает: table, error — массив { part_number, url }.
Каждая часть, кроме последней, должна быть не меньше 5 МиБ; провайдер проверяет это при завершении.
complete_multipart_upload
Собирает итоговый объект из загруженных частей. Части можно сообщать в любом порядке — перед завершением они сортируются по номеру.
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта |
upload_id |
string | Из create_multipart_upload |
parts |
table | Массив { part_number = int, etag = string } |
Возвращает: table, error — etag, а также version_id и location, если провайдер их сообщает. Неизвестный upload ID возвращает errors.NOT_FOUND.
abort_multipart_upload
Отбрасывает выполняющуюся загрузку и освобождает её сохранённые части.
| Параметр | Тип | Описание |
|---|---|---|
key |
string | Ключ объекта |
upload_id |
string | Из create_multipart_upload |
Возвращает: boolean, error
Загрузка, которая так и не была завершена, хранит свои части — и тарифицируется — пока не будет отменена. Отменяйте её на каждом пути сбоя и настройте правило жизненного цикла бакета как подстраховку — см. Cloud Storage.
Чтение по диапазонам
open_reader открывает произвольный доступ к объекту через ranged GET — без локального промежуточного хранения и без полной загрузки. Основной потребитель — archive.open, который читает многогигабайтные архивы прямо из объектного хранилища с ограниченным потреблением памяти.
local archive = require("archive")
local storage = cloudstorage.get("app.infra:files")
local reader, err = storage:open_reader("uploads/huge.zip", {
block_size = 8 * 1024 * 1024,
cache_blocks = 4,
})
if err then return nil, err end
local r = assert(archive.open(reader))
for e in r:entries() do
print(e.name, e.size)
end
r:close()
reader:close()
storage:release()
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
key |
string | обязательно | Ключ объекта |
options.block_size |
int | 8388608 | Единица ranged GET в байтах (от 64 КиБ до 128 МиБ) |
options.cache_blocks |
int | 4 | Число резидентных LRU-блоков (от 1 до 64) |
block_size * cache_blocks не может превышать 256 МиБ. Отсутствующий объект возвращает errors.NOT_FOUND.
Возвращает: Reader, error
ETag объекта закрепляется при открытии reader'а и отправляется как If-Match при каждом чтении диапазона, поэтому объект, перезаписанный посреди чтения, приводит к отказу чтения с ошибкой предусловия от провайдера вместо выдачи смеси двух поколений объекта; archive отдаёт её как errors.INTERNAL. Провайдер, не способный предоставить ETag, возвращает errors.UNAVAILABLE; reader никогда не отдаёт незакреплённый объект.
Промахи кэша выполняют блокирующий сетевой ввод-вывод в вызывающей задаче и сериализуют параллельных читателей, поэтому последовательный доступ по записям — как в архиве — и есть предполагаемая форма использования.
Методы Reader
| Метод | Возвращает | Описание |
|---|---|---|
size() |
integer |
Размер объекта в байтах, из stat при открытии |
key() |
string |
Ключ объекта, из которого читает reader |
close() |
boolean, error |
Освободить кэш блоков; идемпотентно |
Reader закрывается автоматически в области задачи, если не закрыт явно.
Методы Storage
| Метод | Возвращает | Описание |
|---|---|---|
upload_object(key, content, opts?) |
boolean, error |
Загрузить строку или файл |
download_object(key, writer, opts?) |
boolean, error |
Скачать в файловый writer |
head_object(key) |
table, error |
Получить метаданные объекта |
list_objects(opts?) |
table, error |
Список объектов с фильтром по префиксу |
delete_objects(keys) |
boolean, error |
Удалить несколько объектов |
presigned_get_url(key, opts?) |
string, error |
Сгенерировать временный URL для скачивания |
presigned_put_url(key, opts?) |
string, error |
Сгенерировать временный URL для загрузки |
create_multipart_upload(key, opts?) |
table, error |
Начать presigned multipart-загрузку |
presigned_part_urls(key, upload_id, opts) |
table, error |
Подписать PUT URL для частей загрузки |
complete_multipart_upload(key, upload_id, parts) |
table, error |
Собрать объект из загруженных частей |
abort_multipart_upload(key, upload_id) |
boolean, error |
Отбросить выполняющуюся multipart-загрузку |
open_reader(key, opts?) |
Reader, error |
Открыть reader произвольного доступа по диапазонам |
release() |
boolean |
Освободить ресурс хранилища |
Разрешения
Операции облачного хранилища подчиняются вычислению политики безопасности.
| Действие | Ресурс | Описание |
|---|---|---|
cloudstorage.get |
ID хранилища | Получить ресурс хранилища |
Ошибки
| Условие | Kind | Повторяемо |
|---|---|---|
| Пустой ID ресурса | errors.INVALID |
нет |
| Ресурс не найден | errors.NOT_FOUND |
нет |
| Не ресурс облачного хранилища | errors.INVALID |
нет |
| Хранилище освобождено | errors.INVALID |
нет |
| Пустой ключ | errors.INVALID |
нет |
| Содержимое nil | errors.INVALID |
нет |
| Writer некорректен | errors.INVALID |
нет |
| Объект не найден | errors.NOT_FOUND |
нет |
| Неизвестный upload ID | errors.NOT_FOUND |
нет |
| Условное предусловие не выполнено | errors.CONFLICT |
нет |
Объект перезаписан во время чтения по диапазону (отдаётся через archive) |
errors.INTERNAL |
нет |
| Провайдер не поддерживает multipart-загрузки | errors.UNAVAILABLE |
нет |
Провайдер не предоставляет ETag для open_reader |
errors.UNAVAILABLE |
нет |
| Доступ запрещён | выбрасывается как ошибка Lua, не возвращается | - |
| Операция провайдера не удалась | errors.UNKNOWN |
не задано |
См. Обработка ошибок для работы с ошибками.