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,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