# 设计 ## 动机 增强系统的扩展性。包含的目的有: - 完全支持各种钩子,能够根据其返回值修改 EMQ X 或者 Client 的行为。 - 例如 `auth/acl`:可以查询数据库或者执行某种算法校验操作权限。然后返回 `false` 表示 `认证/ACL` 失败。 - 例如 `message.publish`:可以解析 `消息/主题` 并将其存储至数据库中。 - 支持多种语言的扩展;并包含该语言的示例程序。 - python - webhook - Java - Lua - c,go,..... - 热操作 - 允许在插件运行过程中,添加和移除 `Driver`。 - 需要 CLI ,甚至 API 来管理 `Driver` 注:`message` 类钩子仅包括在企业版中。 ## 设计 架构如下: ``` EMQ X Third-party Runtimes +========================+ +====================+ | Extension | | | | +----------------+ | Hooks | Python scripts / | | | Drivers | ------------------> | Java Classes / | | +----------------+ | (pipe) | Others ... | | | | | +========================+ +====================+ ``` ### 配置文件示例 #### 驱动 配置 ```properties ## Driver type ## ## Exmaples: ## - python3 --- 仅配置 python3 ## - python3, java, webhook --- 配置多个 Driver exhook.dirvers = python3, java, webhook ## --- 具体 driver 的配置详情 ## Python exhook.dirvers.python3.path = data/extension/python exhook.dirvers.python3.call_timeout = 5s exhook.dirvers.python3.pool_size = 8 ## java exhook.drivers.java.path = data/extension/java ... ``` #### 钩子配置 钩子支持配置在配置文件中,例如: ```properties exhook.rule.python3.client.connected = {"module": "client", "callback": "on_client_connected"} exhook.rule.python3.message.publish = {"module": "client", "callback": "on_client_connected", "topics": ["#", "t/#"]} ``` ***已废弃!!(冗余)*** ### 驱动抽象 #### APIs | 方法名 | 说明 | 入参 | 返回 | | ------------------------ | -------- | ------ | ------ | | `init` | 初始化 | - | 见下表 | | `deinit` | 销毁 | - | - | | `xxx `*(由init函数定义)* | 钩子回调 | 见下表 | 见下表 | ##### init 函数规格 ```erlang %% init 函数 %% HookSpec : 为用户在脚本中的 初始化函数指定的;他会与配置文件中的内容作为默认值,进行合并 %% 该参数的目的,用于 EMQ X 判断需要执行哪些 Hook 和 如何执行 Hook %% State : 为用户自己管理的数据内容,EMQ X 不关心它,只来回透传 init() -> {HookSpec, State}. %% 例如: {[{client_connect, callback_m(), callback_f(),#{}, {}}]} %%-------------------------------------------------------------- %% Type Defines -tpye hook_spec() :: [{hookname(), callback_m(), callback_f(), hook_opts()}]. -tpye state :: any(). -type hookname() :: client_connect | client_connack | client_connected | client_disconnected | client_authenticate | client_check_acl | client_subscribe | client_unsubscribe | session_created | session_subscribed | session_unsubscribed | session_resumed | session_discarded %% TODO: Should squash to `terminated` ? | session_takeovered %% TODO: Should squash to `terminated` ? | session_terminated | message_publish | message_delivered | message_acked | message_dropped. -type callback_m() :: atom(). -- 回调的模块名称;python 为脚本文件名称;java 为类名;webhook 为 URI 地址 -type callback_f() :: atom(). -- 回调的方法名称;python,java 等为方法名;webhook 为资源地址 -tpye hook_opts() :: [{hook_key(), any()}]. -- 配置项;配置该项钩子的行为 -type hook_key() :: topics | ... ``` ##### deinit 函数规格 ``` erlang %% deinit 函数;不关心返回的任何内容 deinit() -> any(). ``` ##### 回调函数规格 | 钩子 | 入参 | 返回 | | -------------------- | ----------------------------------------------------- | --------- | | client_connect | `connifno`
`props` | - | | client_connack | `connifno`
`rc`
`props` | - | | client_connected | `clientinfo`
| - | | client_disconnected | `clientinfo`
`reason` | - | | client_authenticate | `clientinfo`
`result` | `result` | | client_check_acl | `clientinfo`
`pubsub`
`topic`
`result` | `result` | | client_subscribe | `clientinfo`
`props`
`topicfilters` | - | | client_unsubscribe | `clientinfo`
`props`
`topicfilters` | - | | session_created | `clientinfo` | - | | session_subscribed | `clientinfo`
`topic`
`subopts` | - | | session_unsubscribed | `clientinfo`
`topic` | - | | session_resumed | `clientinfo` | - | | session_discared | `clientinfo` | - | | session_takeovered | `clientinfo` | - | | session_terminated | `clientinfo`
`reason` | - | | message_publish | `messsage` | `message` | | message_delivered | `clientinfo`
`message` | - | | message_dropped | `message` | - | | message_acked | `clientinfo`
`message` | - | 上表中包含数据格式为: ```erlang -type conninfo :: [ {node, atom()} , {clientid, binary()} , {username, binary()} , {peerhost, binary()} , {sockport, integer()} , {proto_name, binary()} , {proto_ver, integer()} , {keepalive, integer()} ]. -type clientinfo :: [ {node, atom()} , {clientid, binary()} , {username, binary()} , {password, binary()} , {peerhost, binary()} , {sockport, integer()} , {protocol, binary()} , {mountpoint, binary()} , {is_superuser, boolean()} , {anonymous, boolean()} ]. -type message :: [ {node, atom()} , {id, binary()} , {qos, integer()} , {from, binary()} , {topic, binary()} , {payload, binary()} , {timestamp, integer()} ]. -type rc :: binary(). -type props :: [{key(), value()}] -type topics :: [topic()]. -type topic :: binary(). -type pubsub :: publish | subscribe. -type result :: true | false. ``` ### 统计 在驱动运行过程中,应有对每种钩子调用计数,例如: ``` exhook.python3.check_acl 10 ``` ### 管理 **CLI 示例:** **列出所有的驱动** ``` ./bin/emqx_ctl exhook dirvers list Drivers(xxx=yyy) Drivers(aaa=bbb) ``` **开关驱动** ``` ./bin/emqx_ctl exhook drivers enable python3 ok ./bin/emqx_ctl exhook drivers disable python3 ok ./bin/emqx_ctl exhook drivers stats python3.client_connect 123 webhook.check_acl 20 ```