Облачные функции

Запускайте Python (python-v1) или Node.js (node-v1) с JSON-входом в изолированной среде gVisor. Создание и вызов доступны владельцу активной подписки на тариф с поддержкой облачных функций. Один файл UTF-8 до 256 KiB, встроенные библиотеки; установка пакетов и постоянное хранилище не поддерживаются.

От создания до результата

  1. Создайте функцию в разделе «Облачные функции» в панели управления. Выберите среду, введите код и настройте сеть.
  2. Сохраните черновик. Укажите HTTPS webhook для результата: он обязателен для публикации в любом режиме сети.
  3. Добавьте секреты на отдельной странице «Секреты». Значения можно записать или заменить, прочитать их из API нельзя.
  4. Опубликуйте функцию из сохранённого черновика. При несохранённых изменениях сначала сохраните форму.
  5. Вызовите и откройте историю. Введите JSON, сохраните execution ID и проверьте состояние, результат, ошибку, лог и доставку webhook.

Код, среда, сетевые разрешения и webhook входят в опубликованную версию. После их изменения сохраните и опубликуйте функцию снова. Имя, порядок доменов, пауза и live-секреты не требуют публикации. Секреты применяются к следующим исполнениям без новой публикации; принятые запуски сохраняют снимки. Пауза сохраняет место в квоте, удаление освобождает его.

Обработчик и context.http

async def handler(event, context):
    response = await context.http.request(
        "POST", "https://example.com/events", json=event
    )
    return {"status": response["status"], "execution": context.execution_id}
export async function handler(event, context) {
  const response = await context.http.request({
    method: "POST", url: "https://example.com/events", json: event
  });
  return {status: response.status, execution: context.execution_id};
}

event — переданный JSON. Python допускает синхронный handler; оба runtime допускают асинхронный. context содержит execution_id, deadline (UTC), secrets и http.request. Ответ HTTP: status, headers, body; body — строка UTF-8. Явный json=None или json:null отправляет JSON null, пропущенное поле json — запрос без тела. Возвращайте JSON-совместимый результат: null, число, строку, массив или объект; NaN, Infinity, bytes, undefined и циклы недопустимы.

Сеть и ошибки HTTP

Используйте только context.http для HTTPS на порту 443. Прямые socket, urllib и fetch доступа в сеть не имеют. В режиме allowlist укажите до 20 точных доменов: пустой список запрещает запросы, поддомены автоматически не разрешаются. public разрешает публичные HTTPS-имена, но не анонимный вызов функции. В обоих режимах закрыты IP-литералы, private, loopback, link-local и reserved адреса, инфраструктурные endpoint и userinfo. DNS проверяется на каждый запрос; перенаправления не выполняются. Правила webhook проверяются отдельно.

Ответы HTTP 4xx/5xx возвращаются обработчику — проверяйте status. Ошибка сети отклоняет request исключением; при необходимости обработайте её в коде. Timeout одного HTTP-запроса — 10 секунд.

Запрос к API с секретом

Замените api.example.com адресом вашего API, добавьте его домен в allowlist и сохраните API_TOKEN в секретах. Укажите свой HTTPS webhook и опубликуйте функцию. Токен не должен попадать в исходник, входной JSON или лог.

async def handler(event, context):
    token = context.secrets["API_TOKEN"]
    response = await context.http.request(
        "POST", "https://api.example.com/events",
        headers={"Authorization": f"Bearer {token}"}, json=event
    )
    return {"status": response["status"]}
export async function handler(event, context) {
  const token = context.secrets.API_TOKEN;
  const response = await context.http.request({
    method: "POST", url: "https://api.example.com/events",
    headers: {Authorization: "Bearer " + token}, json: event
  });
  return {status: response.status};
}
{"event":"task.completed","task_id":123}

Авторизация и безопасный повтор

Используйте JWT владельца или API-ключ с отдельными scopes: functions:read для чтения, functions:write для изменений, functions:invoke для вызовов. POST-адрес не содержит токен и требует Authorization и Idempotency-Key.

POST
/workspaces/{workspace_id}/functions/{function_id}/invoke

Асинхронный запуск функции

curl --request POST 'https://api.cronbox.ru/v1/workspaces/WORKSPACE/functions/FUNCTION/invoke' \
  --header 'Authorization: Bearer YOUR_SCOPED_API_KEY' \
  --header 'Idempotency-Key: order-123' \
  --header 'Content-Type: application/json' \
  --data '{"order":123}'
{"execution_id":"EXECUTION_ID","status":"queued"}

202 означает принятие в очередь, а не успешное завершение. Ключ обязателен: 1–128 печатных ASCII-символов. При потере ответа повторите тот же JSON и тот же ключ: получите исходный execution_id без повторного исполнения и списания. Другой JSON с занятым ключом даёт 409. Для нового намеренного запуска нужен новый ключ. Не меняйте ключ после неопределённого сетевого исхода — можно выполнить код дважды.

GET
/workspaces/{workspace_id}/functions/{function_id}/runs

История состояний и доставки

GET
/workspaces/{workspace_id}/function-runs/{execution_id}

Вход, результат, ошибка и ограниченный лог

Подписанный webhook

Итоговый POST содержит event: function.completed, event_id, execution_id, function_id, version_id, status, result, error и duration_ms. Проверяйте HMAC-SHA256 по исходным байтам тела, до разбора JSON. X-Webhook-Signature имеет формат t=SECONDS,v1=HEX; X-Webhook-Timestamp содержит тот же timestamp. Формула подписи: HMAC-SHA256(secret, timestamp + «.» + raw_body). Пример отклоняет timestamp старше 300 секунд и сравнивает подписи без утечки через время сравнения.

import hashlib, hmac, time

def verify(raw_body, signature, signing_secret):
    try:
        parts = [part.split("=", 1) for part in signature.split(",")]
        fields = dict(parts)
        if len(parts) != 2 or set(fields) != {"t", "v1"}:
            return False
        timestamp = fields["t"]
        if abs(time.time() - int(timestamp)) > 300:
            return False
        expected = hmac.new(signing_secret.encode(), timestamp.encode() + b"." + raw_body,
                            hashlib.sha256).hexdigest()
        return hmac.compare_digest(fields["v1"], expected)
    except (ValueError, KeyError, TypeError):
        return False

Ключ подписи показывается однократно при создании/ротации; храните его защищённо у получателя, отдельно от runtime secrets. После ротации обновите проверку на webhook; уже принятые запуски используют свои снимки ключа. После проверки подписи атомарно сохраните event_id и выполните побочный эффект только один раз. Дубликату отвечайте 2xx.

2xx подтверждает доставку. Timeout, сеть, 408, 429 и 5xx повторяются через 30, 120, 600 секунд; другие 4xx и 3xx завершают доставку ошибкой. Тело и event_id сохраняются, timestamp подписи обновляется. Ручной повтор доставки не запускает handler заново. CronBox Heartbeat не проверяет HMAC-подпись: он подходит для тестового приёма, а проверку подписи реализуйте на своём webhook.

POST
/workspaces/{workspace_id}/function-runs/{execution_id}/webhook/retry

Повторная доставка того же результата

Квоты и ограничения

Количество функций, вызовов за календарный месяц UTC и вызовов в минуту определяется текущим тарифом владельца. Квоты общие для всех его рабочих пространств. Актуальные условия смотрите в тарифах, а свои лимиты и использование — в разделе «Облачные функции» панели управления. API capabilities возвращает текущие значения max_functions, monthly_invocations, invocations_per_minute и счётчики functions_count, accepted_count. Ноль функций запрещает создание, ноль вызовов — новые запуски; ноль не означает unlimited. Снижение квот не удаляет функции и историю, не отменяет принятые запуски и не блокирует ответ по уже принятому ключу. Если подписка закончилась или тариф больше не поддерживает функции, данные остаются читаемыми, а запись и вызовы следуют текущим разрешениям сервера.

GET
/workspaces/{workspace_id}/functions/capabilities

Квоты, использование и доступность исполнения

Технические ограничения платформы

Эти ограничения общие для тарифов. Их актуальные значения также возвращает API capabilities.

  • CPU: 0,5 ядра; память: 512 MiB; выполнение: 60 секунд.
  • 2 среды одновременно, 100 ожидающих запусков.
  • Очередь: 5 минут; запуск среды: 15 секунд.
  • Вход и результат: по 1 MiB; лог: до 256 KiB; глубина JSON: 128.
  • HTTP: до 20 запросов, 4 одновременно, до 10 MiB трафика.
  • Секреты: до 20 имён, 4 KiB на значение, 32 KiB суммарно.
  • Исходный код хранится для 10 последних версий; история и идемпотентность: 7 дней.

CPU, память, время выполнения, размеры, параллельность и sandbox фиксированы платформой и не изменяются редактором тарифов. Ошибка кода расходует квоту; подтверждённый сбой платформы до исполнения возвращает резерв один раз. Неоднозначный исход после потери runner не запускает код автоматически снова.