Внешний программный интерфейс доступа к Engee
Страница в процессе разработки. |
Engee предоставляет разработчикам сторонних приложений программный интерфейс (HTTP API), позволяющий стороннему приложению авторизоваться в Engee и совершать действия от имени пользователя в системе Engee.
Авторизация
Авторизация пользователя стороннего приложения в Engee осуществляется через стандартный механизм OAuth.
Разработчик стороннего приложения передает следующую информацию разработчику Engee:
-
redirect_uri
— URL в стороннем приложении, куда будет возвращен пользователь.
Разработчик Engee передает разработчику стороннего приложения следующую информацию:
-
client_id
— ID стороннего приложения; -
client_secret
— секретный ключ стороннего приложения; -
scope
— авторизованные API для данного приложения.
Как это работает?
-
Стороннее приложение направляет пользователя на
Authorize URL
(https://engee.com/account/authorize) со следующими query параметрами:-
client_id
— ID стороннего приложения; -
redirect_uri
— URL в стороннем приложении, куда будет возвращен пользователь; -
response_type
— тип ответа (должен бытьcode
); -
scope
— авторизованные API для данного приложения; -
state
— данные стороннего приложения, которые вернутся в том же виде, когда пользователь будет перенаправлен наredirect_uri
.
-
-
После того как пользователь даст согласие на предоставление данных, его перенаправит обратно в стороннее приложение на
redirect_uri
, со следующими query параметрами:-
scope
— авторизованные API для данного приложения; -
code
— код для авторизации; -
state
— параметрstate
, переданный в Engee на первом шаге.Пример страницы перенаправления с query параметрами: http://redirect_uri/?code=code&scope=user%3Aid%20user%3Ausername%20profile%3Aname%20profile%3Aprimary_email%20engee.
-
-
После этого необходимо получить токен. Для этого нужно послать
POST
запрос наToken URL
(https://engee.com/account/api/oauth2/token), со следующим телом (application/x-www-form-urlencoded
):-
grant_type: "authorization_code"
— фиксированное значение; -
code
— полученный на втором шаге код; -
client_id
— ID стороннего приложения; -
client_secret
— секретный ключ стороннего приложения; -
scope
— авторизованные API для данного приложения; -
redirect_uri
— URL в стороннем приложении, куда будет возвращен пользователь;Пример запроса:
curl -d grant_type=authorization_code -d code=$code -d client_id=$client_id -d client_secret=$client_secret -d "scope=user:id user:username profile:name profile:primary_email engee" -d redirect_uri=$redirect_uri https://engee.com/account/api/oauth2/token
В ответ придет access token, которым авторизуются все последующие запросы к HTTP API. Также придет refresh token, который необходим для обновления access token.
Для обновления токена необходимо послать запрос на адрес
POST
https://engee.com/account/api/oauth2/token c параметрами:-
grant_type: "refresh_token"
— фиксированное значение; -
refresh_token
— токен обновления; -
client_id
— ID стороннего приложения; -
client_secret
— секретный ключ стороннего приложения; -
scope
— авторизованные API для данного приложения; -
redirect_uri
— URL в стороннем приложении, куда будет возвращен пользователь.Время жизни access token: 10 минут, refresh token: 30 дней.
-
-
Далее возможно получить данные пользователя с помощью полученного токена, для этого необходимо отправить запрос
GET
с заголовком (Header
) на https://engee.com/account/api/oauth2/session.Пример запроса:
curl -H "Authorization: Bearer $token" https://engee.com/account/api/oauth2/session
Пример ответа:
{"sub":"1d592c69-9664-4eb5-9166-447c421a02df","nickname":"username","email":"username@email.tld"}
Взаимодействие с Engee
Для работы всех API по взаимодействию с Engee нужно, чтобы система была запущена у пользователя (нажата кнопка Старт Engee в личном кабинете). Если нет возможности нажимать кнопку запуска, то можно также автоматизировать запуск и остановку Engee (см. описание методов управления Engee далее).
Во все запросы должен быть добавлен заголовок авторизации "Authorization: Bearer $token" (токен получен на этапе авторизации).
Методы API
Префикс для всех API Управления Engee — https://engee.com.
Управление Engee
GET /account/api/engee/info
— получение информации о состоянии Engee. Тело ответа:
{
"username": "string",
"serverName": "string",
"serverId": "string",
"url": "string",
"serverStatus": "starting"|"running"|"stopping"|"stopped",
"startingAt": "2025-02-10T10:24:31.328Z",
"startedAt": "2025-02-10T10:24:31.328Z",
"stoppingAt": "2025-02-10T10:24:31.328Z",
"stoppedAt": "2025-02-10T10:24:31.328Z",
"lastActivityAt": "2025-02-10T10:24:31.328Z",
"inactivityTimeout": 0,
"stopSessionAt": "2025-02-10T10:24:31.328Z",
"clusterNamespace": {
"minimalMode": true
}
POST /account/api/engee/start
— запуск Engee (аналог нажатия кнопки Старт Engee в личном кабинете). Тело ответа (URL запускаемого сервера):
{
'server': string
}
DELETE /account/api/engee/stop
— остановка Engee. Возвращает код ответа 204.
Префикс для всех остальных API имеет вид: https://engee.com/prod/user/$license-$nickname/ . Префикс у каждого пользователя свой, его нужно брать из ответа состояния Engee из поля URL.
|
Исполнение произвольного кода
POST /external/command/eval
-
Простейший запрос. Запрос:
{ "command": "3 + 5" }
Возвращаемые данные:
-
Статус-код: 200.
{ "result": "8" }
-
-
Ошибка при исполнении исходного кода. Запрос:
{ "command": "unexisting_variable" }
Возвращаемые данные:
-
Статус-код: 400.
{ "result_code": "command_error", "error": { "full_description": "UndefVarError: `unexisting_variable` not defined\n\nStacktrace:\n [1] top-level scope\n @ none:1\n [2] eval(m::Module, e::Any)\n @ Core ./boot.jl:370\n [3] top-level scope\n @ none:4\n [4] eval\n @ ./boot.jl:370 [inlined]\n [5] eval_mask_code(code::String)\n @ Main.Masks /app/IJulia/preload_cells/masks.jl:206\n [6] eval_mask_codes(mask_codes::Vector{Main.Masks.MaskCode})\n @ Main.Masks /app/IJulia/preload_cells/masks.jl:197\n [7] |>\n @ ./operators.jl:907 [inlined]\n [8] evaluate_mask_codes(mask_codes::Vector{Dict{String, String}})\n @ Main.Masks /app/IJulia/preload_cells/masks.jl:272\n [9] |>\n @ ./operators.jl:907 [inlined]\n [10] (::Workspaces.Servers.var\"#eval_mask_codes#157\"{typeof(Main.Masks.evaluate_mask_codes), Workspaces.Servers.var\"#convert_mask_result#156\"})(blocks::Vector{Dict{String, String}})\n @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers.jl:539\n [11] request_pipeline(req::HTTP.Messages.Request, log_func::var\"#13#14\"{String}, req_params_func::Function, log_response_func::Workspaces.Servers.var\"#mask_log_and_response#159\"{Workspaces.Servers.var\"#result_to_response#158\"}, pipeline_stages::Workspaces.Servers.PipelineStages)\n @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers_common.jl:282\n [12] required_body_request_pipeline\n @ /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers_common.jl:289 [inlined]\n [13] eval_mask_code_handler\n @ /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers.jl:556 [inlined]\n [14] (::Workspaces.Servers.var\"#eval_mask_code#186\"{var\"#13#14\"{String}, typeof(Main.Masks.evaluate_mask_codes)})(req::HTTP.Messages.Request)\n @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/Servers.jl:68\n [15] (::HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing})(req::HTTP.Messages.Request)\n @ HTTP.Handlers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Handlers.jl:439\n [16] (::HTTP.Handlers.var\"#1#2\"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}})(stream::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.Connections.Connection{Sockets.TCPSocket}})\n @ HTTP.Handlers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Handlers.jl:58\n [17] #invokelatest#2\n @ ./essentials.jl:819 [inlined]\n [18] invokelatest\n @ ./essentials.jl:816 [inlined]\n [19] handle_connection(f::Function, c::HTTP.Connections.Connection{Sockets.TCPSocket}, listener::HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, readtimeout::Int64, access_log::Nothing)\n @ HTTP.Servers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Servers.jl:469\n [20] macro expansion\n @ /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Servers.jl:401 [inlined]\n [21] (::HTTP.Servers.var\"#16#17\"{HTTP.Handlers.var\"#1#2\"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}}, HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, Set{HTTP.Connections.Connection}, Int64, Nothing, ReentrantLock, Base.Semaphore, HTTP.Connections.Connection{Sockets.TCPSocket}})()\n @ HTTP.Servers ./task.jl:514", "short_description": "UndefVarError(:unexisting_variable)", "type": "UndefVarError" } }
здесь:
-
error.full_description
— полное описание ошибки вместе со стектрейсом. Вывод аналогичен выводу ошибки в терминал; -
error.short_description
— исключение без стектрейса; -
error.type
— тип ошибки.
-
-
-
Возврат JSON-а. Запрос:
{ "command": "using JSON\nJSON.json(\"a\"=>1)" }
Возвращаемые данные:
-
Статус-код: 200.
{ "result": "{\"a\":1}" }
-
Загрузка файлов
POST /external/file/upload
Запрос отправляется в формате multipart/form-data
.
Поле paths
содержит JSON следующего формата:
{
"sources": [
"/user/some_dir"
]
}
здесь:
-
Каждый элемент внутри
sources
означает директорию, куда будет загружен файл; -
Поле
upload_files
содержит файл, который необходимо загрузить; -
Количество полей
upload_files
должно соответствовать количеству переданных директорий.
Пример запроса:
curl -X 'POST' \
'https://engee.com/prod/user/$license-$user/external/file/upload' \
-H 'Authorization: Bearer $access_token' \
-H 'Content-Type: multipart/form-data' \
-F 'paths={
"sources": [
"/user/some_directory"
]
}' \
-F 'upload_files=@2.png;type=image/png'
-
Если директория, куда загружается файл, не существует, то она будет создана;
-
Все скачиваемые файлы должны находиться внутри директории
/user
, иначе вернется ошибка 403; -
Если файл уже существует, то его к его имени добавится суффикс;
-
Файл будет записан на диск только после полной загрузки. Если загрузка будет прервана, то файл не появится в файловой системе.
Скачивание файлов
POST /external/file/download
Запрос:
{
"sources": [
"/user/some_file"
]
}
-
Если файл один, то он возвращается в формате
multipart/form-data
; -
Если файлов несколько, то они сжимаются в ZIP и уже возвращается
application/x-zip-compressed
; -
Если хотя бы один файл не существует, то в ответ вернется 404;
-
Все скачиваемые файлы должны находиться внутри директории
/user
, иначе вернется ошибка 403.