openfire源码分析---7
2015-11-29 14:34
399 查看
用户注册
本章介绍openfire中的用户注册,分成两步介绍。第一个请求
客户端首先发起一个xmpp请求,如下所示<iq id="#id" to="#serverName" type="get"> <query xmlns="jabber:iq:register"></query> </iq>
这里,#id和#serverName都是在上一章中创建的,该请求最终会到达ClientStanzaHandler的process函数,该函数从mina框架中的IoSession取出处理器ClientStanzaHandler,该ClientStanzaHandler在第一次创建IoSession时构造的,第五章中分析了,然后调用其process方法,为了方便分析,这里贴出关键代码,
public void process(String stanza, XMPPPacketReader reader) throws Exception { boolean initialStream = stanza.startsWith("<stream:stream") || stanza.startsWith("<flash:stream"); if (!sessionCreated || initialStream) { ... } ... Element doc = reader.read(new StringReader(stanza)).getRootElement(); ... String tag = doc.getName(); process(doc); }
这里,由于已经创建了LocalClientSession,这里不会进入到第一个if语句里,然后继续调用proc函数,
private void process(Element doc) throws UnauthorizedException { String tag = doc.getName(); if ("message".equals(tag)) { ... } else if ("presence".equals(tag)) { ... } else if ("iq".equals(tag)) { IQ packet; try { packet = getIQ(doc); } catch (IllegalArgumentException e) { } ... processIQ(packet); } else { } }
getIQ定义在StanzaHandler中,
private IQ getIQ(Element doc) { Element query = doc.element("query"); if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) { return new Roster(doc); } else { return new IQ(doc, !validateJIDs()); } }
根据XMPP协议,这里就是构造一个IQ,validateJIDs表是是否需要对to或者from的JID进行验证,这里默认返回true,表示需要对其进行验证,并将其添加进缓存中。
回到process函数中,接下来调用processIQ进行处理,定义在ClientStanzaHandler中,
protected void processIQ(IQ packet) throws UnauthorizedException { packet.setFrom(session.getAddress()); super.processIQ(packet); }
这里先要在packet中设置from变量,这里个人认为是为了防止后面取不出from的bug,不管它。其父类的函数如下
protected void processIQ(IQ packet) throws UnauthorizedException { router.route(packet); session.incrementClientPacketCount(); }
这里的router是在XMPPServer的start函数中构造的PacketRouterImpl,其route函数如下
public void route(IQ packet) { iqRouter.route(packet); }
iqRouter也是在XMPPServer中构造的IQRouter,其route函数如下
public void route(IQ packet) { JID sender = packet.getFrom(); ClientSession session = sessionManager.getSession(sender); Element childElement = packet.getChildElement(); // may be null try { InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false); JID to = packet.getTo(); if (session != null && to != null && session.getStatus() == Session.STATUS_CONNECTED && !serverName.equals(to.toString())) { ... } else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || ( childElement != null && isLocalServer(to) && ( "jabber:iq:auth".equals(childElement.getNamespaceURI()) || "jabber:iq:register".equals(childElement.getNamespaceURI()) || "urn:ietf:params:xml:ns:xmpp-bind".equals(childElement.getNamespaceURI())))) { handle(packet); } else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) { ... } InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true); } catch (PacketRejectedException e) { } }
这里会通过拦截器,拦截器和业务代码没有太大关系,本章暂时不分析它,后面有时间再细看。因为刚刚的XMPP消息的命名空间为jabber:iq:register,因此进入第二个if语句,调用handle进行处理,继续看
private void handle(IQ packet) { JID recipientJID = packet.getTo(); ... try { ... if (isLocalServer(recipientJID)) { Element childElement = packet.getChildElement(); String namespace = null; if (childElement != null) { namespace = childElement.getNamespaceURI(); } if (namespace == null) { ... } else { ... IQHandler handler = getHandler(namespace); if (handler == null) { ... } else { handler.process(packet); } } } else { ... } } catch (Exception e) { } }
这里首先判断客户端请求的XMPP消息是否发往本服务器,如果是,就从该消息中取出namespace(jabber:iq:register),然后调用getHandler根据namespace取出相应的事件处理器。
private IQHandler getHandler(String namespace) { IQHandler handler = namespace2Handlers.get(namespace); if (handler == null) { for (IQHandler handlerCandidate : iqHandlers) { IQHandlerInfo handlerInfo = handlerCandidate.getInfo(); if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) { handler = handlerCandidate; namespace2Handlers.put(namespace, handler); break; } } } return handler; }
这里就是从iq消息的Handler列表iqHandlers中依次取出Handler,然后比较namespace,如果相等就添加到namespace2Handlers哈希表中,然后返回。哪一个Handler对应到刚才的XMPP消息的命名空间jabber:iq:register呢?IQRegisterHandler!看看其构造函数,
public IQRegisterHandler() { super("XMPP Registration Handler"); info = new IQHandlerInfo("query", "jabber:iq:register"); }
它是在XMPP中loadModules函数中实例化的,然后在IQRouter的initialize函数中被添加进iqHandlers中的,
public void initialize(XMPPServer server) { ... iqHandlers.addAll(server.getIQHandlers()); ... }
返回到handle函数中,取出IQRegisterHandler后,调用其process函数继续处理,该函数定义在其父类IQHandler中,
public void process(Packet packet) throws PacketException { IQ iq = (IQ) packet; try { IQ reply = handleIQ(iq); if (reply != null) { deliverer.deliver(reply); } } catch (org.jivesoftware.openfire.auth.UnauthorizedException e) { } catch (Exception e) { } }
首先将消息包Packet强制转化为IQ消息包,然后调用handleIQ进行处理,然后调用deliver函数发送结果。handleIQ函数定义在IQRegisterHandler中,
public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException { ClientSession session = sessionManager.getSession(packet.getFrom()); IQ reply = null; if (IQ.Type.get.equals(packet.getType())) { if (!registrationEnabled) { ... } else { reply = IQ.createResultIQ(packet); if (session.getStatus() == Session.STATUS_AUTHENTICATED) { ... } else { reply.setTo((JID) null); reply.setChildElement(probeResult.createCopy()); } } } else if (IQ.Type.set.equals(packet.getType())) { ... } if (reply != null) { session.process(reply); } return null; }
这里本次客户端的请求类型为get,然后调用createResultIQ构造返回信息,并添加一些基本的信息,
public static IQ createResultIQ(IQ iq) { IQ result = new IQ(Type.result, iq.getID()); result.setFrom(iq.getTo()); result.setTo(iq.getFrom()); return result; }
因此,这里就是设置XMPP协议中type、to和from属性。
回到handleIQ函数,接下来先更改reply的to属性为null,然后设置其子元素。probeResult在IQRegisterHandler模块的initialize函数中被设置,
public void initialize(XMPPServer server) { super.initialize(server); userManager = server.getUserManager(); rosterManager = server.getRosterManager(); if (probeResult == null) { probeResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:register")); probeResult.addElement("username"); probeResult.addElement("password"); probeResult.addElement("email"); probeResult.addElement("name"); final DataForm registrationForm = new DataForm(DataForm.Type.form); registrationForm.setTitle("XMPP Client Registration"); registrationForm.addInstruction("Please provide the following information"); final FormField fieldForm = registrationForm.addField(); fieldForm.setVariable("FORM_TYPE"); fieldForm.setType(FormField.Type.hidden); fieldForm.addValue("jabber:iq:register"); final FormField fieldUser = registrationForm.addField(); fieldUser.setVariable("username"); fieldUser.setType(FormField.Type.text_single); fieldUser.setLabel("Username"); fieldUser.setRequired(true); final FormField fieldName = registrationForm.addField(); fieldName.setVariable("name"); fieldName.setType(FormField.Type.text_single); fieldName.setLabel("Full name"); if (UserManager.getUserProvider().isNameRequired()) { fieldName.setRequired(true); } final FormField fieldMail = registrationForm.addField(); fieldMail.setVariable("email"); fieldMail.setType(FormField.Type.text_single); fieldMail.setLabel("Email"); if (UserManager.getUserProvider().isEmailRequired()) { fieldMail.setRequired(true); } final FormField fieldPwd = registrationForm.addField(); fieldPwd.setVariable("password"); fieldPwd.setType(FormField.Type.text_private); fieldPwd.setLabel("Password"); fieldPwd.setRequired(true); probeResult.add(registrationForm.getElement()); } JiveGlobals.migrateProperty("register.inband"); JiveGlobals.migrateProperty("register.password"); registrationEnabled = JiveGlobals.getBooleanProperty("register.inband", true); canChangePassword = JiveGlobals.getBooleanProperty("register.password", true); }
这里就不详细分析了,总之就是构造一个Element,添加对应的元素。回到handleIQ函数中,最后调用LocalClientSession的process函数发送消息,这样最后客户端返回的结果如下,
<iq type="result" id="#id" from="#serverName"> <query xmlns="jabber:iq:register"> <username/><password/><email/><name/> <x xmlns="jabber:x:data" type="form"> <title>XMPP Client Registration</title> <instructions>Please provide the following information</instructions> <field var="FORM_TYPE" type="hidden"> <value>jabber:iq:register</value> </field> <field var="username" type="text-single" label="Username"> <required/> </field> <field var="name" type="text-single" label="Full name"/> <field var="email" type="text-single" label="Email"/> <field var="password" type="text-private" label="Password"> <required/> </field> </x> </query> </iq>
因此这里就是返回给客户端服务器需要的信息,客户端需要根据这里得到的信息进行相应的填写,并提交给openfire服务器。
第二个请求
这里假设客户端根据刚刚服务器返回的信息,进行填写,然后提交给openfire服务器的消息如下所示,<iq id="#id" to="#serverName" type="set"> <query xmlns="jabber:iq:register"> <username>123</username> <email></email> <name></name> <password>456</password> </query> </iq>
参照上面第一个客户端请求的分析,这里最后会到达handleIQ,但是由于本次请求的type为set,因此这里重新贴一遍该函数,
public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException { ClientSession session = sessionManager.getSession(packet.getFrom()); IQ reply = null; if (IQ.Type.get.equals(packet.getType())) { ... } else if (IQ.Type.set.equals(packet.getType())) { try { Element iqElement = packet.getChildElement(); if (iqElement.element("remove") != null) { ... } else { String username; String password = null; String email = null; String name = null; User newUser; DataForm registrationForm; FormField field; Element formElement = iqElement.element("x"); if (formElement != null) { ... } else { username = iqElement.elementText("username"); password = iqElement.elementText("password"); email = iqElement.elementText("email"); name = iqElement.elementText("name"); } ... if (session.getStatus() == Session.STATUS_AUTHENTICATED) { ... } else { if (!registrationEnabled) { ... } else if (password == null || password.trim().length() == 0) { ... } else { newUser = userManager.createUser(username, password, name, email); } } if (newUser != null && name != null && !name.equals(newUser.getName())) { newUser.setName(name); } reply = IQ.createResultIQ(packet); } } catch (Exception e) { } } if (reply != null) { session.process(reply); } return null; }
这里省略了大部分代码,剩下一些关键代码,因此可以很容易看出这里就是调用UserManager的create函数,然后构造返回信息。
public User createUser(String username, String password, String name, String email) throws UserAlreadyExistsException { ... User user = provider.createUser(username, password, name, email); userCache.put(username, user); Map<String,Object> params = Collections.emptyMap(); UserEventDispatcher.dispatchEvent(user, UserEventDispatcher.EventType.user_created, params); return user; }
provider默认为DefaultUserProvider,其createUser函数如下,
public User createUser(String username, String password, String name, String email) throws UserAlreadyExistsException { try { loadUser(username); } catch (UserNotFoundException unfe) { ... Date now = new Date(); Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(INSERT_USER); pstmt.setString(1, username); if (password == null) { pstmt.setNull(2, Types.VARCHAR); } else { pstmt.setString(2, password); } if (encryptedPassword == null) { pstmt.setNull(3, Types.VARCHAR); } else { pstmt.setString(3, encryptedPassword); } if (name == null || name.matches("\\s*")) { pstmt.setNull(4, Types.VARCHAR); } else { pstmt.setString(4, name); } if (email == null || email.matches("\\s*")) { pstmt.setNull(5, Types.VARCHAR); } else { pstmt.setString(5, email); } pstmt.setString(6, StringUtils.dateToMillis(now)); pstmt.setString(7, StringUtils.dateToMillis(now)); pstmt.execute(); } catch (Exception e) { } finally { DbConnectionManager.closeConnection(pstmt, con); } return new User(username, name, email, now, now); } }
这里首先调用loadUser查询数据库是否已经有该用户名对应的用户,这里假设没有,然后下面就是将用户的数据存入数据库,接着创建一个User并返回,用来存入缓存。值得注意的是,这里都是一些openfire的默认实现,实际项目中,肯定需要根据具体业务定义一些特定的类或者函数。
返回到UserManager中,接下来就是将User实例添加进缓存,并触发监听函数,例如openfire中存在一些公共的群组,需要更新这些群组信息。
再回到handleIQ,接下来通过createResultIQ构造返回信息,最后返回的信息是,
<iq type="result" id="#id" from="#from" to="#to"/>
到此,关于openfire注册的全部逻辑就分析到这了,下一章分析登录。
相关文章推荐
- 小项目-linux下简单shell
- 由lib引发的血案(opencv找不函数问题)
- Linux统计某文件夹下文件、文件夹的个数
- OpenGL(二)之使用GLUT进行显示窗口管理
- centos忘记root密码解决方法
- unbuntu server12.04配置hadoop2.7.1(四):hadoop 2.7.1的安装(所有2.x版本都可以)
- Linux终端的总结和shell
- 20135326、20135303-linux实验四实验报告
- Apache内存池使用过程的分析
- linux下时间同步的方法
- 20135326、20135303-linux实验二实验报告
- Java日志学习三:Apache Log4j源码浅析
- Linux扫描技术笔记
- 20135326、20135303-linux实验三实验报告
- centos 7 安装mySql
- 自己安装cocopods过程遇到问题,菜鸟一个不喜勿喷!
- 【OpenCV学习笔记】1.1简介环境搭建
- Java日志学习二:Apache Commons Logging (JCL)源码
- linux命令C开发下使用常用函数system与popen开销比较
- Nginx配置文件nginx.conf中文详解(汇总)