diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpBindServlet.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpBindServlet.java index 25c101f..92332fb 100644 --- a/src/java/org/jivesoftware/multiplexer/net/http/HttpBindServlet.java +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpBindServlet.java @@ -16,6 +16,7 @@ import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; +import org.mortbay.util.ajax.ContinuationSupport; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -47,6 +48,9 @@ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(isContinuation(request, response)) { + return; + } Document document; try { document = createDocument(request); @@ -76,6 +80,17 @@ } } + private boolean isContinuation(HttpServletRequest request, HttpServletResponse response) + throws IOException + { + HttpConnection connection = (HttpConnection) request.getAttribute("request-connection"); + if(connection == null) { + return false; + } + respond(response, connection); + return true; + } + private void handleSessionRequest(String sid, HttpServletRequest request, HttpServletResponse response, Element rootNode) throws IOException @@ -94,7 +109,10 @@ return; } synchronized(session) { - sessionManager.forwardRequest(rid, session, rootNode); + HttpConnection connection = sessionManager.forwardRequest(rid, session, rootNode); + connection.setContinuation(ContinuationSupport.getContinuation(request, connection)); + request.setAttribute("request-connection", connection); + respond(response, connection); } } @@ -115,15 +133,26 @@ private void respond(HttpServletResponse response, HttpConnection connection) throws IOException { + byte [] content; + try { + content = connection.getDeliverable().getBytes("utf-8"); + } + catch (HttpBindTimeoutException e) { + content = createEmptyBody().getBytes("utf-8"); + } + response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/xml"); response.setCharacterEncoding("utf-8"); - byte [] content = connection.getDeliverable().getBytes(); response.setContentLength(content.length); response.getOutputStream().write(content); } + private String createEmptyBody() { + return ""; + } + private long getLongAttribue(String value, long defaultValue) { if(value == null || "".equals(value)) { return defaultValue; @@ -139,15 +168,10 @@ private Document createDocument(HttpServletRequest request) throws DocumentException, IOException, XmlPullParserException { - Document document = (Document) request.getAttribute("xml-document"); - if (document == null) { - // Reader is associated with a new XMPPPacketReader - XMPPPacketReader reader = new XMPPPacketReader(); - reader.setXPPFactory(factory); + // Reader is associated with a new XMPPPacketReader + XMPPPacketReader reader = new XMPPPacketReader(); + reader.setXPPFactory(factory); - document = reader.read(request.getInputStream()); - request.setAttribute("xml-document", document); - } - return document; + return reader.read(request.getInputStream()); } } diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpBindTimeoutException.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpBindTimeoutException.java new file mode 100644 index 0000000..5e2aa1b --- /dev/null +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpBindTimeoutException.java @@ -0,0 +1,25 @@ +/** + * $RCSfile: $ + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ +package org.jivesoftware.multiplexer.net.http; + +/** + * An exception which indicates that the maximum waiting time for a client response has been + * surpassed and an empty response should be returned to the requesting client. + * + * @author Alexander Wenckus + */ +class HttpBindTimeoutException extends Exception { + public HttpBindTimeoutException(String message) { + super(message); + } + + public HttpBindTimeoutException() { + super(); + } +} diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpConnection.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpConnection.java index c291051..b0f58ea 100644 --- a/src/java/org/jivesoftware/multiplexer/net/http/HttpConnection.java +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpConnection.java @@ -9,6 +9,7 @@ package org.jivesoftware.multiplexer.net.http; import org.jivesoftware.multiplexer.Connection; +import org.mortbay.util.ajax.Continuation; /** @@ -19,6 +20,8 @@ private long requestId; private String body; private HttpSession session; + private Continuation continuation; + private boolean isClosed; public HttpConnection(long requestID) { this.requestId = requestID; @@ -28,28 +31,80 @@ return false; } + /** + * The connection should be closed without delivering a stanza to the requestor. + */ public void close() { - } + if(isClosed) { + return; + } - public void systemShutdown() { + try { + deliverBody(null); + } + catch (HttpConnectionClosedException e) { + /* Shouldn't happen */ + } } public boolean isClosed() { - return false; + return isClosed; } public boolean isSecure() { return false; } - public void deliverBody(String body) { + public void deliverBody(String body) throws HttpConnectionClosedException { + // We only want to use this function once so we will close it when the body is delivered. + if(isClosed) { + throw new HttpConnectionClosedException("The http connection is no longer " + + "available to deliver content"); + } + else { + isClosed = true; + } + + if (continuation != null) { + continuation.setObject(body); + continuation.resume(); + return; + } this.body = body; } - public String getDeliverable() { + /** + * A call that will cause a wait, or in the case of Jetty the thread to be freed, if there + * is no deliverable currently available. Once the deliverable becomes available it is returned. + * + * @return the deliverable to send to the client + * @throws HttpBindTimeoutException to indicate that the maximum wait time requested by the + * client + * has been surpassed and an empty response should be returned. + */ + public String getDeliverable() throws HttpBindTimeoutException { + if (body == null && continuation != null) { + body = waitForDeliverable(); + } + else if (body == null && continuation == null) { + throw new IllegalStateException("Continuation not set, cannot wait for deliverable."); + } return body; } + private String waitForDeliverable() throws HttpBindTimeoutException { + if(continuation.suspend(session.getWait() * 1000)) { + String deliverable = (String) continuation.getObject(); + // This will occur when the hold attribute of a session has been exceded. + if(deliverable == null) { + throw new HttpBindTimeoutException(); + } + return deliverable; + } + throw new HttpBindTimeoutException("Request " + requestId + " exceded response time from " + + "server of " + session.getWait() + " seconds."); + } + public boolean isCompressed() { return false; } @@ -83,4 +138,8 @@ public HttpSession getSession() { return session; } + + void setContinuation(Continuation continuation) { + this.continuation = continuation; + } } diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpConnectionClosedException.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpConnectionClosedException.java new file mode 100644 index 0000000..275da31 --- /dev/null +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpConnectionClosedException.java @@ -0,0 +1,18 @@ +/** + * $RCSfile: $ + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ +package org.jivesoftware.multiplexer.net.http; + +/** + * + */ +public class HttpConnectionClosedException extends Exception { + public HttpConnectionClosedException(String message) { + super(message); + } +} diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpSession.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpSession.java index 67b278c..8ebfa7d 100644 --- a/src/java/org/jivesoftware/multiplexer/net/http/HttpSession.java +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpSession.java @@ -10,27 +10,42 @@ import org.jivesoftware.multiplexer.*; import org.dom4j.Element; +import org.dom4j.DocumentHelper; -import java.util.Queue; -import java.util.LinkedList; +import java.util.*; /** * */ public class HttpSession extends Session { private int wait; - private int hold; + private int hold = -1000; private String language; private final Queue connectionQueue = new LinkedList(); - private String stanza; + private final List pendingElements = new ArrayList(); public HttpSession(String serverName, String streamID) { super(serverName, null, streamID); } - void addConnection(HttpConnection connection) { + void addConnection(HttpConnection connection, boolean isPoll) { + if(connection == null) { + throw new IllegalArgumentException("Connection cannot be null."); + } + connection.setSession(this); + if(pendingElements.size() > 0) { + createDeliverable(pendingElements); + pendingElements.clear(); + return; + } + // With this connection we need to check if we will have too many connections open, closing + // any extras. + while(hold > 0 && connectionQueue.size() >= hold) { + HttpConnection toClose = connectionQueue.remove(); + toClose.close(); + } connectionQueue.offer(connection); } @@ -45,7 +60,31 @@ } public synchronized void deliver(Element stanza) { - this.stanza = stanza.asXML(); + String deliverable = createDeliverable(Arrays.asList(stanza)); + boolean delivered = false; + while(!delivered && connectionQueue.size() > 0) { + HttpConnection connection = connectionQueue.remove(); + try { + connection.deliverBody(deliverable); + delivered = true; + } + catch (HttpConnectionClosedException e) { + /* Connection was closed, try the next one */ + } + } + + if(!delivered) { + pendingElements.add(stanza); + } + } + + private String createDeliverable(Collection elements) { + Element body = DocumentHelper.createElement("body"); + body.addAttribute("xmlns", "http://jabber.org/protocol/httpbind"); + for(Element child : elements) { + body.add(child); + } + return body.asXML(); } /** @@ -100,4 +139,15 @@ public void setLanaguage(String language) { this.language = language; } + + public String getLanguage() { + return language; + } + + /** + * Sets the max interval within which a client can send polling requests. If more than one + * @param pollingInterval + */ + public void setMaxPollingInterval(int pollingInterval) { + } } diff --git a/src/java/org/jivesoftware/multiplexer/net/http/HttpSessionManager.java b/src/java/org/jivesoftware/multiplexer/net/http/HttpSessionManager.java index 1180f74..b31c155 100644 --- a/src/java/org/jivesoftware/multiplexer/net/http/HttpSessionManager.java +++ b/src/java/org/jivesoftware/multiplexer/net/http/HttpSessionManager.java @@ -91,9 +91,15 @@ HttpSession session = createSession(serverName); session.setWait(wait); session.setHold(hold); + session.setMaxPollingInterval(pollingInterval); // Store language and version information in the connection. session.setLanaguage(language); - connection.deliverBody(createSessionCreationResponse(session)); + try { + connection.deliverBody(createSessionCreationResponse(session)); + } + catch (HttpConnectionClosedException e) { + /* This won't happen here. */ + } return session; } @@ -145,12 +151,13 @@ } public HttpConnection forwardRequest(long rid, HttpSession session, Element rootNode) { - HttpConnection connection = new HttpConnection(rid); - session.addConnection(connection); - //noinspection unchecked List elements = rootNode.elements(); - for(Element packet : elements) { + boolean isPoll = elements.size() <= 0; + HttpConnection connection = new HttpConnection(rid); + session.addConnection(connection, isPoll); + + for (Element packet : elements) { serverSurrogate.send(packet, session.getStreamID()); }