Packages

CMDC Gateway — HTTP + SSE + WebSocket 协议网关,接入 CMDC Agent 能力

Current section

Files

Jump to
cmdc_gateway lib cmdc_gateway application.ex
Raw

lib/cmdc_gateway/application.ex

defmodule CMDCGateway.Application do
@moduledoc """
Gateway OTP Application。
独立启动时(`mix run --no-halt`)自动读取环境变量配置 Cowboy HTTP + WebSocket。
## 环境变量
| 变量 | 说明 | 默认 |
|------|------|------|
| `GATEWAY_PORT` | HTTP 端口 | `4000` |
| `GATEWAY_API_KEYS` | 逗号分隔的合法 API Key | 无(为空则跳过认证) |
## 嵌入模式
设置 `config :cmdc_gateway, :standalone, false` 后 Application
仅启动 SessionStore / RateLimiter / Meter,由宿主应用自行启动 Cowboy。
"""
use Application
require Logger
@default_port 4000
@impl true
def start(_type, _args) do
CMDCGateway.CallbackTool.init_registry()
configure_from_env()
children =
[
CMDCGateway.SessionStore,
CMDCGateway.RateLimiter,
CMDCGateway.Meter,
CMDCGateway.TaskStore,
webhook_dispatcher_spec(),
{DynamicSupervisor, name: CMDCGateway.AgentSupervisor, strategy: :one_for_one}
] ++ cowboy_children()
opts = [strategy: :one_for_one, name: CMDCGateway.Supervisor]
Supervisor.start_link(children, opts)
end
# ==========================================================================
# Private
# ==========================================================================
defp configure_from_env do
if api_keys_str = System.get_env("GATEWAY_API_KEYS") do
keys =
api_keys_str
|> String.split(",", trim: true)
|> Enum.map(&String.trim/1)
Application.put_env(:cmdc_gateway, CMDCGateway.Plugs.Auth, api_keys: keys)
end
end
defp cowboy_children do
standalone? = Application.get_env(:cmdc_gateway, :standalone, true)
if standalone? do
port = get_port()
Logger.info("[CMDCGateway] Starting on port #{port} (HTTP + WebSocket)")
plug_opts = CMDCGateway.Router.init([])
dispatch = [
{:_,
[
{"/v1/sessions/:session_id/ws", CMDCGateway.WSHandler, []},
{:_, Plug.Cowboy.Handler, {CMDCGateway.Router, plug_opts}}
]}
]
[
{Plug.Cowboy,
scheme: :http,
plug: CMDCGateway.Router,
options: [
port: port,
dispatch: dispatch,
protocol_options: [idle_timeout: 300_000]
]}
]
else
[]
end
end
defp get_port do
case System.get_env("GATEWAY_PORT") do
nil -> @default_port
str -> String.to_integer(str)
end
end
# ==========================================================================
# Webhook Dispatcher — dead-letter 重试调度
# ==========================================================================
#
# 默认配置:
# - enabled?: true(环境变量 WEBHOOK_DLQ_ENABLED=false 关闭)
# - backend: DETS(环境变量 WEBHOOK_DLQ_FILE 指定文件,默认 webhook_dlq.dets)
# - tick_interval_ms: 30_000(30s)
# - max_retries: 10
#
# 应用层覆盖:在 config.exs 设置
#
# config :cmdc_gateway, CMDCGateway.Webhook.Dispatcher,
# enabled?: true,
# backend: CMDCGateway.Webhook.DeadLetter.DETS,
# backend_opts: [file: ~c"/var/cmdc/webhook_dlq.dets"],
# tick_interval_ms: 60_000,
# max_retries: 10
defp webhook_dispatcher_spec do
app_opts = Application.get_env(:cmdc_gateway, CMDCGateway.Webhook.Dispatcher, [])
enabled? =
Keyword.get_lazy(app_opts, :enabled?, fn ->
case System.get_env("WEBHOOK_DLQ_ENABLED") do
nil -> true
v -> String.downcase(v) not in ["false", "0", "off", "no"]
end
end)
backend = Keyword.get(app_opts, :backend, CMDCGateway.Webhook.DeadLetter.DETS)
backend_opts =
Keyword.get_lazy(app_opts, :backend_opts, fn ->
file =
System.get_env("WEBHOOK_DLQ_FILE") ||
Path.join(System.tmp_dir!(), "cmdc_gateway_webhook_dlq.dets")
[file: String.to_charlist(file)]
end)
final_opts = [
backend: backend,
backend_opts: backend_opts,
enabled?: enabled?,
tick_interval_ms: Keyword.get(app_opts, :tick_interval_ms, 30_000),
max_retries: Keyword.get(app_opts, :max_retries, 10)
]
{CMDCGateway.Webhook.Dispatcher, final_opts}
end
end