# 主题重写

很多物联网设备不支持重新配置或升级，修改设备业务主题会非常困难。

主题重写功能可以帮助使这种业务升级变得更容易：通过给 EMQX 设置一套规则，它可以在订阅、发布时改变将原有主题重写为新的目标主题。

[保留消息](./mqtt-retained-message.md)和[延迟发布](./mqtt-delayed-publish.md) 也可以与主题重写结合使用。例如，当用户想使用延迟发布时，他们可以使用主题重写来将消息重定向到所需的主题。

:::tip
由于发布/订阅授权检查会在主题重写之前执行，所以只要确保重写之前的主题能够通过 ACL 检查即可。
:::

:::tip
主题重写在作用于客户端的订阅/取消订阅共享订阅主题时，仅对实际主题生效。即只对共享订阅主题去除前缀 `$share/<group-name>/` 或 `$queue` 之后的部分生效。
例如：在客户端订阅/取消订阅共享订阅主题过滤器 `$share/group/t/1` 或 `$queue/t/2` 时，仅尝试匹配并重写 `t/1` 或 `t/2`，忽略 `$share/group/` 与 `$queue/`。
关于共享订阅与 `$queue`，请参考 [共享订阅](./mqtt-shared-subscription.md)。
:::

## 配置主题重写规则

EMQX 的主题重写规则需要用户自行配置，用户可以自行添加多条主题重写规则，规则的数量没有限制，但由于任何携带主题的 MQTT 报文都需要匹配一遍重写规则，因此此功能在高吞吐场景下带来的性能损耗与规则数量是成正比的，用户需要谨慎地使用此功能。

每条主题重写规则的格式如下：

```bash
rewrite = [
  {
    action:       "all"
    source_topic: "x/#"
    dest_topic:   "x/y/z/$1"
    re:           "^x/y/(.+)$"
  }
]
```

每个重写规则由过滤器、正则表达式和目标表达式组成。

重写规则分为 `publish` 、`subscribe` 和 `all` 规则，`publish` 规则匹配 PUBLISH 报文携带的主题，`subscribe` 规则匹配 SUBSCRIBE、UNSUBSCRIBE 报文携带的主题。`all` 规则对 PUBLISH、SUBSCRIBE 和 UNSUBSCRIBE 报文携带的主题都生效。

在启用主题重写的前提下，当收到 MQTT 数据包（如带有主题的PUBLISH消息）时，EMQX 将使用数据包中的主题来依次匹配配置文件中规则的主题过滤器部分。匹配成功之后，正则表达式就会被用来提取主题中的信息，然后用目标表达式替换旧的主题，生成一个新的主题。

目标表达式可以使用 `$N` 格式的变量来匹配从正则表达式中提取的元素。`$N` 的值是指从正则表达式中提取的第 N 个元素，例如，`$1` 是正则表达式提取的第一个元素。

同时，表达式中也可以使用 `${clientid}` 代表 `客户端Id`, 使用 `${username}` 代表 `客户端用户名`。

注意：EMQX 会按照配置文件中规则配置的顺序来执行主题重写。当一个主题可以同时匹配多个主题重写规则的主题过滤器时，EMQX 仅使用第一个匹配的规则来重写该主题。

如果规则中的正则表达式与 MQTT 数据包的主题不匹配，则重写失败，其他规则将不会被用来重写。因此，需要仔细设计 MQTT 数据包主题和主题重写规则。

## 示例

假设 `etc/emqx.conf` 文件中已经添加了以下主题重写规则：

```bash
rewrite = [
  {
    action:       "all"
    source_topic: "y/+/z/#"
    dest_topic:   "y/z/$2"
    re:           "^y/(.+)/z/(.+)$"
  }
  {
    action:       "all"
    source_topic: "x/#"
    dest_topic:   "z/y/x/$1"
    re:           "^x/y/(.+)$"
  }
  {
    action:       "all"
    source_topic: "x/y/+"
    dest_topic:   "z/y/$1"
    re:           "^x/y/(\d+)$"
  }
]
```

如果订阅五个主题。 `y/a/z/b`, `y/def`, `x/1/2`, `x/y/2`, 和 `x/y/z` 。

+ `y/def` 不符合任何主题过滤器，所以它不执行主题重写，只是订阅 `y/def` 主题。
+ `y/a/z/b` 匹配 `y/+/z/#` 主题过滤器，EMQX 执行第一条规则，并通过正则表达式匹配元素 `[a、b]` ，将匹配的第二个元素带入 `y/z/$2` ，并实际订阅主题 `y/z/b`。
+ `x/1/2` 匹配 `x/#` 主题过滤器，EMQX 执行第二个规则。它不通过正则表达式匹配元素，不执行主题重写，并实际订阅了 `x/1/2` 的主题。
+ `x/y/2` 同时匹配 `x/#` 和 `x/y/+` 两个主题过滤器，EMQX 以相反的顺序读取配置，所以它优先匹配第三个。通过正则替换，它实际上订阅了 `z/y/2` 主题。
+ `x/y/z` 同时匹配 `x/#` 和 `x/y/+` 两个主题过滤器，EMQX 以相反的顺序读取配置，所以优先级匹配第三个。该元素没有通过正则表达式进行匹配，没有进行主题重写，它实际上订阅了 `x/y/z` 主题。需要注意的是，即使第三条的正则表达式匹配失败，它也不会再匹配第二条的规则。
