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.