用于访问 Engee 的外部软件接口
*Engee*为第三方应用程序开发人员提供了一个编程接口(HTTP API),允许第三方应用程序向*Engee*授权,并代表*Engee*系统上的用户执行操作。
授权
第三方应用程序用户在*Engee*的授权是通过标准的OAuth机制完成的。
第三方应用程序开发人员向 Engee 开发人员传递以下信息:
-
redirect_uri
- 用户将被返回到第三方应用程序中的 URL。
Engee*开发人员将以下信息传递给第三方应用程序开发人员:
-
client_id
- 第三方应用程序的 ID; -
client_secret
- 第三方应用程序的密钥; -
范围"--该应用程序的授权 API。
它是如何工作的?
-
第三方应用程序使用以下查询参数引导用户访问
Authorise URL
(https://engee.com/account/authorize):-
client_id
- 第三方应用程序的 ID; -
redirect_uri
- 用户将被返回到的第三方应用程序中的 URL; -
response_type
- 响应类型(必须是code
); -
scope` - 该应用程序的授权 API;
-
state
- 当用户被重定向到redirect_uri
时,第三方应用程序将以相同形式返回的数据。
-
-
用户同意提供数据后,他将被重定向到位于
redirect_uri
的第三方应用程序,查询参数如下:-
范围"--此应用程序的授权 API;
-
code
- 授权代码; -
state
- 第一步中传给 Engee 的state
参数。
-
-
然后,需要获取令牌。方法是向 "令牌 URL"(https://engee.com/account/api/oauth2/token )发送一个 "POST "请求,请求内容如下("application/x-www-form-urlencoded"):
-
grant_type: "authorisation_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
作为回应,将发送一个访问令牌,用于授权对 HTTP API 的所有后续请求。同时还会发送刷新令牌,这是更新访问令牌所必需的。
要刷新令牌,必须向地址 `POST`https://engee.com/account/api/oauth2/token 发送带参数的请求:
-
grant_type: "refresh_token"
- 固定值; -
refresh_token
- 刷新令牌; -
client_id
- 第三方应用程序的 ID; -
client_secret
- 第三方应用程序的密钥; -
scope
- 该应用程序的授权 API; -
redirect_uri
- 用户将被返回到第三方应用程序中的 URL。访问令牌有效期:10 分钟,刷新令牌有效期:30 天。
-
-
此外,还可以使用接收到的令牌获取用户数据,为此,有必要向https://engee.com/account/api/oauth2/session 发送一个带有标头("Header")的 "GET "请求。
请求示例
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*互动的应用程序接口都必须由用户启动(按下个人账户中的启动*Engee*按钮),系统才能运行。如果无法按下启动按钮,也可以自动启动和停止*Engee*(见下文*Engee*管理方法说明)。
所有请求都应添加授权标题 "Authorisation: Bearer $token"(在授权阶段收到的令牌)。
API 方法
所有 Engee Authority API 的前缀 -https://engee.com.
恩吉管理
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/`。每个用户的前缀都不同,应从 URL 字段的 Engee 状态响应中获取。 |
执行任意代码
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
格式发送。
路径 "字段包含以下格式的 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;
-
如果文件已经存在,将在其名称后添加一个后缀;
-
只有在下载完成后,文件才会被写入光盘。如果下载中断,文件将不会出现在文件系统中。