openfire插件–外部组件开发
与外部交互
外部组件
openfire外部组件,就是与openfire所在的进程没有关系,甚至可以不与openfire在同一台机器上运行的组件,一般是一个可运行的jar包,我们叫做外部组件,使用tcp连接(类似客户端Socket的监听端口)与openfire之间进行通信,处理一些服务器需要处理的逻辑。目前可以使用Whack
和Tinder
连接操作。
Openfire的外部组件使用tcp连接与openfire之间进行通信。
- 发送消息到openfire,格式形如
<message to="x.myopenfire.com">内容</message>
。 - Openfire收到这条消息,发现了有一个二级域名
x.myopenfire.com
,它会在外部组件中寻找谁会处理这个域名的消息,发现了该外部组件并会转发给外部组件。 - 外部组件接受到这条消息,处理后再发回openfire,格式形如
<message to="[email protected]" from="x.myopenfire.com">内容</message>
。 - openfire发到用户上。
配置
先配置子域名和密码,如下服务器端口默认为5275,子域名为chat,密码为123456。这里操作的是ofExtComponentConf
数据库表。
Java
外部组件使用Whack
(需自行编译jar包,ver2.0.1)连接openfire,类似Socket监听服务器端口。注意必须在openfire启动后在启动外部组件,不然会报错。这种方式相当于转发。
org.xmpp.component.ComponentException: internal-server-error
import org.jivesoftware.whack.ExternalComponentManager;
import org.xmpp.component.ComponentException;
public class Main {
public static void main(String[] args) {
ExternalComponentManager mgr = new ExternalComponentManager(
"192.168.43.42", 5275); //openfire的IP和相应的端口
mgr.setSecretKey("chat", "123456"); //设置子域和口令(密码)
try {
mgr.addComponent("chat", new ChatComponent());
} catch (ComponentException e) {
e.printStackTrace();
System.exit(-1);
}
// daemon
while (true) {
try {
Thread.sleep(10000);
} catch (Exception e) {
}
}
}
}
业务逻辑实现Component,在public void processPacket(Packet packet)
处理具体数据。
import org.xmpp.component.Component;
import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManager;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
public class ChatComponent implements Component{
private static final String name="chat";
private static final String description="实现多人通过组件在区域内聊天";
private JID jid;
private ComponentManager comMgr;
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public void processPacket(Packet packet) {
System.out.println(packet.toXML());
}
public void initialize(JID jid, ComponentManager componentManager)
throws ComponentException {
this.jid=jid;
this.comMgr=componentManager;
}
public void start() {
System.out.println("component start");
}
public void shutdown() {
System.out.println("component shutdown");
}
}
通过客户端Spark登录发送hello,会接收到如下数据(前者为心跳,后者为实际文本数据)
K3ekQ1
helloK3ekQ1
主动发送消息 //TODO接收组件发送的消息失败
package xmpp;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.chat.ChatManagerListener;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.chat2.OutgoingChatMessageListener;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.io.IOException;
public class Test {
public static void main(String args[]) throws XMPPException {
try{
//smack ver4.2.3
//实质上时两个用户间的通信,其中一方为虚拟用户,代表的时外部组件
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setUsernameAndPassword("a", "a")
.setXmppDomain("wang")
.setHost("wang")
.setPort(5222)
//非安全模式下,不校验证书(todo:安全模式下的)
.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
.setDebuggerEnabled(true)
.build();
AbstractXMPPConnection connection = new XMPPTCPConnection(config);
connection.connect().login();
ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addIncomingListener(new IncomingChatMessageListener() {
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
System.out.println("New message from " + from + ": " + message.getBody());
}
});
chatManager.addOutgoingListener(new OutgoingChatMessageListener() {
public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
System.out.println("New message to " + to + ": " + message.getBody());
}
});
EntityBareJid jid = JidCreate.entityBareFrom("[email protected]");
Chat chat = chatManager.chatWith(jid);
chat.send("Howdy!");
while(true){
Thread.sleep(1000);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (XmppStringprepException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} catch (SmackException e1) {
e1.printStackTrace();
}
}
}
//TODO 安全模式下发送消息
https://github.com/escline/InstallCert
在%JRE_HOME%/bin 执行如下命令,添加证书,密钥库口令为changeit
keytool -keystore ..\lib\security\cacerts -import -alias myKey -file C:\env\tomcat\wpgl\usr\myKey.crt
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
NodeJS
openfire里面有插件,但只是负责启动NodeJS的应用,不负责数据交互。
和外部组件通信,也是基于用户间的交流
<!DOCTYPE html>
<html>
<head>
<title>Latest content</title>
<script src='http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js'></script>
<script src='http://cdn.bootcss.com/strophe.js/1.1.3/strophe.min.js'></script>
<script type="text/javascript">
var BOSH_SERVICE = 'http://wang:7070/http-bind/';
var connection = null;
function log(msg)
{
$('#log').append('<div></div>').append(document.createTextNode(msg));
}
/**
* 连接绑定方法
* @param status
*/
function onConnect(status)
{
if (status == Strophe.Status.CONNECTING) {
log('Strophe is connecting.');
} else if (status == Strophe.Status.CONNFAIL) {
log('Strophe failed to connect.');
$('#connect').get(0).value = 'connect';
} else if (status == Strophe.Status.DISCONNECTING) {
log('Strophe is disconnecting.');
} else if (status == Strophe.Status.DISCONNECTED) {
log('Strophe is disconnected.');
$('#connect').get(0).value = 'connect';
} else if (status == Strophe.Status.CONNECTED) {
log('Strophe is connected.');
log('ECHOBOT: Send a message to ' + connection.jid +
' to talk to me.');
connection.addHandler(onMessage, null, 'message', null, null, null);
connection.send($pres().tree());
}
}
/**
* 获取消息时的方法
* @param msg
* @returns {Boolean}
*/
function onMessage(msg) {
var to = msg.getAttribute('to');
var from = msg.getAttribute('from');
var type = msg.getAttribute('type');
var elems = msg.getElementsByTagName('body');
if (type == "chat" && elems.length > 0) {
var body = elems[0];
log('ECHOBOT: I got a message from ' + from + ': ' +
Strophe.getText(body));
/* 关闭echo机器的自动回复
var reply = $msg({to: from, from: to, type: 'chat'})
.cnode(Strophe.copyElement(body));
connection.send(reply.tree());
log('ECHOBOT: I sent ' + from + ': ' + Strophe.getText(body));*/
}
return true;
}
/**
* 发信息
* @param toId
* @param fromId
* @param msg
*/
function sendMsg(toId,fromId,msg) {
var reply = $msg({to: toId, from:fromId , type: 'chat'}).cnode(Strophe.xmlElement('body', '' ,msg));
connection.send(reply.tree());
log('ECHOBOT: I sent ' + toId + ': ' + msg);
}
/**
* 事件监听
*/
$(document).ready(function () {
connection = new Strophe.Connection(BOSH_SERVICE);
// Uncomment the following lines to spy on the wire traffic.
connection.rawInput = function (data) { console.log('RECV: ' + data); };
connection.rawOutput = function (data) { console.log('SEND: ' + data); };
// Uncomment the following line to see all the debug output.
//Strophe.log = function (level, msg) { log('LOG: ' + msg); };
$('#connect').bind('click', function () {
var button = $('#connect').get(0);
if (button.value == 'connect') {
button.value = 'disconnect';
connection.connect($('#jid').get(0).value,
$('#pass').get(0).value,
onConnect);
} else {
button.value = 'connect';
connection.disconnect();
}
});
$('#replay').bind('click', function () {
toId = $('#tojid').val();
fromId = $('#jid').val();
msg=$('#msg').val();
sendMsg(toId,fromId,msg);
});
});
</script>
</head>
<body>
<div id='login' style='text-align: left'>
<form name='cred'>
<label for='jid'>JID:</label>
<input type='text' id='jid' value="a@wang" /><br/>
<label for='pass'>Password:</label>
<input type='password' id='pass' value="a" /><br/>
<input type='button' id='connect' value='connect' />
</form>
</div>
<hr />
<div style="text-align: left">
<label for='tojid'>tojid</label>
<input type='text' id='tojid' value="" /><br/>
<label for='msg'>msg:</label>
<textarea id="msg">Hello world!!!</textarea>
<br/>
<input type='button' id='replay' value='replay' />
</div>
<hr />
<div id='log'></div>
</body>
</html>
NodeJS和Java通过Thrift进行RPC通信.。
附相应例子:Thrift–实现NodeJS和Java间通信