blob: 33a9b7d8984212a8e911d5f44f14d07655932ddc [file] [log] [blame]
diff --git com/kenai/jbosh/ApacheHTTPResponse.java com/kenai/jbosh/ApacheHTTPResponse.java
new file mode 100644
index 0000000..9f6731f
--- /dev/null
+++ com/kenai/jbosh/ApacheHTTPResponse.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2009 Guenther Niess
+ *
+ * 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.
+ */
+
+package com.kenai.jbosh;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+
+final class ApacheHTTPResponse implements HTTPResponse {
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constants:
+
+ /**
+ * Name of the accept encoding header.
+ */
+ private static final String ACCEPT_ENCODING = "Accept-Encoding";
+
+ /**
+ * Value to use for the ACCEPT_ENCODING header.
+ */
+ private static final String ACCEPT_ENCODING_VAL =
+ ZLIBCodec.getID() + ", " + GZIPCodec.getID();
+
+ /**
+ * Name of the character set to encode the body to/from.
+ */
+ private static final String CHARSET = "UTF-8";
+
+ /**
+ * Content type to use when transmitting the body data.
+ */
+ private static final String CONTENT_TYPE = "text/xml; charset=utf-8";
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Class variables:
+
+ /**
+ * Lock used for internal synchronization.
+ */
+ private final Lock lock = new ReentrantLock();
+
+ /**
+ * The execution state of an HTTP process.
+ */
+ private final HttpContext context;
+
+ /**
+ * HttpClient instance to use to communicate.
+ */
+ private final HttpClient client;
+
+ /**
+ * The HTTP POST request is sent to the server.
+ */
+ private final HttpPost post;
+
+ /**
+ * A flag which indicates if the transmission was already done.
+ */
+ private boolean sent;
+
+ /**
+ * Exception to throw when the response data is attempted to be accessed,
+ * or {@code null} if no exception should be thrown.
+ */
+ private BOSHException toThrow;
+
+ /**
+ * The response body which was received from the server or {@code null}
+ * if that has not yet happened.
+ */
+ private AbstractBody body;
+
+ /**
+ * The HTTP response status code.
+ */
+ private int statusCode;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constructors:
+
+ /**
+ * Create and send a new request to the upstream connection manager,
+ * providing deferred access to the results to be returned.
+ *
+ * @param client client instance to use when sending the request
+ * @param cfg client configuration
+ * @param params connection manager parameters from the session creation
+ * response, or {@code null} if the session has not yet been established
+ * @param request body of the client request
+ */
+ ApacheHTTPResponse(
+ final HttpClient client,
+ final BOSHClientConfig cfg,
+ final CMSessionParams params,
+ final AbstractBody request) {
+ super();
+ this.client = client;
+ this.context = new BasicHttpContext();
+ this.post = new HttpPost(cfg.getURI().toString());
+ this.sent = false;
+
+ try {
+ String xml = request.toXML();
+ byte[] data = xml.getBytes(CHARSET);
+
+ String encoding = null;
+ if (cfg.isCompressionEnabled() && params != null) {
+ AttrAccept accept = params.getAccept();
+ if (accept != null) {
+ if (accept.isAccepted(ZLIBCodec.getID())) {
+ encoding = ZLIBCodec.getID();
+ data = ZLIBCodec.encode(data);
+ } else if (accept.isAccepted(GZIPCodec.getID())) {
+ encoding = GZIPCodec.getID();
+ data = GZIPCodec.encode(data);
+ }
+ }
+ }
+
+ ByteArrayEntity entity = new ByteArrayEntity(data);
+ entity.setContentType(CONTENT_TYPE);
+ if (encoding != null) {
+ entity.setContentEncoding(encoding);
+ }
+ post.setEntity(entity);
+ if (cfg.isCompressionEnabled()) {
+ post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL);
+ }
+ } catch (Exception e) {
+ toThrow = new BOSHException("Could not generate request", e);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // HTTPResponse interface methods:
+
+ /**
+ * Abort the client transmission and response processing.
+ */
+ public void abort() {
+ if (post != null) {
+ post.abort();
+ toThrow = new BOSHException("HTTP request aborted");
+ }
+ }
+
+ /**
+ * Wait for and then return the response body.
+ *
+ * @return body of the response
+ * @throws InterruptedException if interrupted while awaiting the response
+ * @throws BOSHException on communication failure
+ */
+ public AbstractBody getBody() throws InterruptedException, BOSHException {
+ if (toThrow != null) {
+ throw(toThrow);
+ }
+ lock.lock();
+ try {
+ if (!sent) {
+ awaitResponse();
+ }
+ } finally {
+ lock.unlock();
+ }
+ return body;
+ }
+
+ /**
+ * Wait for and then return the response HTTP status code.
+ *
+ * @return HTTP status code of the response
+ * @throws InterruptedException if interrupted while awaiting the response
+ * @throws BOSHException on communication failure
+ */
+ public int getHTTPStatus() throws InterruptedException, BOSHException {
+ if (toThrow != null) {
+ throw(toThrow);
+ }
+ lock.lock();
+ try {
+ if (!sent) {
+ awaitResponse();
+ }
+ } finally {
+ lock.unlock();
+ }
+ return statusCode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Package-private methods:
+
+ /**
+ * Await the response, storing the result in the instance variables of
+ * this class when they arrive.
+ *
+ * @throws InterruptedException if interrupted while awaiting the response
+ * @throws BOSHException on communication failure
+ */
+ private synchronized void awaitResponse() throws BOSHException {
+ HttpEntity entity = null;
+ try {
+ HttpResponse httpResp = client.execute(post, context);
+ entity = httpResp.getEntity();
+ byte[] data = EntityUtils.toByteArray(entity);
+ String encoding = entity.getContentEncoding() != null ?
+ entity.getContentEncoding().getValue() :
+ null;
+ if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) {
+ data = ZLIBCodec.decode(data);
+ } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) {
+ data = GZIPCodec.decode(data);
+ }
+ body = StaticBody.fromString(new String(data, CHARSET));
+ statusCode = httpResp.getStatusLine().getStatusCode();
+ sent = true;
+ } catch (IOException iox) {
+ abort();
+ toThrow = new BOSHException("Could not obtain response", iox);
+ throw(toThrow);
+ } catch (RuntimeException ex) {
+ abort();
+ throw(ex);
+ }
+ }
+}
diff --git com/kenai/jbosh/ApacheHTTPSender.java com/kenai/jbosh/ApacheHTTPSender.java
new file mode 100644
index 0000000..2abb4ee
--- /dev/null
+++ com/kenai/jbosh/ApacheHTTPSender.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2009 Guenther Niess
+ *
+ * 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.
+ */
+
+package com.kenai.jbosh;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnManagerParams;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+
+/**
+ * Implementation of the {@code HTTPSender} interface which uses the
+ * Apache HttpClient API to send messages to the connection manager.
+ */
+final class ApacheHTTPSender implements HTTPSender {
+
+ /**
+ * Lock used for internal synchronization.
+ */
+ private final Lock lock = new ReentrantLock();
+
+ /**
+ * Session configuration.
+ */
+ private BOSHClientConfig cfg;
+
+ /**
+ * HttpClient instance to use to communicate.
+ */
+ private HttpClient httpClient;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constructors:
+
+ /**
+ * Prevent construction apart from our package.
+ */
+ ApacheHTTPSender() {
+ // Load Apache HTTP client class
+ HttpClient.class.getName();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // HTTPSender interface methods:
+
+ /**
+ * {@inheritDoc}
+ */
+ public void init(final BOSHClientConfig session) {
+ lock.lock();
+ try {
+ cfg = session;
+ httpClient = initHttpClient(session);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void destroy() {
+ lock.lock();
+ try {
+ if (httpClient != null) {
+ httpClient.getConnectionManager().shutdown();
+ }
+ } finally {
+ cfg = null;
+ httpClient = null;
+ lock.unlock();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public HTTPResponse send(
+ final CMSessionParams params,
+ final AbstractBody body) {
+ HttpClient mClient;
+ BOSHClientConfig mCfg;
+ lock.lock();
+ try {
+ if (httpClient == null) {
+ httpClient = initHttpClient(cfg);
+ }
+ mClient = httpClient;
+ mCfg = cfg;
+ } finally {
+ lock.unlock();
+ }
+ return new ApacheHTTPResponse(mClient, mCfg, params, body);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Package-private methods:
+
+ private synchronized HttpClient initHttpClient(final BOSHClientConfig config) {
+ // Create and initialize HTTP parameters
+ HttpParams params = new BasicHttpParams();
+ ConnManagerParams.setMaxTotalConnections(params, 100);
+ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+ HttpProtocolParams.setUseExpectContinue(params, false);
+ if (config != null &&
+ config.getProxyHost() != null &&
+ config.getProxyPort() != 0) {
+ HttpHost proxy = new HttpHost(
+ config.getProxyHost(),
+ config.getProxyPort());
+ params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+ }
+
+ // Create and initialize scheme registry
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry.register(
+ new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+ SSLSocketFactory sslFactory = SSLSocketFactory.getSocketFactory();
+ sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+ schemeRegistry.register(
+ new Scheme("https", sslFactory, 443));
+
+ // Create an HttpClient with the ThreadSafeClientConnManager.
+ // This connection manager must be used if more than one thread will
+ // be using the HttpClient.
+ ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
+ return new DefaultHttpClient(cm, params);
+ }
+}
diff --git com/kenai/jbosh/BodyParserXmlPull.java com/kenai/jbosh/BodyParserXmlPull.java
index cc95236..5f23b06 100644
--- com/kenai/jbosh/BodyParserXmlPull.java
+++ com/kenai/jbosh/BodyParserXmlPull.java
@@ -22,7 +22,6 @@ import java.lang.ref.SoftReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
-import javax.xml.namespace.QName;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
diff --git com/kenai/jbosh/BodyQName.java com/kenai/jbosh/BodyQName.java
index fc7ab0c..83acdf1 100644
--- com/kenai/jbosh/BodyQName.java
+++ com/kenai/jbosh/BodyQName.java
@@ -16,8 +16,6 @@
package com.kenai.jbosh;
-import javax.xml.namespace.QName;
-
/**
* Qualified name of an attribute of the wrapper element. This class is
* analagous to the {@code javax.xml.namespace.QName} class.