Commit dc2c44c2 authored by Alex Wenckus's avatar Alex Wenckus Committed by alex

Incorporated feedback from code review.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@6687 b35dd754-fafc-0310-a699-88a17e54d16e
parent 82bccaca
......@@ -32,7 +32,10 @@ import java.io.IOException;
import java.net.InetAddress;
/**
* Handles requests to the HTTP Bind service.
* Servlet which handles requests to the HTTP binding service. It determines if there is currently
* an {@link HttpSession} related to the connection or if one needs to be created and then passes
* it off to the {@link HttpBindManager} for processing of the client request and formulating of
* the response.
*
* @author Alexander Wenckus
*/
......@@ -50,6 +53,8 @@ public class HttpBindServlet extends HttpServlet {
}
}
private ThreadLocal<XMPPPacketReader> localReader = new ThreadLocal<XMPPPacketReader>();
public HttpBindServlet() {
}
......@@ -66,8 +71,7 @@ public class HttpBindServlet extends HttpServlet {
sessionManager.stop();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
if (isContinuation(request, response)) {
......@@ -196,7 +200,7 @@ public class HttpBindServlet extends HttpServlet {
{
byte[] content;
try {
content = connection.getDeliverable().getBytes("utf-8");
content = connection.getResponse().getBytes("utf-8");
}
catch (HttpBindTimeoutException e) {
content = createEmptyBody().getBytes("utf-8");
......@@ -235,9 +239,12 @@ public class HttpBindServlet extends HttpServlet {
private Document createDocument(HttpServletRequest request) throws
DocumentException, IOException, XmlPullParserException {
// Reader is associated with a new XMPPPacketReader
XMPPPacketReader reader = new XMPPPacketReader();
XMPPPacketReader reader = localReader.get();
if (reader == null) {
reader = new XMPPPacketReader();
reader.setXPPFactory(factory);
localReader.set(reader);
}
return reader.read("utf-8", request.getInputStream());
}
}
......@@ -3,7 +3,7 @@
* $Revision: $
* $Date: $
*
* Copyright (C) 2006 Jive Software. All rights reserved.
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
......@@ -11,17 +11,16 @@
package org.jivesoftware.wildfire.http;
import org.jivesoftware.wildfire.Connection;
import org.mortbay.util.ajax.Continuation;
/**
* A connection to a client. The client will wait on getDeliverable() until the server forwards a
* message to it or the wait time on the session timesout.
* Represents one HTTP connection with a client using the HTTP Binding service. The client will wait
* on {@link #getResponse()} until the server forwards a message to it or the wait time on the
* session timesout.
*
* @author Alexander Wenckus
*/
public class HttpConnection {
private Connection.CompressionPolicy compressionPolicy;
private long requestId;
private String body;
private HttpSession session;
......@@ -29,15 +28,17 @@ public class HttpConnection {
private boolean isClosed;
private boolean isSecure = false;
/**
* Constructs an HTTP Connection.
*
* @param requestId the ID which uniquely identifies this request.
* @param isSecure true if this connection is using HTTPS
*/
public HttpConnection(long requestId, boolean isSecure) {
this.requestId = requestId;
this.isSecure = isSecure;
}
public boolean validate() {
return false;
}
/**
* The connection should be closed without delivering a stanza to the requestor.
*/
......@@ -54,10 +55,21 @@ public class HttpConnection {
}
}
/**
* Returns true if this connection has been closed, either a response was delivered to the
* client or the server closed the connection aburbtly.
*
* @return true if this connection has been closed.
*/
public boolean isClosed() {
return isClosed;
}
/**
* Returns true if this connection is using HTTPS.
*
* @return true if this connection is using HTTPS.
*/
public boolean isSecure() {
return isSecure;
}
......@@ -93,16 +105,16 @@ public class HttpConnection {
/**
* 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.
* deliverable currently available. Once the response 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 {
public String getResponse() throws HttpBindTimeoutException {
if (body == null && continuation != null) {
try {
body = waitForDeliverable();
body = waitForResponse();
}
catch (HttpBindTimeoutException e) {
this.isClosed = true;
......@@ -115,31 +127,11 @@ public class HttpConnection {
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;
}
public Connection.CompressionPolicy getCompressionPolicy() {
return compressionPolicy;
}
public void setCompressionPolicy(Connection.CompressionPolicy compressionPolicy) {
this.compressionPolicy = compressionPolicy;
}
/**
* Returns the ID which uniquely identifies this connection.
*
* @return the ID which uniquely identifies this connection.
*/
public long getRequestId() {
return requestId;
}
......@@ -165,4 +157,17 @@ public class HttpConnection {
void setContinuation(Continuation continuation) {
this.continuation = continuation;
}
private String waitForResponse() 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.");
}
}
......@@ -32,10 +32,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
/**
* A session represents a serious of interactions with an XMPP client sending packets using the HTTP
* Binding protocol specified in
* <a href="http://www.xmpp.org/extensions/xep-0124.html">XEP-0124</a>. A session can have several
* client connections open simultaneously while awaiting packets bound for the client from the
* server.
* Binding protocol specified in <a href="http://www.xmpp.org/extensions/xep-0124.html">XEP-0124</a>.
* A session can have several client connections open simultaneously while awaiting packets bound
* for the client from the server.
*
* @author Alexander Wenckus
*/
......@@ -63,119 +62,16 @@ public class HttpSession extends ClientSession {
this.lastRequestID = rid;
}
HttpConnection createConnection(long rid, boolean isPoll, boolean isSecure)
throws HttpConnectionClosedException, HttpBindException
{
HttpConnection connection = new HttpConnection(rid, isSecure);
if(rid <= lastRequestID) {
Deliverable deliverable = retrieveDeliverable(rid);
if (deliverable == null) {
Log.warn("Deliverable unavailable for " + rid);
throw new HttpBindException("Unexpected RID Error", true, 404);
}
connection.deliverBody(deliverable.getDeliverable());
return connection;
}
else if (rid > (lastRequestID + hold)) {
// TODO handle the case of greater RID which basically has it wait
Log.warn("Request " + rid + " > " + (lastRequestID + hold) + ", ending session.");
throw new HttpBindException("Unexpected RID Error", true, 404);
}
addConnection(connection, isPoll);
return connection;
}
private Deliverable retrieveDeliverable(long rid) throws HttpBindException {
for(Deliverable delivered : sentElements) {
if(delivered.getRequestID() == rid) {
return delivered;
}
}
return null;
}
private void addConnection(HttpConnection connection, boolean isPoll) throws HttpBindException,
HttpConnectionClosedException
{
if(connection == null) {
throw new IllegalArgumentException("Connection cannot be null.");
}
if(isPoll) {
checkPollingInterval();
}
if(isSecure && !connection.isSecure()) {
throw new HttpBindException("Session was started from secure connection, all " +
"connections on this session must be secured.", false, 403);
}
connection.setSession(this);
// We aren't supposed to hold connections open or we already have some packets waiting
// to be sent to the client.
if (hold <= 0 || pendingElements.size() > 0) {
String deliverable = createDeliverable(pendingElements);
try {
fireConnectionOpened(connection);
deliver(connection, deliverable);
fireConnectionClosed(connection);
pendingElements.clear();
}
catch (HttpConnectionClosedException he) {
throw he;
}
}
else {
// With this connection we need to check if we will have too many connections open,
// closing any extras.
while (connectionQueue.size() >= hold) {
HttpConnection toClose = connectionQueue.remove();
toClose.close();
fireConnectionClosed(toClose);
}
connectionQueue.offer(connection);
fireConnectionOpened(connection);
}
lastRequestID = connection.getRequestId();
}
private void deliver(HttpConnection connection, String deliverable)
throws HttpConnectionClosedException
{
connection.deliverBody(deliverable);
Deliverable delivered = new Deliverable(deliverable);
delivered.setRequestID(connection.getRequestId());
while(sentElements.size() > hold) {
sentElements.remove(0);
}
sentElements.add(delivered);
}
private void fireConnectionOpened(HttpConnection connection) {
lastActivity = System.currentTimeMillis();
for(SessionListener listener : listeners) {
listener.connectionOpened(this, connection);
}
}
private void checkPollingInterval() throws HttpBindException {
long time = System.currentTimeMillis();
if(((time - lastPoll) / 1000) < maxPollingInterval) {
throw new HttpBindException("Too frequent polling minimum interval is "
+ maxPollingInterval + ", current interval " + ((lastPoll - time) / 1000),
true, 403);
}
lastPoll = time;
}
/**
* Returns the stream features which are available for this session.
*
* @return the stream features which are available for this session.
*/
public Collection<Element> getAvailableStreamFeaturesElements() {
List<Element> elements = new ArrayList<Element>();
Element sasl = SASLAuthentication.getSASLMechanismsElement(this);
if(sasl != null) {
if (sasl != null) {
elements.add(sasl);
}
......@@ -201,103 +97,37 @@ public class HttpSession extends ClientSession {
public String getAvailableStreamFeatures() {
StringBuilder sb = new StringBuilder(200);
for(Element element : getAvailableStreamFeaturesElements()) {
for (Element element : getAvailableStreamFeaturesElements()) {
sb.append(element.asXML());
}
return sb.toString();
}
/**
* Closes the session. After a session has been closed it will no longer accept new connections
* on the session ID.
*/
public synchronized void close() {
conn.close();
}
private synchronized void closeConnection() {
if(isClosed) {
if (isClosed) {
return;
}
isClosed = true;
if(pendingElements.size() > 0) {
failDelivery();
}
while (connectionQueue.size() > 0) {
HttpConnection toClose = connectionQueue.remove();
toClose.close();
fireConnectionClosed(toClose);
}
for(SessionListener listener : listeners) {
listener.sessionClosed(this);
}
this.listeners.clear();
}
private void failDelivery() {
for(Deliverable deliverable : pendingElements) {
Packet packet = deliverable.packet;
if (packet != null && packet instanceof Message) {
XMPPServer.getInstance().getOfflineMessageStrategy()
.storeOffline((Message) packet);
}
}
pendingElements.clear();
conn.close();
}
/**
* Returns true if this session has been closed and no longer activley accepting connections.
*
* @return true if this session has been closed and no longer activley accepting connections.
*/
public synchronized boolean isClosed() {
return isClosed;
}
private synchronized void deliver(String text) {
deliver(new Deliverable(text));
}
private synchronized void deliver(Packet stanza) {
deliver(new Deliverable(stanza));
}
private void deliver(Deliverable stanza) {
String deliverable = createDeliverable(Arrays.asList(stanza));
boolean delivered = false;
while(!delivered && connectionQueue.size() > 0) {
HttpConnection connection = connectionQueue.remove();
try {
deliver(connection, deliverable);
delivered = true;
fireConnectionClosed(connection);
}
catch (HttpConnectionClosedException e) {
/* Connection was closed, try the next one */
}
}
if(!delivered) {
pendingElements.add(stanza);
}
}
private void fireConnectionClosed(HttpConnection connection) {
lastActivity = System.currentTimeMillis();
for(SessionListener listener : listeners) {
listener.connectionClosed(this, connection);
}
}
private String createDeliverable(Collection<Deliverable> elements) {
StringBuilder builder = new StringBuilder();
builder.append("<body xmlns='" + "http://jabber.org/protocol/httpbind" + "'>");
for(Deliverable child : elements) {
builder.append(child.getDeliverable());
}
builder.append("</body>");
return builder.toString();
}
/**
* Specifies the longest time (in seconds) that the connection manager is allowed
* to wait before responding to any request during the session. This enables the client to
* prevent its TCP connection from expiring due to inactivity, as well as to limit the delay
* before it discovers any network failure.
* Specifies the longest time (in seconds) that the connection manager is allowed to wait before
* responding to any request during the session. This enables the client to prevent its TCP
* connection from expiring due to inactivity, as well as to limit the delay before it discovers
* any network failure.
*
* @param wait the longest time it is permissible to wait for a response.
*/
......@@ -306,10 +136,10 @@ public class HttpSession extends ClientSession {
}
/**
* Specifies the longest time (in seconds) that the connection manager is allowed
* to wait before responding to any request during the session. This enables the client to
* prevent its TCP connection from expiring due to inactivity, as well as to limit the delay
* before it discovers any network failure.
* Specifies the longest time (in seconds) that the connection manager is allowed to wait before
* responding to any request during the session. This enables the client to prevent its TCP
* connection from expiring due to inactivity, as well as to limit the delay before it discovers
* any network failure.
*
* @return the longest time it is permissible to wait for a response.
*/
......@@ -318,23 +148,22 @@ public class HttpSession extends ClientSession {
}
/**
* Specifies the maximum number of requests the connection manager is allowed
* to keep waiting at any one time during the session. (For example, if a constrained client
* is unable to keep open more than two HTTP connections to the same HTTP server simultaneously,
* then it SHOULD specify a value of "1".)
* Specifies the maximum number of requests the connection manager is allowed to keep waiting at
* any one time during the session. (For example, if a constrained client is unable to keep open
* more than two HTTP connections to the same HTTP server simultaneously, then it SHOULD specify
* a value of "1".)
*
* @param hold the maximum number of simultaneous waiting requests.
*
*/
public void setHold(int hold) {
this.hold = hold;
}
/**
* Specifies the maximum number of requests the connection manager is allowed
* to keep waiting at any one time during the session. (For example, if a constrained client
* is unable to keep open more than two HTTP connections to the same HTTP server simultaneously,
* then it SHOULD specify a value of "1".)
* Specifies the maximum number of requests the connection manager is allowed to keep waiting at
* any one time during the session. (For example, if a constrained client is unable to keep open
* more than two HTTP connections to the same HTTP server simultaneously, then it SHOULD specify
* a value of "1".)
*
* @return the maximum number of simultaneous waiting requests
*/
......@@ -342,10 +171,20 @@ public class HttpSession extends ClientSession {
return hold;
}
/**
* Sets the language this session is using.
*
* @param language the language this session is using.
*/
public void setLanaguage(String language) {
this.language = language;
}
/**
* Returns the language this session is using.
*
* @return the language this session is using.
*/
public String getLanguage() {
return language;
}
......@@ -375,71 +214,306 @@ public class HttpSession extends ClientSession {
/**
* The max number of requests it is permissable for this session to have open at any one time.
*
* @param maxRequests The max number of requests it is permissable for this session to have
* open at any one time.
* @param maxRequests The max number of requests it is permissable for this session to have open
* at any one time.
*/
public void setMaxRequests(int maxRequests) {
this.maxRequests = maxRequests;
}
/**
* Returns the max number of requests it is permissable for this session to have open at any
* one time.
* Returns the max number of requests it is permissable for this session to have open at any one
* time.
*
* @return the max number of requests it is permissable for this session to have open at any
* one time.
* @return the max number of requests it is permissable for this session to have open at any one
* time.
*/
public int getMaxRequests() {
return this.maxRequests;
}
/**
* Sets whether the initial request on the session was secure.
* Returns true if all connections on this session should be secured, and false if they should
* not.
*
* @param isSecure true if the initial request was secure and false if it wasn't.
* @return true if all connections on this session should be secured, and false if they should
* not.
*/
protected void setSecure(boolean isSecure) {
this.isSecure = isSecure;
public boolean isSecure() {
return isSecure;
}
/**
* Returns true if all connections on this session should be secured, and false if
* they should not.
* Adds a {@link org.jivesoftware.wildfire.http.SessionListener} to this session. The listener
* will be notified of changes to the session.
*
* @return true if all connections on this session should be secured, and false if
* they should not.
* @param listener the listener which is being added to the session.
*/
public boolean isSecure() {
return isSecure;
}
public void addSessionCloseListener(SessionListener listener) {
listeners.add(listener);
}
/**
* Removes a {@link org.jivesoftware.wildfire.http.SessionListener} from this session. The
* listener will no longer be updated when an event occurs on the session.
*
* @param listener the session listener that is to be removed.
*/
public void removeSessionCloseListener(SessionListener listener) {
listeners.remove(listener);
}
/**
* Sets the time, in seconds, after which this session will be considered inactive and be be
* terminated.
*
* @param inactivityTimeout the time, in seconds, after which this session will be considered
* inactive and be terminated.
*/
public void setInactivityTimeout(int inactivityTimeout) {
this.inactivityTimeout = inactivityTimeout;
}
/**
* Returns the time, in seconds, after which this session will be considered inactive and
* terminated.
*
* @return the time, in seconds, after which this session will be considered inactive and
* terminated.
*/
public int getInactivityTimeout() {
return inactivityTimeout;
}
/**
* Returns the number of connections currently awaiting a response on this session.
*
* @return the number of connections currently awaiting a response on this session.
*/
public int getConnectionCount() {
return connectionQueue.size();
}
/**
* Returns the time in milliseconds since the epoch that this session was last active. Activity
* is a request was either made or responded to.
*
* @return the time in milliseconds since the epoch that this session was last active.
*/
public synchronized long getLastActivity() {
return lastActivity;
}
/**
* A virtual server connection relates to a http session which its self can relate to many
* http connections.
* Sets whether the initial request on the session was secure.
*
* @param isSecure true if the initial request was secure and false if it wasn't.
*/
protected void setSecure(boolean isSecure) {
this.isSecure = isSecure;
}
/**
* Creates a new connection on this session. If a response is currently available for this
* session the connection is responded to immediately, otherwise it is queued awaiting a
* response.
*
* @param rid the request id related to the connection.
* @param isPoll true if the request was a poll, no packets were sent along with the request.
* @param isSecure true if the connection was secured using HTTPS.
* @return the created {@link org.jivesoftware.wildfire.http.HttpConnection} which represents
* the connection.
* @throws HttpConnectionClosedException if the connection was closed before a response could
* be delivered.
* @throws HttpBindException if the connection has violated a facet of the HTTP binding
* protocol.
*/
HttpConnection createConnection(long rid, boolean isPoll, boolean isSecure)
throws HttpConnectionClosedException, HttpBindException {
HttpConnection connection = new HttpConnection(rid, isSecure);
if (rid <= lastRequestID) {
Deliverable deliverable = retrieveDeliverable(rid);
if (deliverable == null) {
Log.warn("Deliverable unavailable for " + rid);
throw new HttpBindException("Unexpected RID Error", true, 404);
}
connection.deliverBody(deliverable.getDeliverable());
return connection;
}
else if (rid > (lastRequestID + hold + 1)) {
// TODO handle the case of greater RID which basically has it wait
Log.warn("Request " + rid + " > " + (lastRequestID + hold + 1) + ", ending session.");
throw new HttpBindException("Unexpected RID Error", true, 404);
}
addConnection(connection, isPoll);
return connection;
}
private Deliverable retrieveDeliverable(long rid) throws HttpBindException {
for (Deliverable delivered : sentElements) {
if (delivered.getRequestID() == rid) {
return delivered;
}
}
return null;
}
private void addConnection(HttpConnection connection, boolean isPoll) throws HttpBindException,
HttpConnectionClosedException {
if (connection == null) {
throw new IllegalArgumentException("Connection cannot be null.");
}
if (isPoll) {
checkPollingInterval();
}
if (isSecure && !connection.isSecure()) {
throw new HttpBindException("Session was started from secure connection, all " +
"connections on this session must be secured.", false, 403);
}
connection.setSession(this);
// We aren't supposed to hold connections open or we already have some packets waiting
// to be sent to the client.
if (hold <= 0 || pendingElements.size() > 0) {
String deliverable = createDeliverable(pendingElements);
try {
fireConnectionOpened(connection);
deliver(connection, deliverable);
fireConnectionClosed(connection);
pendingElements.clear();
}
catch (HttpConnectionClosedException he) {
throw he;
}
}
else {
// With this connection we need to check if we will have too many connections open,
// closing any extras.
while (connectionQueue.size() >= hold) {
HttpConnection toClose = connectionQueue.remove();
toClose.close();
fireConnectionClosed(toClose);
}
connectionQueue.offer(connection);
fireConnectionOpened(connection);
}
lastRequestID = connection.getRequestId();
}
private void deliver(HttpConnection connection, String deliverable)
throws HttpConnectionClosedException {
connection.deliverBody(deliverable);
Deliverable delivered = new Deliverable(deliverable);
delivered.setRequestID(connection.getRequestId());
while (sentElements.size() > hold) {
sentElements.remove(0);
}
sentElements.add(delivered);
}
private void fireConnectionOpened(HttpConnection connection) {
lastActivity = System.currentTimeMillis();
for (SessionListener listener : listeners) {
listener.connectionOpened(this, connection);
}
}
private void checkPollingInterval() throws HttpBindException {
long time = System.currentTimeMillis();
if (((time - lastPoll) / 1000) < maxPollingInterval) {
throw new HttpBindException("Too frequent polling minimum interval is "
+ maxPollingInterval + ", current interval " + ((lastPoll - time) / 1000),
true, 403);
}
lastPoll = time;
}
private synchronized void deliver(String text) {
deliver(new Deliverable(text));
}
private synchronized void deliver(Packet stanza) {
deliver(new Deliverable(stanza));
}
private void deliver(Deliverable stanza) {
String deliverable = createDeliverable(Arrays.asList(stanza));
boolean delivered = false;
while (!delivered && connectionQueue.size() > 0) {
HttpConnection connection = connectionQueue.remove();
try {
deliver(connection, deliverable);
delivered = true;
fireConnectionClosed(connection);
}
catch (HttpConnectionClosedException e) {
/* Connection was closed, try the next one */
}
}
if (!delivered) {
pendingElements.add(stanza);
}
}
private void fireConnectionClosed(HttpConnection connection) {
lastActivity = System.currentTimeMillis();
for (SessionListener listener : listeners) {
listener.connectionClosed(this, connection);
}
}
private String createDeliverable(Collection<Deliverable> elements) {
StringBuilder builder = new StringBuilder();
builder.append("<body xmlns='" + "http://jabber.org/protocol/httpbind" + "'>");
for (Deliverable child : elements) {
builder.append(child.getDeliverable());
}
builder.append("</body>");
return builder.toString();
}
private synchronized void closeConnection() {
if (isClosed) {
return;
}
isClosed = true;
if (pendingElements.size() > 0) {
failDelivery();
}
while (connectionQueue.size() > 0) {
HttpConnection toClose = connectionQueue.remove();
toClose.close();
fireConnectionClosed(toClose);
}
for (SessionListener listener : listeners) {
listener.sessionClosed(this);
}
this.listeners.clear();
}
private void failDelivery() {
for (Deliverable deliverable : pendingElements) {
Packet packet = deliverable.packet;
if (packet != null && packet instanceof Message) {
XMPPServer.getInstance().getOfflineMessageStrategy()
.storeOffline((Message) packet);
}
}
pendingElements.clear();
}
/**
* A virtual server connection relates to a http session which its self can relate to many http
* connections.
*/
public static class HttpVirtualConnection extends VirtualConnection {
......@@ -450,7 +524,7 @@ public class HttpSession extends ClientSession {
}
public void closeVirtualConnection() {
((HttpSession)session).closeConnection();
((HttpSession) session).closeConnection();
}
public InetAddress getInetAddress() {
......@@ -458,15 +532,15 @@ public class HttpSession extends ClientSession {
}
public void systemShutdown() {
((HttpSession)session).closeConnection();
((HttpSession) session).closeConnection();
}
public void deliver(Packet packet) throws UnauthorizedException {
((HttpSession)session).deliver(packet);
((HttpSession) session).deliver(packet);
}
public void deliverRawText(String text) {
((HttpSession)session).deliver(text);
((HttpSession) session).deliver(text);
}
}
......@@ -486,7 +560,7 @@ public class HttpSession extends ClientSession {
}
public String getDeliverable() {
if(text == null) {
if (text == null) {
return packet.toXML();
}
else {
......@@ -506,4 +580,24 @@ public class HttpSession extends ClientSession {
return (int) (o.getRequestID() - requestID);
}
}
/**
* A queue of all current connections. Connections are dealt with in order of their request
* IDs.
*/
private class ConnectionQueue {
private final HttpConnection[] connections;
private final String[] requestIds;
private int pointer = 0;
public ConnectionQueue(int size) {
this.connections = new HttpConnection[size];
this.requestIds = new String[size];
}
}
}
......@@ -32,21 +32,41 @@ import java.net.InetAddress;
* <a href="http://www.xmpp.org/extensions/xep-0124.html">XEP-0124</a>.
*/
public class HttpSessionManager {
private SessionManager sessionManager;
private Map<String, HttpSession> sessionMap = new ConcurrentHashMap<String, HttpSession>();
private TimerTask inactivityTask;
private SessionListener sessionListener = new SessionListener() {
public void connectionOpened(HttpSession session, HttpConnection connection) {
}
public void connectionClosed(HttpSession session, HttpConnection connection) {
}
public void sessionClosed(HttpSession session) {
sessionMap.remove(session.getStreamID().getID());
sessionManager.removeSession(session);
}
};
/**
* Creates a new HttpSessionManager instance.
*/
public HttpSessionManager() {
this.sessionManager = SessionManager.getInstance();
}
/**
* Starts the services used by the HttpSessionManager.
*/
public void start() {
inactivityTask = new HttpSessionReaper();
TaskEngine.getInstance().schedule(inactivityTask, 30 * JiveConstants.SECOND,
30 * JiveConstants.SECOND);
}
/**
* Stops any services and cleans up any resources used by the HttpSessionManager.
*/
public void stop() {
inactivityTask.cancel();
for(HttpSession session : sessionMap.values()) {
......@@ -173,6 +193,47 @@ public class HttpSessionManager {
return JiveGlobals.getIntProperty("xmpp.httpbind.client.idle", 30);
}
/**
* Forwards a client request, which is related to a session, to the server. A connection is
* created and queued up in the provided session. When a connection reaches the top of a queue
* any pending packets bound for the client will be forwarded to the client through the
* connection.
*
* @param rid the unique, sequential, requestID sent from the client.
* @param session the HTTP session of the client that made the request.
* @param isSecure true if the request was made over a secure channel, HTTPS, and false if it
* was not.
* @param rootNode the XML body of the request.
* @return the created HTTP connection.
* @throws HttpBindException for several reasons: if the encoding inside of an auth packet is
* not recognized by the server, or if the packet type is not recognized.
* @throws HttpConnectionClosedException if the session is no longer available.
*/
public HttpConnection forwardRequest(long rid, HttpSession session, boolean isSecure,
Element rootNode) throws HttpBindException,
HttpConnectionClosedException
{
//noinspection unchecked
List<Element> elements = rootNode.elements();
boolean isPoll = elements.size() <= 0;
HttpConnection connection = session.createConnection(rid, isPoll, isSecure);
SessionPacketRouter router = new SessionPacketRouter(session);
for (Element packet : elements) {
try {
router.route(packet);
}
catch (UnsupportedEncodingException e) {
throw new HttpBindException("Bad auth request, unknown encoding", true, 400);
}
catch (UnknownStanzaException e) {
throw new HttpBindException("Unknown packet type.", false, 400);
}
}
return connection;
}
private HttpSession createSession(long rid, InetAddress address) throws UnauthorizedException {
// Create a ClientSession for this user.
StreamID streamID = SessionManager.getInstance().nextStreamID();
......@@ -180,18 +241,7 @@ public class HttpSessionManager {
HttpSession session = sessionManager.createClientHttpSession(rid, address, streamID);
// Register that the new session is associated with the specified stream ID
sessionMap.put(streamID.getID(), session);
session.addSessionCloseListener(new SessionListener() {
public void connectionOpened(HttpSession session, HttpConnection connection) {
}
public void connectionClosed(HttpSession session, HttpConnection connection) {
}
public void sessionClosed(HttpSession session) {
sessionMap.remove(session.getStreamID().getID());
sessionManager.removeSession(session);
}
});
session.addSessionCloseListener(sessionListener);
return session;
}
......@@ -227,52 +277,12 @@ public class HttpSessionManager {
return response.asXML();
}
/**
* Forwards a client request, which is related to a session, to the server. A connection is
* created and queued up in the provided session. When a connection reaches the top of a queue
* any pending packets bound for the client will be forwarded to the client through the
* connection.
*
* @param rid the unique, sequential, requestID sent from the client.
* @param session the HTTP session of the client that made the request.
* @param isSecure true if the request was made over a secure channel, HTTPS, and false if it
* was not.
* @param rootNode the XML body of the request.
* @return the created HTTP connection.
* @throws HttpBindException for several reasons: if the encoding inside of an auth packet is
* not recognized by the server, or if the packet type is not recognized.
* @throws HttpConnectionClosedException if the session is no longer available.
*/
public HttpConnection forwardRequest(long rid, HttpSession session, boolean isSecure,
Element rootNode) throws HttpBindException,
HttpConnectionClosedException
{
//noinspection unchecked
List<Element> elements = rootNode.elements();
boolean isPoll = elements.size() <= 0;
HttpConnection connection = session.createConnection(rid, isPoll, isSecure);
SessionPacketRouter router = new SessionPacketRouter(session);
for (Element packet : elements) {
try {
router.route(packet);
}
catch (UnsupportedEncodingException e) {
throw new HttpBindException("Bad auth request, unknown encoding", true, 400);
}
catch (UnknownStanzaException e) {
throw new HttpBindException("Unknown packet type.", false, 400);
}
}
return connection;
}
private class HttpSessionReaper extends TimerTask {
public void run() {
long currentTime = System.currentTimeMillis();
for(HttpSession session : sessionMap.values()) {
long lastActive = (System.currentTimeMillis() - session.getLastActivity()) / 1000;
long lastActive = (currentTime - session.getLastActivity()) / 1000;
if (lastActive > session.getInactivityTimeout()) {
session.close();
}
......
......@@ -24,9 +24,9 @@ import java.util.zip.GZIPOutputStream;
import java.io.*;
/**
*
* Combines and serves resources, such as javascript or css files.
*/
public class JavaScriptServlet extends HttpServlet {
public class ResourceServlet extends HttpServlet {
// private static String suffix = ""; // Set to "_src" to use source version
private static long expiresOffset = 3600 * 24 * 10; // 10 days util client cache expires
private boolean debug = false;
......@@ -44,10 +44,11 @@ public class JavaScriptServlet extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) {
boolean compress = false;
boolean javascript = request.getRequestURI().endsWith("scripts/");
if (!disableCompression) {
if (request.getHeader("accept-encoding") != null &&
request.getHeader("accept-encoding").indexOf("gzip") != -1)
{
request.getHeader("accept-encoding").indexOf("gzip") != -1) {
compress = true;
}
else if (request.getHeader("---------------") != null) {
......@@ -56,7 +57,12 @@ public class JavaScriptServlet extends HttpServlet {
}
}
if(javascript) {
response.setHeader("Content-type", "text/javascript");
}
else {
response.setHeader("Content-type", "text/css");
}
response.setHeader("Vary", "Accept-Encoding"); // Handle proxies
if (!debug) {
......@@ -74,21 +80,24 @@ public class JavaScriptServlet extends HttpServlet {
InputStream in = null;
try {
byte[] jsContent;
String cacheKey = String.valueOf(compress);
jsContent = cache.get(cacheKey);
if (debug || jsContent == null) {
jsContent = getJavaScriptContent(compress);
cache.put(cacheKey, jsContent);
byte[] content;
String cacheKey = String.valueOf(compress + " " + javascript);
content = cache.get(cacheKey);
if (javascript && (debug || content == null)) {
content = getJavaScriptContent(compress);
cache.put(cacheKey, content);
}
else if(!javascript && content == null) {
}
response.setContentLength(jsContent.length);
response.setContentLength(content.length);
if (compress) {
response.setHeader("Content-Encoding", "gzip");
}
// Write the content out
in = new ByteArrayInputStream(jsContent);
in = new ByteArrayInputStream(content);
out = response.getOutputStream();
// Use a 128K buffer.
......@@ -142,8 +151,9 @@ public class JavaScriptServlet extends HttpServlet {
}
private static Collection<String> getJavascriptFiles() {
return Arrays.asList("xmlextras.js", "connection.js", "dojo.js",
"flash.js");
return Arrays.asList("prototype.js", "getelementsbyselector.js", "sarissa.js",
"connection.js", "yahoo-min.js", "dom-min.js", "event-min.js", "dragdrop-min.js",
"yui-ext.js", "spank.js");
}
private static String getJavaScriptFile(String path) {
......
......@@ -17,7 +17,7 @@
<servlet>
<servlet-name>JavaScriptServlet</servlet-name>
<servlet-class>org.jivesoftware.wildfire.http.JavaScriptServlet</servlet-class>
<servlet-class>org.jivesoftware.wildfire.http.ResourceServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
......
/*----------------------------------------------------------------------------\
| XML Extras |
|-----------------------------------------------------------------------------|
| Created by Erik Arvidsson |
| (http://webfx.eae.net/contact.html#erik) |
| For WebFX (http://webfx.eae.net/) |
|-----------------------------------------------------------------------------|
| XML and XML HTTP request abstraction. |
|-----------------------------------------------------------------------------|
| Copyright (c) 2001, 2002, 2003, 2006 Erik Arvidsson |
|-----------------------------------------------------------------------------|
| Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| use this file except in compliance with the License. You may obtain a copy |
| of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| License for the specific language governing permissions and limitations |
| under the License. |
|-----------------------------------------------------------------------------|
| 2001-09-27 | Original Version Posted. |
| 2006-05-29 | Changed license to Apache Software License 2.0. |
|-----------------------------------------------------------------------------|
| Created 2001-09-27 | All changes are in the log above. | Updated 2006-05-29 |
\----------------------------------------------------------------------------*/
//<script>
//////////////////
// Helper Stuff //
//////////////////
// used to find the Automation server name
function getDomDocumentPrefix() {
if (getDomDocumentPrefix.prefix)
return getDomDocumentPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".DomDocument");
return getDomDocumentPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
function getXmlHttpPrefix() {
if (getXmlHttpPrefix.prefix)
return getXmlHttpPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".XmlHttp");
return getXmlHttpPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
//////////////////////////
// Start the Real stuff //
//////////////////////////
// XmlHttp factory
function XmlHttp() {}
XmlHttp.create = function () {
try {
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (req.readyState == null) {
req.readyState = 1;
req.addEventListener("load", function () {
req.readyState = 4;
if (typeof req.onreadystatechange == "function")
req.onreadystatechange();
}, false);
}
return req;
}
if (window.ActiveXObject) {
return new ActiveXObject(getXmlHttpPrefix() + ".XmlHttp");
}
}
catch (ex) {}
// fell through
throw new Error("Your browser does not support XmlHttp objects");
};
// XmlDocument factory
function XmlDocument() {}
XmlDocument.create = function () {
try {
// DOM2
if (document.implementation && document.implementation.createDocument) {
var doc = document.implementation.createDocument("", "", null);
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (doc.readyState == null) {
doc.readyState = 1;
doc.addEventListener("load", function () {
doc.readyState = 4;
if (typeof doc.onreadystatechange == "function")
doc.onreadystatechange();
}, false);
}
return doc;
}
if (window.ActiveXObject)
return new ActiveXObject(getDomDocumentPrefix() + ".DomDocument");
}
catch (ex) {}
throw new Error("Your browser does not support XmlDocument objects");
};
// Create the loadXML method and xml getter for Mozilla
if (window.DOMParser &&
window.XMLSerializer &&
window.Node && Node.prototype && Node.prototype.__defineGetter__) {
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
//XMLDocument.prototype.loadXML =
Document.prototype.loadXML = function (s) {
// parse the string to a new doc
var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
// remove all initial children
while (this.hasChildNodes())
this.removeChild(this.lastChild);
// insert and import nodes
for (var i = 0; i < doc2.childNodes.length; i++) {
this.appendChild(this.importNode(doc2.childNodes[i], true));
}
};
/*
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
*
*/
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
/*
XMLDocument.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
*/
Document.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment