Завантаження файлів (/file)

3 хв читання

Завантаження файлів

Ендпоінт /file віддає бінарні файли, згенеровані функцією PostgreSQL, без base64 всередині JSON-RPC. Один повернений файл надсилається як є; кілька файлів пакуються в ZIP-архів на льоту.

POST /{prefix}/{database}/file

Автентифікація, перемикання ролі (SET LOCAL ROLE), обмеження частоти запитів і ліміт розміру запиту ідентичні /jsonrpc. Викликач повинен мати EXECUTE на функцію.

Запит

{
  "method": "api.export_documents",
  "params": { "count": 3 },
  "options": { "filename": "export.zip", "force_zip": false, "compression_level": 6 },
  "idempotencyKey": "optional"
}
ПолеОпис
methodОбов’язкове. schema.function, викликається як fn(params::jsonb).
paramsПередається функції як jsonb (за умовчанням {}).
options.filenameІм’я завантаженого файлу/ZIP. За умовчанням: власне ім’я файлу або export-YYYYMMDD-HHMMSS.zip.
options.force_zipЗавжди повертати ZIP, навіть для одного файлу.
options.compression_level0 = без стиснення, 1–9; за умовчанням 6.

Контракт бази даних

Функція приймає один аргумент jsonb і повертає набір рядків. path та content обов’язкові; mime_type і store_only необов’язкові.

CREATE FUNCTION api.export_documents(params jsonb)
RETURNS TABLE (path text, content bytea, mime_type text, store_only boolean)
LANGUAGE sql AS $$
  SELECT 'report.csv', convert_to('a,b' || E'\n' || '1,2', 'UTF8'), 'text/csv', false;
$$;
  • path має бути простим відносним шляхом, безпечним для розпакування в будь-якій операційній системі. Шлях відхиляється (запит завершується помилкою 500, а проблема логується), якщо він:
    • порожній, довший за 512 байтів, не є коректним UTF-8 або містить керівні символи;
    • абсолютний (/…, C:…) або містить зворотну скісну риску;
    • має порожній сегмент, або сегмент . чи ..;
    • має сегмент, що закінчується крапкою або пробілом (Windows їх відкидає, тож ".. " став би ..);
    • містить : (альтернативний потік даних NTFS) або будь-який із символів < > " | ? *;
    • використовує зарезервоване ім’я пристрою Windows як сегмент, з розширенням або без (CON, PRN, AUX, NUL, COM0–COM9, LPT0–LPT9);
    • збігається з іншим шляхом у тій самій відповіді: імена порівнюються без урахування регістру (a.txt і A.TXT конфліктують у macOS/Windows), а файл не може одночасно бути каталогом (a і a/b).
  • content зі значенням NULL означає порожній файл.
  • mime_type використовується лише якщо це коректний медіатип, інакше визначається за розширенням (запасне значення application/octet-stream).
  • store_only = true зберігає запис ZIP без стиснення (потрібно, наприклад, для файлу mimetype в EPUB).

Відповідь

Рядкиforce_zipРезультат
0будь-яке404 (помилка JSON); транзакція відкочується, тож нічого, що зробила функція, не зберігається, а idempotencyKey не витрачається
1falseсам файл з його медіатипом
1trueZIP з одним записом
≥ 2будь-якеZIP (application/zip)
curl -X POST http://localhost:8080/db/my_database/file \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"method":"api.export_documents","params":{"count":3}}' -OJ

Ім’я завантажуваного файлу (options.filename або власне ім’я файлу) санітизується: роздільники шляху, лапки та зарезервовані символи замінюються на _, а керівні символи, символи перевизначення напрямку тексту, символи нульової ширини та роздільники рядків видаляються, тож ім’я не може маскуватися під файл іншого типу.

Помилки завжди повертаються у форматі JSON, ніколи як бінарні дані: 400 некоректний запит, 401 не автентифіковано, 403 немає дозволу, 404 невідома функція або немає рядків, 413 відповідь перевищує FILE_MAX_BYTES / FILE_MAX_ENTRIES, 500 функція завершилася помилкою або повернула некоректну структуру.

Безпечно за конструкцією

База даних керує лише іменами файлів, вмістом і медіатипами — ніколи HTTP-заголовками. Відповіді завжди мають Content-Disposition: attachment разом з X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox і Cache-Control: no-store, тож функція, що повертає text/html або SVG, не може виконати скрипт в origin API. Імена файлів санітизуються. Весь результат буферизується в пам’яті: реальна межа на запит — це FILE_MAX_BYTES плюс найбільший окремий рядок, оскільки драйвер бази даних читає рядок повністю, перш ніж можна перевірити ліміт — зважайте на кількість одночасних завантажень. Сам ZIP передається потоком.

Виявлення

Функція, що повертає набір рядків, оголошена з RETURNS TABLE (…) або параметрами OUT, які містять стовпці path і content, відображається в capabilities з "kind": "file" та ендпоінтом /file. Специфікація OpenAPI описує реальну операцію POST /{prefix}/{database}/file (лише для ролей, які можуть виконувати файлову функцію); файлові функції не відкриваються як інструменти MCP.