You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by be...@apache.org on 2012/04/20 17:45:00 UTC
svn commit: r1328417 - in
/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src:
main/java/org/apache/vysper/xmpp/extension/xep0124/
test/java/org/apache/vysper/xmpp/extension/xep0124/
Author: berndf
Date: Fri Apr 20 15:45:00 2012
New Revision: 1328417
URL: http://svn.apache.org/viewvc?rev=1328417&view=rev
Log:
+ rename writeBoshResponse() (again)
+ don't create standard stanzas over and over again
+ create less short-lived objects when merging
+ move helper methods from BoshHandler to static utilities
+ improve error handling and logging
Added:
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshStanzaUtils.java
Modified:
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandler.java
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshRequest.java
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContextTest.java
mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandlerTest.java
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContext.java Fri Apr 20 15:45:00 2012
@@ -20,6 +20,7 @@
package org.apache.vysper.xmpp.extension.xep0124;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.SortedMap;
@@ -201,7 +202,7 @@ public class BoshBackedSessionContext ex
synchronized public void write(Stanza stanza) {
if (stanza == null) throw new IllegalArgumentException("stanza must not be null.");
LOGGER.debug("adding server stanza for writing to BOSH client");
- writeBOSHResponse(boshHandler.wrapStanza(stanza));
+ writeBoshResponse(BoshStanzaUtils.wrapStanza(stanza));
}
/**
@@ -212,9 +213,9 @@ public class BoshBackedSessionContext ex
*
* @param responseStanza The BOSH response to write
*/
- /*package*/ void writeBOSHResponse(Stanza responseStanza) {
+ /*package*/ void writeBoshResponse(Stanza responseStanza) {
if (responseStanza == null) throw new IllegalArgumentException();
- final boolean isEmtpyResponse = responseStanza == BoshHandler.EMPTY_BOSH_RESPONSE;
+ final boolean isEmtpyResponse = responseStanza == BoshStanzaUtils.EMPTY_BOSH_RESPONSE;
BoshRequest req;
BoshResponse boshResponse;
@@ -283,8 +284,8 @@ public class BoshBackedSessionContext ex
final Long rid = br.getRid();
requestsWindow.put(rid, br);
BoshRequest req = requestsWindow.remove(requestsWindow.firstKey());
- Stanza body = boshHandler.getTerminateResponse();
- body = boshHandler.addAttribute(body, "condition", condition);
+ Stanza body = BoshStanzaUtils.TERMINATE_BOSH_RESPONSE;
+ body = BoshStanzaUtils.addAttribute(body, "condition", condition);
BoshResponse boshResponse = getBoshResponse(body, null);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("rid = {} - BOSH writing response: {}", rid, new String(boshResponse.getContent()));
@@ -302,7 +303,7 @@ public class BoshBackedSessionContext ex
// respond to all the queued HTTP requests with termination responses
while (!requestsWindow.isEmpty()) {
BoshRequest req = requestsWindow.remove(requestsWindow.firstKey());
- Stanza body = boshHandler.getTerminateResponse();
+ Stanza body = BoshStanzaUtils.TERMINATE_BOSH_RESPONSE;
BoshResponse boshResponse = getBoshResponse(body, null);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("rid = {} - BOSH writing response: {}", req.getRid(), new String(boshResponse.getContent()));
@@ -411,19 +412,23 @@ public class BoshBackedSessionContext ex
* specified by the client in its request, whichever is lower.
* @param version the BOSH version
*/
- public void setBoshVersion(String version) {
- String[] v = boshVersion.split("\\.");
- int major = Integer.parseInt(v[0]);
- int minor = Integer.parseInt(v[1]);
- v = version.split("\\.");
-
- if (v.length == 2) {
- int clientMajor = Integer.parseInt(v[0]);
- int clientMinor = Integer.parseInt(v[1]);
-
- if (clientMajor < major || (clientMajor == major && clientMinor < minor)) {
- boshVersion = version;
+ public void setBoshVersion(String version) throws NumberFormatException {
+ try {
+ String[] v = boshVersion.split("\\.");
+ int major = Integer.parseInt(v[0]);
+ int minor = Integer.parseInt(v[1]);
+ v = version.split("\\.");
+
+ if (v.length == 2) {
+ int clientMajor = Integer.parseInt(v[0]);
+ int clientMinor = Integer.parseInt(v[1]);
+
+ if (clientMajor < major || (clientMajor == major && clientMinor < minor)) {
+ boshVersion = version;
+ }
}
+ } catch (NumberFormatException e) {
+ throw e;
}
}
@@ -474,7 +479,7 @@ public class BoshBackedSessionContext ex
}
LOGGER.debug("rid = {} - BOSH request expired", req.getRid());
while (!requestsWindow.isEmpty() && requestsWindow.firstKey() <= req.getRid()) {
- writeBOSHResponse(BoshHandler.EMPTY_BOSH_RESPONSE);
+ writeBoshResponse(BoshStanzaUtils.EMPTY_BOSH_RESPONSE);
}
}
@@ -484,8 +489,9 @@ public class BoshBackedSessionContext ex
*
* @param req the HTTP request
*/
- public void insertRequest(BoshRequest br) {
+ public void insertRequest(final BoshRequest br) {
+ final Stanza stanza = br.getBody();
final Long rid = br.getRid();
LOGGER.debug("rid = {} - inserting new BOSH request", rid);
@@ -517,14 +523,14 @@ public class BoshBackedSessionContext ex
}
return;
}
- if (requestsWindow.size() + 1 > currentRequests && !"terminate".equals(br.getBody().getAttributeValue("type"))
- && br.getBody().getAttributeValue("pause") == null) {
+ if (requestsWindow.size() + 1 > currentRequests && !"terminate".equals(stanza.getAttributeValue("type"))
+ && stanza.getAttributeValue("pause") == null) {
LOGGER.warn("BOSH Overactivity: Too many simultaneous requests");
error(br, "policy-violation");
return;
}
- if (requestsWindow.size() + 1 == currentRequests && !"terminate".equals(br.getBody().getAttributeValue("type"))
- && br.getBody().getAttributeValue("pause") == null && br.getBody().getInnerElements().isEmpty()) {
+ if (requestsWindow.size() + 1 == currentRequests && !"terminate".equals(stanza.getAttributeValue("type"))
+ && stanza.getAttributeValue("pause") == null && stanza.getInnerElements().isEmpty()) {
if (!requestsWindow.isEmpty()
&& br.getTimestamp() - requestsWindow.get(requestsWindow.lastKey()).getTimestamp() < polling * 1000) {
LOGGER.warn("BOSH Overactivity: Too frequent requests");
@@ -532,7 +538,7 @@ public class BoshBackedSessionContext ex
return;
}
}
- if ((wait == 0 || hold == 0) && br.getBody().getInnerElements().isEmpty()) {
+ if ((wait == 0 || hold == 0) && stanza.getInnerElements().isEmpty()) {
if (latestEmptyPollingRequest != null && br.getTimestamp() - latestEmptyPollingRequest.getTimestamp() < polling * 1000) {
LOGGER.warn("BOSH Overactivity for polling: Too frequent requests");
error(br, "policy-violation");
@@ -561,7 +567,7 @@ public class BoshBackedSessionContext ex
if (isClientAcknowledgements()) {
synchronized (sentResponses) {
- if (br.getBody().getAttribute("ack") == null) {
+ if (stanza.getAttribute("ack") == null) {
// if there is no ack attribute present then the client confirmed it received all the responses to all the previous requests
// and we clear the cache
sentResponses.clear();
@@ -570,7 +576,7 @@ public class BoshBackedSessionContext ex
// the connection manager MAY inform the client of the situation. In this case it SHOULD include a 'report' attribute set
// to one greater than the 'ack' attribute it received from the client, and a 'time' attribute set to the number of milliseconds
// since it sent the response associated with the 'report' attribute.
- long ack = Long.parseLong(br.getBody().getAttributeValue("ack"));
+ long ack = Long.parseLong(stanza.getAttributeValue("ack"));
if (ack < sentResponses.lastKey() && sentResponses.containsKey(ack + 1)) {
long delta = System.currentTimeMillis() - sentResponses.get(ack + 1).getTimestamp();
if (delta >= brokenConnectionReportTimeout) {
@@ -585,15 +591,17 @@ public class BoshBackedSessionContext ex
// we cannot pause if there are missing requests, this is tested with
// br.getRid().equals(requestsWindow.lastKey()) && highestReadRid.equals(br.getRid())
synchronized (requestsWindow) {
- if (br.getBody().getAttribute("pause") != null && rid.equals(requestsWindow.lastKey()) && highestReadRid.equals(rid)) {
- int pause = Integer.parseInt(br.getBody().getAttributeValue("pause"));
- if (pause > maxpause) {
- // do not allow to pause more than maxpause
- pause = maxpause;
- }
- if (pause < 0) {
- pause = 0;
+ final String pauseAttribute = stanza.getAttributeValue("pause");
+ if (pauseAttribute != null && rid.equals(requestsWindow.lastKey()) && highestReadRid.equals(rid)) {
+ int pause;
+ try {
+ pause = Integer.parseInt(pauseAttribute);
+ } catch (NumberFormatException e) {
+ error(br, "bad-request");
+ return;
}
+ pause = Math.max(0, pause);
+ pause = Math.min(pause, maxpause);
respondToPause(pause);
return;
}
@@ -602,19 +610,21 @@ public class BoshBackedSessionContext ex
// If there are delayed responses waiting to be sent to the BOSH client, then we wrap them all in
// a <body/> element and send them as a HTTP response to the current HTTP request.
Stanza delayedResponse;
- Stanza mergedResponse = null;
+ ArrayList<Stanza> mergeCandidates = null; // do not create until there is a delayed response
while ((delayedResponse = delayedResponseQueue.poll()) != null) {
- mergedResponse = boshHandler.mergeResponses(mergedResponse, delayedResponse);
+ if (mergeCandidates == null) mergeCandidates = new ArrayList<Stanza>();
+ mergeCandidates.add(delayedResponse);
}
+ Stanza mergedResponse = BoshStanzaUtils.mergeResponses(mergeCandidates);
if (mergedResponse != null) {
- writeBOSHResponse(mergedResponse);
+ writeBoshResponse(mergedResponse);
return;
}
// If there are more suspended enqueued requests than it is allowed by the BOSH 'hold' parameter,
// than we release the oldest one by sending an empty response.
if (requestsWindow.size() > hold) {
- writeBOSHResponse(BoshHandler.EMPTY_BOSH_RESPONSE);
+ writeBoshResponse(BoshStanzaUtils.EMPTY_BOSH_RESPONSE);
}
}
@@ -626,15 +636,15 @@ public class BoshBackedSessionContext ex
if (boshRequest == null) {
break;
}
- writeBOSHResponse(BoshHandler.EMPTY_BOSH_RESPONSE);
+ writeBoshResponse(BoshStanzaUtils.EMPTY_BOSH_RESPONSE);
}
}
protected void sendBrokenConnectionReport(long report, long delta) {
- Stanza body = boshHandler.getTerminateResponse();
- body = boshHandler.addAttribute(body, "report", Long.toString(report));
- body = boshHandler.addAttribute(body, "time", Long.toString(delta));
- writeBOSHResponse(body);
+ Stanza body = BoshStanzaUtils.TERMINATE_BOSH_RESPONSE;
+ body = BoshStanzaUtils.addAttribute(body, "report", Long.toString(report));
+ body = BoshStanzaUtils.addAttribute(body, "time", Long.toString(delta));
+ writeBoshResponse(body);
}
protected void addContinuationExpirationListener(final AsyncContext context) {
@@ -678,7 +688,7 @@ public class BoshBackedSessionContext ex
protected BoshResponse getBoshResponse(Stanza stanza, Long ack) {
if (ack != null) {
- stanza = boshHandler.addAttribute(stanza, "ack", ack.toString());
+ stanza = BoshStanzaUtils.addAttribute(stanza, "ack", ack.toString());
}
byte[] content = new Renderer(stanza).getComplete().getBytes();
return new BoshResponse(contentType, content);
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshEndpoint.java Fri Apr 20 15:45:00 2012
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
/**
* Allows HTTP clients to communicate via the BOSH protocol with Vysper.
+ * This endpoint creates an (embedded) servlet container answering HTTP.
* <p>
* See http://xmpp.org/extensions/xep-0124.html and
* http://xmpp.org/extensions/xep-0206.html
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandler.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandler.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandler.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandler.java Fri Apr 20 15:45:00 2012
@@ -23,9 +23,11 @@ import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.vysper.xml.fragment.Attribute;
+import org.apache.vysper.xml.fragment.Renderer;
import org.apache.vysper.xml.fragment.XMLElement;
import org.apache.vysper.xmpp.protocol.NamespaceURIs;
import org.apache.vysper.xmpp.server.ServerRuntimeContext;
@@ -46,13 +48,6 @@ import org.slf4j.LoggerFactory;
public class BoshHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(BoshHandler.class);
-
- /**
- * the empty BOSH response.
- * <p>
- * Looks like <code><body xmlns='http://jabber.org/protocol/httpbind'/></code>
- */
- protected static final Stanza EMPTY_BOSH_RESPONSE = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH).build();
private ServerRuntimeContext serverRuntimeContext;
@@ -115,9 +110,12 @@ public class BoshHandler {
return;
}
} else {
- BoshBackedSessionContext session = sessions.get(body.getAttributeValue("sid"));
+ final String sid = body.getAttributeValue("sid");
+ BoshBackedSessionContext session = null;
+ if (sid != null) session = sessions.get(sid);
if (session == null) {
- LOGGER.warn("Received an invalid 'sid'!");
+ LOGGER.warn("Received an invalid sid = '{}', should terminating connection", sid);
+ // TODO terminate connection
return;
}
synchronized (session) {
@@ -137,37 +135,40 @@ public class BoshHandler {
}
private void processSession(BoshBackedSessionContext session, BoshRequest br) {
+ final Stanza stanza = br.getBody();
if (session.getState() == SessionState.ENCRYPTED) {
- if (br.getBody().getInnerElements().isEmpty()) {
+ if (stanza.getInnerElements().isEmpty()) {
// session needs authentication
return;
}
- for (XMLElement element : br.getBody().getInnerElements()) {
+ for (XMLElement element : stanza.getInnerElements()) {
if (element.getNamespaceURI().equals(NamespaceURIs.URN_IETF_PARAMS_XML_NS_XMPP_SASL)) {
processStanza(session, element);
}
}
} else if (session.getState() == SessionState.AUTHENTICATED) {
- if ("true".equals(br.getBody().getAttributeValue(NamespaceURIs.URN_XMPP_XBOSH, "restart"))) {
+ if ("true".equals(stanza.getAttributeValue(NamespaceURIs.URN_XMPP_XBOSH, "restart"))) {
// restart request
- session.writeBOSHResponse(getRestartResponse());
+ session.writeBoshResponse(BoshStanzaUtils.RESTART_BOSH_RESPONSE);
} else {
// any other request
- for (XMLElement element : br.getBody().getInnerElements()) {
+ for (XMLElement element : stanza.getInnerElements()) {
processStanza(session, element);
}
// if the client solicited the session termination
- if ("terminate".equals(br.getBody().getAttributeValue("type"))) {
+ if ("terminate".equals(stanza.getAttributeValue("type"))) {
terminateSession(session);
}
}
+ } else {
+ LOGGER.debug("processing session while in state = " + session.getState());
}
}
private void terminateSession(BoshBackedSessionContext session) {
sessions.remove(session.getSessionId());
- session.writeBOSHResponse(getTerminateResponse());
+ session.writeBoshResponse(BoshStanzaUtils.TERMINATE_BOSH_RESPONSE);
session.close();
}
@@ -184,37 +185,55 @@ public class BoshHandler {
}
private void createSession(BoshRequest br) throws IOException {
+ final Stanza stanza = br.getBody();
+
BoshBackedSessionContext session = new BoshBackedSessionContext(this, serverRuntimeContext, inactivityChecker);
- if (br.getBody().getAttribute("content") != null) {
- session.setContentType(br.getBody().getAttributeValue("content"));
- }
- if (br.getBody().getAttribute("wait") != null) {
- int wait = Integer.parseInt(br.getBody().getAttributeValue("wait"));
+
+ final String contentAttribute = stanza.getAttributeValue("content");
+ if (contentAttribute != null) session.setContentType(contentAttribute);
+
+ String waitAttribute = null;
+ try {
+ waitAttribute = stanza.getAttributeValue("wait");
+ final int wait = Integer.parseInt(waitAttribute);
session.setWait(wait);
+ } catch (NumberFormatException e) {
+ LOGGER.warn("wait value is expected to be an Integer, not {}", waitAttribute);
}
- if (br.getBody().getAttribute("hold") != null) {
- int hold = Integer.parseInt(br.getBody().getAttributeValue("hold"));
+
+ final String holdAttribute = stanza.getAttributeValue("hold");
+ try {
+ int hold = Integer.parseInt(holdAttribute);
session.setHold(hold);
+ } catch (NumberFormatException e) {
+ LOGGER.warn("hold value is expected to be an Integer, not {}", waitAttribute);
}
- if (br.getBody().getAttribute("ver") != null) {
- String ver = br.getBody().getAttributeValue("ver");
- session.setBoshVersion(ver);
- }
- if (br.getBody().getAttribute(NamespaceURIs.XML, "lang") != null) {
- String lang = br.getBody().getAttributeValue(NamespaceURIs.XML, "lang");
- session.setXMLLang(lang);
+
+ final String versionAttribute = stanza.getAttributeValue("ver");
+ if (versionAttribute != null) {
+ try {
+ session.setBoshVersion(versionAttribute);
+ } catch (NumberFormatException e) {
+ LOGGER.warn("bosh version is expected to be of form nn.mm, not {}", waitAttribute);
+ }
}
- if ("1".equals(br.getBody().getAttributeValue("ack"))) {
+
+ final String langAttribute = stanza.getAttributeValue(NamespaceURIs.XML, "lang");
+ if (langAttribute != null) session.setXMLLang(langAttribute);
+
+ if ("1".equals(stanza.getAttributeValue("ack"))) {
session.setClientAcknowledgements(true);
}
session.insertRequest(br);
sessions.put(session.getSessionId(), session);
- session.writeBOSHResponse(getSessionCreationResponse(session));
+ LOGGER.info("BOSH session created with session id = {}", session.getSessionId());
+
+ session.writeBoshResponse(getSessionCreationResponse(session));
}
private Stanza getSessionCreationResponse(BoshBackedSessionContext session) {
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ StanzaBuilder body = BoshStanzaUtils.createBoshStanzaBuilder();
body.addAttribute("wait", Integer.toString(session.getWait()));
body.addAttribute("inactivity", Integer.toString(session.getInactivity()));
body.addAttribute("polling", Integer.toString(session.getPolling()));
@@ -234,71 +253,11 @@ public class BoshHandler {
.getAuthenticationMethods(), session);
body.addPreparedElement(features);
return body.build();
- }
- /**
- * Creates a BOSH response by wrapping a stanza in a <body/> element
- * @param stanza the XMPP stanza to wrap
- * @return the BOSH response
- */
- public Stanza wrapStanza(Stanza stanza) {
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
- body.addPreparedElement(stanza);
- return body.build();
}
-
- /**
- * Creates a BOSH response by merging 2 other BOSH responses, this is useful when sending more than one message as
- * a response to a HTTP request.
- * @param response1 the first BOSH response to merge
- * @param response2 the second BOSH response to merge
- * @return the merged BOSH response
- */
- public Stanza mergeResponses(Stanza response1, Stanza response2) {
- if (response1 == null && response2 == null) {
- return null;
- }
- if (response1 == null) {
- return response2;
- }
- if (response2 == null) {
- return response1;
- }
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
- for (XMLElement element : response1.getInnerElements()) {
- body.addPreparedElement(element);
- }
- for (XMLElement element : response2.getInnerElements()) {
- body.addPreparedElement(element);
- }
- return body.build();
- }
-
- private Stanza getRestartResponse() {
- Stanza features = new ServerResponses().getFeaturesForSession();
- return wrapStanza(features);
- }
-
- /**
- * Creates a session termination BOSH response
- * @return the termination BOSH body
- */
- public Stanza getTerminateResponse() {
- StanzaBuilder stanzaBuilder = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
- stanzaBuilder.addAttribute("type", "terminate");
- return stanzaBuilder.build();
- }
-
- /**
- * Adds a custom attribute to a BOSH body.
- *
- * @param stanza the BOSH body
- * @param attributeName the name of the attribute
- * @param attributeValue the value of the attribute
- * @return a new BOSH body identical with the one provided except it also has the newly added attribute
- */
+
public Stanza addAttribute(Stanza stanza, String attributeName, String attributeValue) {
- StanzaBuilder stanzaBuilder = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ StanzaBuilder stanzaBuilder = BoshStanzaUtils.createBoshStanzaBuilder();
for (Attribute attr : stanza.getAttributes()) {
stanzaBuilder.addAttribute(attr);
}
@@ -308,5 +267,4 @@ public class BoshHandler {
}
return stanzaBuilder.build();
}
-
}
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshRequest.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshRequest.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshRequest.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshRequest.java Fri Apr 20 15:45:00 2012
@@ -68,18 +68,15 @@ public class BoshRequest implements Comp
@Override
public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
BoshRequest other = (BoshRequest) obj;
if (rid == null) {
- if (other.rid != null)
- return false;
- } else if (!rid.equals(other.rid))
- return false;
+ if (other.rid != null) return false;
+ } else {
+ if (!rid.equals(other.rid)) return false;
+ }
return true;
}
Added: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshStanzaUtils.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshStanzaUtils.java?rev=1328417&view=auto
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshStanzaUtils.java (added)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/main/java/org/apache/vysper/xmpp/extension/xep0124/BoshStanzaUtils.java Fri Apr 20 15:45:00 2012
@@ -0,0 +1,95 @@
+package org.apache.vysper.xmpp.extension.xep0124;
+
+import org.apache.vysper.xml.fragment.Attribute;
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.server.response.ServerResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+
+import java.util.Collection;
+
+/**
+ */
+public class BoshStanzaUtils {
+
+ /**
+ * the empty BOSH response.
+ * <p>
+ * Looks like <code><body xmlns='http://jabber.org/protocol/httpbind'/></code>
+ */
+ protected static final Stanza EMPTY_BOSH_RESPONSE = createBoshStanzaBuilder().build();
+
+ protected static final Stanza RESTART_BOSH_RESPONSE = wrapStanza(new ServerResponses().getFeaturesForSession());
+
+ protected static final Stanza TERMINATE_BOSH_RESPONSE = createTerminateResponse();
+
+ /**
+ * Creates a new BOSH response builder
+ */
+ public static StanzaBuilder createBoshStanzaBuilder() {
+ return new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ }
+
+ /**
+ * Creates a BOSH response by wrapping a stanza in a <body/> element
+ * @param stanza the XMPP stanza to wrap
+ * @return the BOSH response
+ */
+ public static Stanza wrapStanza(Stanza stanza) {
+ StanzaBuilder body = createBoshStanzaBuilder();
+ body.addPreparedElement(stanza);
+ return body.build();
+ }
+
+ /**
+ * Creates a unified BOSH response by merging BOSH responses, this is useful when sending more than one message as
+ * a response to a HTTP request.
+ * @param response1 the first BOSH response to merge
+ * @param response2 the second BOSH response to merge
+ * @return the merged BOSH response
+ */
+ public static Stanza mergeResponses(Collection<Stanza> mergeCandidates) {
+ if (mergeCandidates == null || mergeCandidates.size() == 0) {
+ return null;
+ }
+ StanzaBuilder body = createBoshStanzaBuilder();
+ for (Stanza mergee : mergeCandidates) {
+ if (mergee == null) continue;
+ for (XMLElement element : mergee.getInnerElements()) {
+ body.addPreparedElement(element);
+ }
+ }
+ return body.build();
+ }
+
+ /**
+ * Creates a session termination BOSH response
+ * @return the termination BOSH body
+ */
+ private static Stanza createTerminateResponse() {
+ StanzaBuilder stanzaBuilder = createBoshStanzaBuilder();
+ stanzaBuilder.addAttribute("type", "terminate");
+ return stanzaBuilder.build();
+ }
+
+ /**
+ * Adds a custom attribute to a BOSH body.
+ *
+ * @param stanza the BOSH body
+ * @param attributeName the name of the attribute
+ * @param attributeValue the value of the attribute
+ * @return a new BOSH body identical with the one provided except it also has the newly added attribute
+ */
+ public static Stanza addAttribute(Stanza stanza, String attributeName, String attributeValue) {
+ StanzaBuilder stanzaBuilder = createBoshStanzaBuilder();
+ for (Attribute attr : stanza.getAttributes()) {
+ stanzaBuilder.addAttribute(attr);
+ }
+ stanzaBuilder.addAttribute(attributeName, attributeValue);
+ for (XMLElement element : stanza.getInnerElements()) {
+ stanzaBuilder.addPreparedElement(element);
+ }
+ return stanzaBuilder.build();
+ }
+}
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContextTest.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContextTest.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContextTest.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshBackedSessionContextTest.java Fri Apr 20 15:45:00 2012
@@ -19,11 +19,7 @@
*/
package org.apache.vysper.xmpp.extension.xep0124;
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.createControl;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
@@ -33,6 +29,7 @@ import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang.StringUtils;
import org.apache.vysper.xml.fragment.Renderer;
import org.apache.vysper.xmpp.addressing.EntityImpl;
import org.apache.vysper.xmpp.protocol.NamespaceURIs;
@@ -84,16 +81,16 @@ public class BoshBackedSessionContextTes
asyncContext.setTimeout(anyLong());
asyncContext.dispatch();
expectLastCall().atLeastOnce();
- httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest> notNull());
- asyncContext.addListener(EasyMock.<AsyncListener> anyObject());
+ httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest>notNull());
+ asyncContext.addListener(EasyMock.<AsyncListener>anyObject());
Capture<BoshResponse> captured = new Capture<BoshResponse>();
httpServletRequest.setAttribute(eq("response"), EasyMock.<BoshResponse> capture(captured));
mocksControl.replay();
BoshBackedSessionContext boshBackedSessionContext = new BoshBackedSessionContext(boshHandler, serverRuntimeContext, inactivityChecker);
- Stanza body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH).build();
+ Stanza body = BoshStanzaUtils.EMPTY_BOSH_RESPONSE;
boshBackedSessionContext.insertRequest(new BoshRequest(httpServletRequest, body, 1L));
- boshBackedSessionContext.writeBOSHResponse(body);
+ boshBackedSessionContext.writeBoshResponse(body);
mocksControl.verify();
BoshResponse boshResponse = captured.getValue();
@@ -121,7 +118,7 @@ public class BoshBackedSessionContextTes
@Test
public void testRequestExpired() throws IOException {
- Stanza emtpyStanza = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH).build();
+ Stanza emtpyStanza = BoshStanzaUtils.EMPTY_BOSH_RESPONSE;
// addRequest
HttpServletRequest httpServletRequest = mocksControl.createMock(HttpServletRequest.class);
@@ -129,7 +126,7 @@ public class BoshBackedSessionContextTes
expect(httpServletRequest.startAsync()).andReturn(asyncContext).atLeastOnce();
expect(httpServletRequest.getAsyncContext()).andReturn(asyncContext).atLeastOnce();
asyncContext.setTimeout(anyLong());
- httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest> notNull());
+ httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest>notNull());
expect(asyncContext.getRequest()).andReturn(httpServletRequest).atLeastOnce();
asyncContext.dispatch();
@@ -166,6 +163,7 @@ public class BoshBackedSessionContextTes
HttpServletRequest httpServletRequest2 = mocksControl.createMock(HttpServletRequest.class);
AsyncContext asyncContext1 = mocksControl.createMock(AsyncContext.class);
AsyncContext asyncContext2 = mocksControl.createMock(AsyncContext.class);
+ BoshStanzaUtils boshStanzaUtils = mocksControl.createMock(BoshStanzaUtils.class);
expect(httpServletRequest1.startAsync()).andReturn(asyncContext1).atLeastOnce();
expect(httpServletRequest1.getAsyncContext()).andReturn(asyncContext1).atLeastOnce();
@@ -179,16 +177,15 @@ public class BoshBackedSessionContextTes
asyncContext2.setTimeout(anyLong());
Capture<BoshRequest> br2 = new Capture<BoshRequest>();
- httpServletRequest2.setAttribute(eq("request"), EasyMock.<BoshRequest> capture(br2));
+ httpServletRequest2.setAttribute(eq("request"), EasyMock.<BoshRequest>capture(br2));
asyncContext1.addListener(EasyMock.<AsyncListener> anyObject());
- asyncContext2.addListener(EasyMock.<AsyncListener> anyObject());
+ asyncContext2.addListener(EasyMock.<AsyncListener>anyObject());
asyncContext1.dispatch();
expectLastCall().atLeastOnce();
- Stanza body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH).build();
- expect(boshHandler.addAttribute(eq(body), eq("ack"), Long.toString(EasyMock.anyLong()))).andReturn(body);
+ Stanza body = BoshStanzaUtils.EMPTY_BOSH_RESPONSE;
// write0
Capture<BoshResponse> captured = new Capture<BoshResponse>();
@@ -199,15 +196,18 @@ public class BoshBackedSessionContextTes
boshBackedSessionContext.setHold(2);
// consecutive writes with RID 1 and 2
+ long maxRID = 2L;
boshBackedSessionContext.insertRequest(new BoshRequest(httpServletRequest1, body, 1L));
- boshBackedSessionContext.insertRequest(new BoshRequest(httpServletRequest2, body, 2L));
- boshBackedSessionContext.writeBOSHResponse(body);
+ boshBackedSessionContext.insertRequest(new BoshRequest(httpServletRequest2, body, maxRID));
+ boshBackedSessionContext.writeBoshResponse(body);
mocksControl.verify();
assertEquals(httpServletRequest1, br1.getValue().getHttpServletRequest());
assertEquals(httpServletRequest2, br2.getValue().getHttpServletRequest());
- assertEquals(new Renderer(body).getComplete(), new String(captured.getValue().getContent()));
+ // expect ack for newest/largest RID
+ final Stanza ackedResponse = BoshStanzaUtils.addAttribute(body, "ack", Long.toString(maxRID));
+ assertEquals(new Renderer(ackedResponse).getComplete(), new String(captured.getValue().getContent()));
assertEquals(BoshServlet.XML_CONTENT_TYPE, captured.getValue().getContentType());
}
@@ -218,29 +218,30 @@ public class BoshBackedSessionContextTes
expect(httpServletRequest.startAsync()).andReturn(asyncContext).atLeastOnce();
expect(httpServletRequest.getAsyncContext()).andReturn(asyncContext).atLeastOnce();
asyncContext.setTimeout(anyLong());
- httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest> notNull());
+ httpServletRequest.setAttribute(eq("request"), EasyMock.<BoshRequest>notNull());
- asyncContext.addListener(EasyMock.<AsyncListener> anyObject());
+ asyncContext.addListener(EasyMock.<AsyncListener>anyObject());
asyncContext.dispatch();
expectLastCall().atLeastOnce();
- Stanza body1 = mocksControl.createMock(Stanza.class);
- Stanza body2 = mocksControl.createMock(Stanza.class);
- Stanza body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH).build();
- expect(boshHandler.mergeResponses(EasyMock.<Stanza> anyObject(), EasyMock.<Stanza> anyObject()))
- .andReturn(body);
- expectLastCall().times(2);
-
- httpServletRequest.setAttribute(eq("response"), EasyMock.<BoshResponse> anyObject());
+ Stanza body = BoshStanzaUtils.createBoshStanzaBuilder().startInnerElement("presence").endInnerElement().build();
+
+ Capture<BoshResponse> captured = new Capture<BoshResponse>();
+ httpServletRequest.setAttribute(eq("response"), EasyMock.<BoshResponse> capture(captured));
mocksControl.replay();
BoshBackedSessionContext boshBackedSessionContext = new BoshBackedSessionContext(boshHandler,
serverRuntimeContext, inactivityChecker);
- boshBackedSessionContext.writeBOSHResponse(body1);
- boshBackedSessionContext.writeBOSHResponse(body2);
+ boshBackedSessionContext.writeBoshResponse(body); // queued for merging
+ boshBackedSessionContext.writeBoshResponse(body); // queued for merging
+ boshBackedSessionContext.writeBoshResponse(body); // queued for merging
boshBackedSessionContext.insertRequest(new BoshRequest(httpServletRequest, body, 1L));
+
mocksControl.verify();
+
+ final String mergedAllBodiesStanza = new String(captured.getValue().getContent());
+ assertEquals(3, StringUtils.countMatches(mergedAllBodiesStanza, "<presence"));
}
}
Modified: mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandlerTest.java
URL: http://svn.apache.org/viewvc/mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandlerTest.java?rev=1328417&r1=1328416&r2=1328417&view=diff
==============================================================================
--- mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandlerTest.java (original)
+++ mina/vysper/trunk/server/extensions/xep0124-xep0206-bosh/src/test/java/org/apache/vysper/xmpp/extension/xep0124/BoshHandlerTest.java Fri Apr 20 15:45:00 2012
@@ -27,6 +27,7 @@ import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import javax.servlet.AsyncContext;
@@ -152,7 +153,7 @@ public class BoshHandlerTest {
@Test
public void testGetEmptyResponse() {
- Stanza response = BoshHandler.EMPTY_BOSH_RESPONSE;
+ Stanza response = BoshStanzaUtils.EMPTY_BOSH_RESPONSE;
assertNotNull(response);
assertEquals("body", response.getName());
assertEquals(NamespaceURIs.XEP0124_BOSH, response.getNamespaceURI());
@@ -164,7 +165,7 @@ public class BoshHandlerTest {
public void testWrapStanza() {
StanzaBuilder stanzaBuilder = new StanzaBuilder("iq", NamespaceURIs.JABBER_CLIENT);
Stanza stanza = stanzaBuilder.build();
- Stanza body = boshHandler.wrapStanza(stanza);
+ Stanza body = BoshStanzaUtils.wrapStanza(stanza);
assertNotNull(body);
assertEquals("body", body.getName());
assertEquals(NamespaceURIs.XEP0124_BOSH, body.getNamespaceURI());
@@ -177,9 +178,9 @@ public class BoshHandlerTest {
public void testMergeResponses() {
Stanza response1 = createPingStanzaResponse("vysper.org", "user1@vysper.org/resource", "100");
Stanza response2 = createPingStanzaResponse("vysper.org", "user1@vysper.org/resource", "101");
- assertEquals(response1, boshHandler.mergeResponses(response1, null));
- assertEquals(response1, boshHandler.mergeResponses(null, response1));
- Stanza merged = boshHandler.mergeResponses(response1, response2);
+ assertEquals(response1, BoshStanzaUtils.mergeResponses(Arrays.asList(response1, null)));
+ assertEquals(response1, BoshStanzaUtils.mergeResponses(Arrays.asList(null, response1)));
+ Stanza merged = BoshStanzaUtils.mergeResponses(Arrays.asList(response1, response2));
assertNotNull(merged);
assertEquals("body", merged.getName());
assertEquals(NamespaceURIs.XEP0124_BOSH, merged.getNamespaceURI());
@@ -189,7 +190,7 @@ public class BoshHandlerTest {
}
private Stanza createSessionRequest() {
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ StanzaBuilder body = BoshStanzaUtils.createBoshStanzaBuilder();
body.addAttribute("rid", "100");
body.addAttribute("to", "vysper.org");
body.addAttribute(NamespaceURIs.XML, "lang", "en");
@@ -201,7 +202,7 @@ public class BoshHandlerTest {
}
private Stanza createSaslRequest() {
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ StanzaBuilder body = BoshStanzaUtils.createBoshStanzaBuilder();
body.addAttribute("rid", "101");
body.addAttribute("sid", "200");
body.startInnerElement("auth", NamespaceURIs.URN_IETF_PARAMS_XML_NS_XMPP_SASL)
@@ -210,7 +211,7 @@ public class BoshHandlerTest {
}
private static Stanza createPingStanzaResponse(String from, String to, String id) {
- StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
+ StanzaBuilder body = BoshStanzaUtils.createBoshStanzaBuilder();
body.startInnerElement("iq", NamespaceURIs.JABBER_CLIENT).addAttribute("from", from)
.addAttribute("type", "result").addAttribute("to", to).addAttribute("id", id);
body.endInnerElement();