xmpp.md 20.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
# XMPP 支持

## XMPP 支持

Spring 集成为[XMPP](https://www.xmpp.org)提供通道适配器。XMPP 代表“可扩展消息传递和存在协议”。

XMPP 描述了在分布式系统中多个代理相互通信的一种方式。典型的用例是发送和接收聊天消息,尽管 XMPP 可以(并且正在)用于其他类型的应用程序。XMPP 描述了参与者的网络。在该网络中,演员可以直接相互联系,并改变广播状态(例如“存在”)。

XMPP 提供的消息架构是世界上一些最大的即时消息网络的基础,包括 Google Talk(Gtalk,也可在 Gmail 中使用)和 Facebook Chat。许多好的开源 XMPP 服务器都是可用的。两种流行的实现方式是[Openfire](https://www.igniterealtime.org/projects/openfire/)[ejabberd](https://www.ejabberd.im)

Spring 集成通过提供 XMPP 适配器来提供对 XMPP 的支持,该适配器支持发送和接收 XMPP 聊天消息以及来自客户端花名册中其他条目的存在变化。

你需要在项目中包含此依赖项:

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-xmpp</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-xmpp:5.5.9"
```

与其他适配器一样,XMPP 适配器支持方便的基于名称空间的配置。要配置 XMPP 名称空间,在 XML 配置文件的头中包含以下元素:

```
xmlns:int-xmpp="http://www.springframework.org/schema/integration/xmpp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/xmpp
	https://www.springframework.org/schema/integration/xmpp/spring-integration-xmpp.xsd"
```

### XMPP 连接

在使用入站或出站 XMPP 适配器参与 XMPP 网络之前,参与者必须建立其 XMPP 连接。连接到特定帐户的所有 XMPP 适配器都可以共享此连接对象。通常这需要(至少)`user``password``host`。要创建基本的 XMPP 连接,你可以使用名称空间的便利,如下例所示:

```
<int-xmpp:xmpp-connection
    id="myConnection"
    user="user"
    password="password"
    host="host"
    port="port"
    resource="theNameOfTheResource"
    subscription-mode="accept_all"/>
```

|   |为了更方便起见,你可以依赖默认的命名约定,并省略`id`属性。<br/>此连接使用的默认名称(`xmppConnection`) Bean。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果 XMPP 连接失效,只要上一个连接状态被记录(经过身份验证),就会使用自动登录进行重新连接尝试。我们还注册了`ConnectionListener`,如果启用了`DEBUG`日志级别,它将记录连接事件。

`subscription-mode`属性启动 Rose Listener 来处理来自其他用户的订阅。此功能并不总是适用于目标 XMPP 服务器。例如,Google Cloud Messaging 和 Firebase Cloud Messaging 完全禁用了它。要关闭订阅的花名册侦听器,你可以在使用 XML 配置(`subscription-mode=""`)时使用空字符串进行配置,或者在使用 Java 配置时使用`XmppConnectionFactoryBean.setSubscriptionMode(null)`进行配置。这样做也会在登录阶段禁用花名册。有关更多信息,请参见[`Roster.setRosterLoadedAtLogin(boolean)`](https://download.igniterealtime.org/smack/DOCS/latest/javadoc/org/jivesoftware/smack/roster/roster.html#setrosterloadedatlogin-boolean-)。

### XMPP 消息

Spring 集成为发送和接收 XMPP 消息提供了支持。为了接收它们,它提供了一个入站消息通道适配器。对于发送它们,它提供了一个出站消息通道适配器。

#### 入站消息通道适配器

Spring 集成适配器支持从系统中的其他用户接收聊天消息。为此,入站消息通道适配器代表用户“登录”,并接收发送给该用户的消息。然后将这些消息转发到你的 Spring 集成客户端。`inbound-channel-adapter`元素为 XMPP 入站消息通道适配器提供配置支持。下面的示例展示了如何配置它:

```
<int-xmpp:inbound-channel-adapter id="xmppInboundAdapter"
	channel="xmppInbound"
	xmpp-connection="testConnection"
	payload-expression="getExtension('google:mobile:data').json"
	stanza-filter="stanzaFilter"
	auto-startup="true"/>
```

除了通常的属性(对于消息通道适配器),这个适配器还需要对 XMPP 连接的引用。

XMPP 入站适配器是事件驱动的,并且是`Lifecycle`实现。启动时,它会注册一个`PacketListener`,用于侦听传入的 XMPP 聊天消息。它将接收到的任何消息转发给底层适配器,后者将它们转换为 Spring 集成消息,并将它们发送到指定的`channel`。当停止时,它将注销`PacketListener`

从版本 4.3 开始,`ChatMessageListeningEndpoint`(及其`<int-xmpp:inbound-channel-adapter>`)支持在提供的`org.jivesoftware.smack.filter.StanzaFilter`上注册一个`org.jivesoftware.smack.filter.StanzaFilter`的注入,以及一个内部`StanzaListener`实现。有关更多信息,请参见[Javadoc](https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/XMPPConnection.html#addAsyncStanzaListener%28org.jivesoftware.smack.StanzaListener,%20org.jivesoftware.smack.filter.StanzaFilter%29)

版本 4.3 为`ChatMessageListeningEndpoint`引入了`payload-expression`属性。传入的`org.jivesoftware.smack.packet.Message`表示求值上下文的根对象。当你使用[XMPP 扩展](#xmpp-extensions)时,此选项很有用。例如,对于 GCM 协议,我们可以通过使用以下表达式来提取主体:

```
payload-expression="getExtension('google:mobile:data').json"
```

下面的示例提取了 XHTML 协议的主体:

```
payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]"
```

为了简化对 XMPP 消息中扩展的访问,将`extension`变量添加到`EvaluationContext`中。请注意,它是在消息中仅存在一个扩展名时添加的。上面显示`namespace`操作的示例可以简化为以下示例:

```
payload-expression="#extension.json"
payload-expression="#extension.bodies[0]"
```

#### 出站消息通道适配器

你还可以通过使用出站消息通道适配器向 XMPP 上的其他用户发送聊天消息。`outbound-channel-adapter`元素为 XMPP 出站消息通道适配器提供配置支持。

```
<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
						channel="outboundEventChannel"
						xmpp-connection="testConnection"/>
```

适配器期望其输入(至少)是类型`java.lang.String`的有效负载和`XmppHeaders.CHAT_TO`的标头值,该值指定消息应发送给哪个用户。要创建消息,你可以使用类似于以下内容的 Java 代码:

```
Message<String> xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" )
						.setHeader(XmppHeaders.CHAT_TO, "userhandle")
						.build();
```

你还可以通过使用 XMPP Header-Enricher 支持来设置 header,如下例所示:

```
<int-xmpp:header-enricher input-channel="input" output-channel="output">
	<int-xmpp:chat-to value="[email protected]"/>
</int-xmpp:header-enricher>
```

从版本 4.3 开始,包扩展支持已添加到`ChatMessageSendingMessageHandler`(在 XML 配置中是`<int-xmpp:outbound-channel-adapter>`)。随着常规的`String``org.jivesoftware.smack.packet.Message`有效负载,现在可以发送有效负载为`org.jivesoftware.smack.packet.ExtensionElement`(填充到`org.jivesoftware.smack.packet.Message.addExtension()`)而不是`setBody()`的消息。为了方便起见,我们为`ChatMessageSendingMessageHandler`添加了一个`extension-provider`选项。它允许你注入`org.jivesoftware.smack.provider.ExtensionElementProvider`,从而在运行时针对有效负载构建`ExtensionElement`。在这种情况下,有效负载必须是 JSON 或 XML 格式的字符串,这取决于 XEP 协议。

### XMPP 存在

XMPP 还支持广播状态。你可以利用这种能力,让那些把你列入名册的人看到你的状态变化。这种情况经常发生在你的即时通讯客户身上。你改变你的离开状态并设置一个离开消息,每个在他们的花名册上有你的人看到你的图标或用户名的变化,以反映这个新的状态,并可能看到你的新的“离开”消息。如果你希望接收通知或通知他人状态更改,可以使用 Spring Integration 的“存在”适配器。

#### 入站存在消息通道适配器

Spring 集成提供了一种入站存在消息通道适配器,其支持从系统中在你的花名册上的其他用户接收存在事件。为此,适配器代表用户“登录”,注册`RosterListener`,并将接收到的存在更新事件作为消息转发到由`channel`属性标识的通道。消息的有效负载是`org.jivesoftware.smack.packet.Presence`对象(参见[https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html](https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html))。

`presence-inbound-channel-adapter`元素为 XMPP 入站消息通道适配器提供配置支持。以下示例配置了入站存在消息通道适配器:

```
<int-xmpp:presence-inbound-channel-adapter channel="outChannel"
		xmpp-connection="testConnection" auto-startup="false"/>
```

除了通常的属性外,此适配器还需要对 XMPP 连接的引用。这个适配器是事件驱动的,并且是`Lifecycle`实现。它在启动时注册`RosterListener`,在停止时取消注册`RosterListener`

#### 出站存在消息通道适配器

Spring 集成还支持发送存在事件,以被网络中碰巧在他们的花名册上有你的其他用户看到。当你向出站存在消息通道适配器发送消息时,它会提取有效负载(预计类型为`org.jivesoftware.smack.packet.Presence`)并将其发送到 XMPP 连接,从而将你的存在事件广告到网络的其余部分。

`presence-outbound-channel-adapter`元素为 XMPP 出站消息通道适配器提供配置支持。下面的示例展示了如何配置出站消息通道适配器:

```
<int-xmpp:presence-outbound-channel-adapter id="eventOutboundPresenceChannel"
	xmpp-connection="testConnection"/>
```

它也可以是一个轮询消费者(如果它从一个可轮询通道接收消息),在这种情况下,你将需要注册一个 Poller。下面的示例展示了如何做到这一点:

```
<int-xmpp:presence-outbound-channel-adapter id="pollingOutboundPresenceAdapter"
		xmpp-connection="testConnection"
		channel="pollingChannel">
	<int:poller fixed-rate="1000" max-messages-per-poll="1"/>
</int-xmpp:presence-outbound-channel-adapter>
```

与它的入站对应物一样,它需要对 XMPP 连接的引用。

|   |如果你依赖 XMPP 连接 Bean([前面描述的](#xmpp-connection))的默认命名约定,并且在你的应用程序上下文中只配置了一个 XMPP 连接 Bean,那么你可以省略`xmpp-connection`属性。,在这种情况下,<br/>,找到名为`xmppConnection`的 Bean 并将其注入适配器。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 高级配置

Spring Integration 的 XMPP 支持基于 Smack4.0API([https://www.igniterealtime.org/projects/smack/](https://www.igniterealtime.org/projects/smack/)),它允许对 XMPP 连接对象进行更复杂的配置。

由于[更早的陈述](#xmpp-connection)`xmpp-connection`名称空间支持旨在简化基本的连接配置,并且仅支持几个常见的配置属性。然而,`org.jivesoftware.smack.ConnectionConfiguration`对象定义了大约 20 个属性,而为所有这些属性添加名称空间支持并不提供真正的价值。因此,对于更复杂的连接配置,可以将我们的`XmppConnectionFactoryBean`实例配置为常规 Bean,并将`org.jivesoftware.smack.ConnectionConfiguration`作为构造函数参数注入到`FactoryBean`。你可以在`ConnectionConfiguration`实例上直接指定所需的每个属性。( Bean 带有“P”名称空间的定义将很好地工作。)这样,你可以直接设置 SSL(或任何其他属性)。下面的示例展示了如何做到这一点:

```
<bean id="xmppConnection" class="o.s.i.xmpp.XmppConnectionFactoryBean">
    <constructor-arg>
        <bean class="org.jivesoftware.smack.ConnectionConfiguration">
            <constructor-arg value="myServiceName"/>
            <property name="socketFactory" ref="..."/>
        </bean>
    </constructor-arg>
</bean>

<int:channel id="outboundEventChannel"/>

<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
    channel="outboundEventChannel"
    xmpp-connection="xmppConnection"/>
```

Smack API 还提供了静态初始化器,这可能会很有帮助。对于更复杂的情况(例如注册 SASL 机制),你可能需要执行某些静态初始化器。其中一个静态初始化器是`SASL认证`,它允许你注册受支持的 SASL 机制。对于这种复杂程度,我们建议在 XMPP 连接配置中使用 Spring Java 配置。这样,你就可以通过 Java 代码配置整个组件,并在适当的时候执行所有其他必要的 Java 代码,包括静态初始化器。下面的示例展示了如何在 Java 中配置带有 SASL(简单身份验证和安全层)的 XMPP 连接:

```
@Configuration
public class CustomConnectionConfiguration {
  @Bean
  public XMPPConnection xmppConnection() {
	SASLAuthentication.supportSASLMechanism("EXTERNAL", 0); // static initializer

	ConnectionConfiguration config = new ConnectionConfiguration("localhost", 5223);
	config.setKeystorePath("path_to_truststore.jks");
	config.setSecurityEnabled(true);
	config.setSocketFactory(SSLSocketFactory.getDefault());
	return new XMPPConnection(config);
  }
}
```

有关使用 Java 进行应用程序上下文配置的更多信息,请参见[Spring Reference Manual](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-java)中的以下部分。

### XMPP 消息头

Spring 集成 XMPP 适配器自动映射标准 XMPP 属性。默认情况下,这些属性通过使用[`DefaultXmppHeaderMapper`](https://DOCS. Spring.io/ Spring-integration/api/org/springframework/integration/xmpp/support/defaultxmppheadermapper.html)从 Spring integration`MessageHeaders`复制到 Spring integration`MessageHeaders`

除非`DefaultXmppHeaderMapper`中的`requestHeaderNames``replyHeaderNames`属性明确指定,否则不会将任何用户定义的标题复制到或复制到 XMPP 消息。

|   |在映射用户定义的标头时,这些值还可以包含简单的通配符模式(例如“thing\*”或“\*thing”)。|
|---|----------------------------------------------------------------------------------------------------------------------|

从版本 4.1 开始,`AbstractHeaderMapper``DefaultXmppHeaderMapper`的超类)允许你为`requestHeaderNames`属性(除了`STANDARD_REQUEST_HEADERS`)配置`NON_STANDARD_HEADERS`令牌,以映射所有用户定义的标头。

`org.springframework.xmpp.XmppHeaders`类标识了`DefaultXmppHeaderMapper`要使用的默认标头:

* `xmpp_from`

* `xmpp_subject`

* `xmpp_thread`

* `xmpp_to`

* `xmpp_type`

从版本 4.3 开始,你可以通过在模式前面加上`!`来否定头映射中的模式。被否定的模式获得优先权,所以列表(如`STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1`)不映射`thing1``thing2``thing3`。该列表确实映射了标准标题加上`thing4``qux`

|   |如果你有一个希望映射的以`!`开头的用户定义的头文件,则可以使用`\`将其转义为:`STANDARD_REQUEST_HEADERS,\!myBangHeader`。<br/>在该示例中,标准的请求头文件和`!myBangHeader`文件将被映射。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### XMPP 扩展

Extensions 将“可扩展”放在“可扩展消息传递和存在协议”中。

XMPP 是基于 XML 的,XML 是一种支持命名空间概念的数据格式。通过名称空间,你可以向 XMPP 添加原始规范中未定义的位。XMPP 规范特意只描述了一组核心特性:

* 客户机如何连接到服务器

* 加密(SSL/TLS)

* Authentication

* 服务器如何相互通信以中继消息

* 其他几个基本的构建模块

一旦实现了这一点,就有了一个 XMPP 客户机,可以发送任何类型的数据。然而,你可能需要做的不只是基础工作。例如,你可能需要在消息中包含格式(粗体、斜体等),而核心 XMPP 规范中并未对此进行定义。好吧,你可以想出一种方法来做到这一点,但是,除非其他所有人都像你一样做这件事,否则没有其他软件可以解释它(它们忽略它们无法理解的名称空间)。

为了解决这个问题,XMPP 标准基金会发布了一系列额外的文档,即[XMPP 扩展协议](https://xmpp.org/extensions/xep-0001.html)。通常,每个 XEP 描述一个特定的活动(从消息格式到文件传输、多用户聊天等等)。它们还提供了一种标准格式,供每个人在该活动中使用。

Smack API 通过其`extensions`和`experimental`[projects](https://www.igniterealtime.org/builds/smack/docs/latest/documentation/extensions/index.html)提供了许多 XEP 实现。从 Spring 集成版本 4.3 开始,你可以使用现有 XMPP 通道适配器的任何 XEP。

为了能够处理 XEPS 或任何其他定制的 XMPP 扩展,你必须提供 Smack 的`ProviderManager`预配置。你可以使用`static`Java 代码来实现这一点,如下例所示:

```
ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());
```

你还可以在特定实例中使用`.providers`配置文件,并使用 JVM 参数访问它,如下例所示:

```
-Dsmack.provider.file=file:///c:/my/provider/mycustom.providers
```

`mycustom.providers`文件可能如下:

```
<?xml version="1.0"?>
<smackProviders>
<iqProvider>
    <elementName>query</elementName>
    <namespace>jabber:iq:time</namespace>
    <className>org.jivesoftware.smack.packet.Time</className>
</iqProvider>

<iqProvider>
    <elementName>query</elementName>
    <namespace>https://jabber.org/protocol/disco#items</namespace>
    <className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className>
</iqProvider>

<extensionProvider>
    <elementName>subscription</elementName>
    <namespace>https://jabber.org/protocol/pubsub</namespace>
    <className>org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider</className>
</extensionProvider>
</smackProviders>
```

例如,最流行的 XMPP 消息传递扩展是[谷歌云消息](https://developers.google.com/cloud-messaging/)。Smack 库为此目的提供了`org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider`。默认情况下,它使用`experimental.providers`资源在 Classpath 中用`smack-experimental`jar 注册该类,如下 Maven 示例所示:

```
<!-- GCM JSON payload -->
<extensionProvider>
    <elementName>gcm</elementName>
    <namespace>google:mobile:data</namespace>
    <className>org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider</className>
</extensionProvider>
```

此外,`GcmPacketExtension`允许目标消息传递协议解析传入的数据包并构建传出的数据包,如下例所示:

```
GcmPacketExtension gcmExtension = (GcmPacketExtension) xmppMessage.getExtension(GcmPacketExtension.NAMESPACE);
String message = gcmExtension.getJson());
```

```
GcmPacketExtension packetExtension = new GcmPacketExtension(gcmJson);
Message smackMessage = new Message();
smackMessage.addExtension(packetExtension);
```

有关更多信息,请参见本章前面的[入站消息通道适配器](#xmpp-message-inbound-channel-adapter)[出站消息通道适配器](#xmpp-message-outbound-channel-adapter)