1 diff --git com/kenai/jbosh/ApacheHTTPResponse.java com/kenai/jbosh/ApacheHTTPResponse.java 2 new file mode 100644 3 index 0000000..9f6731f 4 --- /dev/null 5 +++ com/kenai/jbosh/ApacheHTTPResponse.java 6 @@ -0,0 +1,253 @@ 7 +/* 8 + * Copyright 2009 Guenther Niess 9 + * 10 + * Licensed under the Apache License, Version 2.0 (the "License"); 11 + * you may not use this file except in compliance with the License. 12 + * You may obtain a copy of the License at 13 + * 14 + * http://www.apache.org/licenses/LICENSE-2.0 15 + * 16 + * Unless required by applicable law or agreed to in writing, software 17 + * distributed under the License is distributed on an "AS IS" BASIS, 18 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 + * See the License for the specific language governing permissions and 20 + * limitations under the License. 21 + */ 22 + 23 +package com.kenai.jbosh; 24 + 25 +import java.io.IOException; 26 +import java.util.concurrent.locks.Lock; 27 +import java.util.concurrent.locks.ReentrantLock; 28 + 29 +import org.apache.http.HttpEntity; 30 +import org.apache.http.HttpResponse; 31 +import org.apache.http.client.HttpClient; 32 +import org.apache.http.client.methods.HttpPost; 33 +import org.apache.http.entity.ByteArrayEntity; 34 + 35 +import org.apache.http.protocol.BasicHttpContext; 36 +import org.apache.http.protocol.HttpContext; 37 +import org.apache.http.util.EntityUtils; 38 + 39 +final class ApacheHTTPResponse implements HTTPResponse { 40 + 41 + /////////////////////////////////////////////////////////////////////////// 42 + // Constants: 43 + 44 + /** 45 + * Name of the accept encoding header. 46 + */ 47 + private static final String ACCEPT_ENCODING = "Accept-Encoding"; 48 + 49 + /** 50 + * Value to use for the ACCEPT_ENCODING header. 51 + */ 52 + private static final String ACCEPT_ENCODING_VAL = 53 + ZLIBCodec.getID() + ", " + GZIPCodec.getID(); 54 + 55 + /** 56 + * Name of the character set to encode the body to/from. 57 + */ 58 + private static final String CHARSET = "UTF-8"; 59 + 60 + /** 61 + * Content type to use when transmitting the body data. 62 + */ 63 + private static final String CONTENT_TYPE = "text/xml; charset=utf-8"; 64 + 65 + /////////////////////////////////////////////////////////////////////////// 66 + // Class variables: 67 + 68 + /** 69 + * Lock used for internal synchronization. 70 + */ 71 + private final Lock lock = new ReentrantLock(); 72 + 73 + /** 74 + * The execution state of an HTTP process. 75 + */ 76 + private final HttpContext context; 77 + 78 + /** 79 + * HttpClient instance to use to communicate. 80 + */ 81 + private final HttpClient client; 82 + 83 + /** 84 + * The HTTP POST request is sent to the server. 85 + */ 86 + private final HttpPost post; 87 + 88 + /** 89 + * A flag which indicates if the transmission was already done. 90 + */ 91 + private boolean sent; 92 + 93 + /** 94 + * Exception to throw when the response data is attempted to be accessed, 95 + * or {@code null} if no exception should be thrown. 96 + */ 97 + private BOSHException toThrow; 98 + 99 + /** 100 + * The response body which was received from the server or {@code null} 101 + * if that has not yet happened. 102 + */ 103 + private AbstractBody body; 104 + 105 + /** 106 + * The HTTP response status code. 107 + */ 108 + private int statusCode; 109 + 110 + /////////////////////////////////////////////////////////////////////////// 111 + // Constructors: 112 + 113 + /** 114 + * Create and send a new request to the upstream connection manager, 115 + * providing deferred access to the results to be returned. 116 + * 117 + * @param client client instance to use when sending the request 118 + * @param cfg client configuration 119 + * @param params connection manager parameters from the session creation 120 + * response, or {@code null} if the session has not yet been established 121 + * @param request body of the client request 122 + */ 123 + ApacheHTTPResponse( 124 + final HttpClient client, 125 + final BOSHClientConfig cfg, 126 + final CMSessionParams params, 127 + final AbstractBody request) { 128 + super(); 129 + this.client = client; 130 + this.context = new BasicHttpContext(); 131 + this.post = new HttpPost(cfg.getURI().toString()); 132 + this.sent = false; 133 + 134 + try { 135 + String xml = request.toXML(); 136 + byte[] data = xml.getBytes(CHARSET); 137 + 138 + String encoding = null; 139 + if (cfg.isCompressionEnabled() && params != null) { 140 + AttrAccept accept = params.getAccept(); 141 + if (accept != null) { 142 + if (accept.isAccepted(ZLIBCodec.getID())) { 143 + encoding = ZLIBCodec.getID(); 144 + data = ZLIBCodec.encode(data); 145 + } else if (accept.isAccepted(GZIPCodec.getID())) { 146 + encoding = GZIPCodec.getID(); 147 + data = GZIPCodec.encode(data); 148 + } 149 + } 150 + } 151 + 152 + ByteArrayEntity entity = new ByteArrayEntity(data); 153 + entity.setContentType(CONTENT_TYPE); 154 + if (encoding != null) { 155 + entity.setContentEncoding(encoding); 156 + } 157 + post.setEntity(entity); 158 + if (cfg.isCompressionEnabled()) { 159 + post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL); 160 + } 161 + } catch (Exception e) { 162 + toThrow = new BOSHException("Could not generate request", e); 163 + } 164 + } 165 + 166 + /////////////////////////////////////////////////////////////////////////// 167 + // HTTPResponse interface methods: 168 + 169 + /** 170 + * Abort the client transmission and response processing. 171 + */ 172 + public void abort() { 173 + if (post != null) { 174 + post.abort(); 175 + toThrow = new BOSHException("HTTP request aborted"); 176 + } 177 + } 178 + 179 + /** 180 + * Wait for and then return the response body. 181 + * 182 + * @return body of the response 183 + * @throws InterruptedException if interrupted while awaiting the response 184 + * @throws BOSHException on communication failure 185 + */ 186 + public AbstractBody getBody() throws InterruptedException, BOSHException { 187 + if (toThrow != null) { 188 + throw(toThrow); 189 + } 190 + lock.lock(); 191 + try { 192 + if (!sent) { 193 + awaitResponse(); 194 + } 195 + } finally { 196 + lock.unlock(); 197 + } 198 + return body; 199 + } 200 + 201 + /** 202 + * Wait for and then return the response HTTP status code. 203 + * 204 + * @return HTTP status code of the response 205 + * @throws InterruptedException if interrupted while awaiting the response 206 + * @throws BOSHException on communication failure 207 + */ 208 + public int getHTTPStatus() throws InterruptedException, BOSHException { 209 + if (toThrow != null) { 210 + throw(toThrow); 211 + } 212 + lock.lock(); 213 + try { 214 + if (!sent) { 215 + awaitResponse(); 216 + } 217 + } finally { 218 + lock.unlock(); 219 + } 220 + return statusCode; 221 + } 222 + 223 + /////////////////////////////////////////////////////////////////////////// 224 + // Package-private methods: 225 + 226 + /** 227 + * Await the response, storing the result in the instance variables of 228 + * this class when they arrive. 229 + * 230 + * @throws InterruptedException if interrupted while awaiting the response 231 + * @throws BOSHException on communication failure 232 + */ 233 + private synchronized void awaitResponse() throws BOSHException { 234 + HttpEntity entity = null; 235 + try { 236 + HttpResponse httpResp = client.execute(post, context); 237 + entity = httpResp.getEntity(); 238 + byte[] data = EntityUtils.toByteArray(entity); 239 + String encoding = entity.getContentEncoding() != null ? 240 + entity.getContentEncoding().getValue() : 241 + null; 242 + if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) { 243 + data = ZLIBCodec.decode(data); 244 + } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) { 245 + data = GZIPCodec.decode(data); 246 + } 247 + body = StaticBody.fromString(new String(data, CHARSET)); 248 + statusCode = httpResp.getStatusLine().getStatusCode(); 249 + sent = true; 250 + } catch (IOException iox) { 251 + abort(); 252 + toThrow = new BOSHException("Could not obtain response", iox); 253 + throw(toThrow); 254 + } catch (RuntimeException ex) { 255 + abort(); 256 + throw(ex); 257 + } 258 + } 259 +} 260 diff --git com/kenai/jbosh/ApacheHTTPSender.java com/kenai/jbosh/ApacheHTTPSender.java 261 new file mode 100644 262 index 0000000..2abb4ee 263 --- /dev/null 264 +++ com/kenai/jbosh/ApacheHTTPSender.java 265 @@ -0,0 +1,156 @@ 266 +/* 267 + * Copyright 2009 Guenther Niess 268 + * 269 + * Licensed under the Apache License, Version 2.0 (the "License"); 270 + * you may not use this file except in compliance with the License. 271 + * You may obtain a copy of the License at 272 + * 273 + * http://www.apache.org/licenses/LICENSE-2.0 274 + * 275 + * Unless required by applicable law or agreed to in writing, software 276 + * distributed under the License is distributed on an "AS IS" BASIS, 277 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 278 + * See the License for the specific language governing permissions and 279 + * limitations under the License. 280 + */ 281 + 282 +package com.kenai.jbosh; 283 + 284 +import java.util.concurrent.locks.Lock; 285 +import java.util.concurrent.locks.ReentrantLock; 286 + 287 +import org.apache.http.HttpHost; 288 +import org.apache.http.HttpVersion; 289 +import org.apache.http.client.HttpClient; 290 +import org.apache.http.conn.ClientConnectionManager; 291 +import org.apache.http.conn.params.ConnManagerParams; 292 +import org.apache.http.conn.params.ConnRoutePNames; 293 +import org.apache.http.conn.scheme.PlainSocketFactory; 294 +import org.apache.http.conn.scheme.Scheme; 295 +import org.apache.http.conn.scheme.SchemeRegistry; 296 +import org.apache.http.conn.ssl.SSLSocketFactory; 297 +import org.apache.http.impl.client.DefaultHttpClient; 298 +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 299 +import org.apache.http.params.BasicHttpParams; 300 +import org.apache.http.params.HttpParams; 301 +import org.apache.http.params.HttpProtocolParams; 302 + 303 +/** 304 + * Implementation of the {@code HTTPSender} interface which uses the 305 + * Apache HttpClient API to send messages to the connection manager. 306 + */ 307 +final class ApacheHTTPSender implements HTTPSender { 308 + 309 + /** 310 + * Lock used for internal synchronization. 311 + */ 312 + private final Lock lock = new ReentrantLock(); 313 + 314 + /** 315 + * Session configuration. 316 + */ 317 + private BOSHClientConfig cfg; 318 + 319 + /** 320 + * HttpClient instance to use to communicate. 321 + */ 322 + private HttpClient httpClient; 323 + 324 + /////////////////////////////////////////////////////////////////////////// 325 + // Constructors: 326 + 327 + /** 328 + * Prevent construction apart from our package. 329 + */ 330 + ApacheHTTPSender() { 331 + // Load Apache HTTP client class 332 + HttpClient.class.getName(); 333 + } 334 + 335 + /////////////////////////////////////////////////////////////////////////// 336 + // HTTPSender interface methods: 337 + 338 + /** 339 + * {@inheritDoc} 340 + */ 341 + public void init(final BOSHClientConfig session) { 342 + lock.lock(); 343 + try { 344 + cfg = session; 345 + httpClient = initHttpClient(session); 346 + } finally { 347 + lock.unlock(); 348 + } 349 + } 350 + 351 + /** 352 + * {@inheritDoc} 353 + */ 354 + public void destroy() { 355 + lock.lock(); 356 + try { 357 + if (httpClient != null) { 358 + httpClient.getConnectionManager().shutdown(); 359 + } 360 + } finally { 361 + cfg = null; 362 + httpClient = null; 363 + lock.unlock(); 364 + } 365 + } 366 + 367 + /** 368 + * {@inheritDoc} 369 + */ 370 + public HTTPResponse send( 371 + final CMSessionParams params, 372 + final AbstractBody body) { 373 + HttpClient mClient; 374 + BOSHClientConfig mCfg; 375 + lock.lock(); 376 + try { 377 + if (httpClient == null) { 378 + httpClient = initHttpClient(cfg); 379 + } 380 + mClient = httpClient; 381 + mCfg = cfg; 382 + } finally { 383 + lock.unlock(); 384 + } 385 + return new ApacheHTTPResponse(mClient, mCfg, params, body); 386 + } 387 + 388 + /////////////////////////////////////////////////////////////////////////// 389 + // Package-private methods: 390 + 391 + private synchronized HttpClient initHttpClient(final BOSHClientConfig config) { 392 + // Create and initialize HTTP parameters 393 + HttpParams params = new BasicHttpParams(); 394 + ConnManagerParams.setMaxTotalConnections(params, 100); 395 + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 396 + HttpProtocolParams.setUseExpectContinue(params, false); 397 + if (config != null && 398 + config.getProxyHost() != null && 399 + config.getProxyPort() != 0) { 400 + HttpHost proxy = new HttpHost( 401 + config.getProxyHost(), 402 + config.getProxyPort()); 403 + params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); 404 + } 405 + 406 + // Create and initialize scheme registry 407 + SchemeRegistry schemeRegistry = new SchemeRegistry(); 408 + schemeRegistry.register( 409 + new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 410 + SSLSocketFactory sslFactory = SSLSocketFactory.getSocketFactory(); 411 + sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 412 + schemeRegistry.register( 413 + new Scheme("https", sslFactory, 443)); 414 + 415 + // Create an HttpClient with the ThreadSafeClientConnManager. 416 + // This connection manager must be used if more than one thread will 417 + // be using the HttpClient. 418 + ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); 419 + return new DefaultHttpClient(cm, params); 420 + } 421 +} 422 diff --git com/kenai/jbosh/BodyParserXmlPull.java com/kenai/jbosh/BodyParserXmlPull.java 423 index cc95236..5f23b06 100644 424 --- com/kenai/jbosh/BodyParserXmlPull.java 425 +++ com/kenai/jbosh/BodyParserXmlPull.java 426 @@ -22,7 +22,6 @@ import java.lang.ref.SoftReference; 427 import java.util.logging.Level; 428 import java.util.logging.Logger; 429 import javax.xml.XMLConstants; 430 -import javax.xml.namespace.QName; 431 import org.xmlpull.v1.XmlPullParser; 432 import org.xmlpull.v1.XmlPullParserException; 433 import org.xmlpull.v1.XmlPullParserFactory; 434 diff --git com/kenai/jbosh/BodyQName.java com/kenai/jbosh/BodyQName.java 435 index fc7ab0c..83acdf1 100644 436 --- com/kenai/jbosh/BodyQName.java 437 +++ com/kenai/jbosh/BodyQName.java 438 @@ -16,8 +16,6 @@ 439 440 package com.kenai.jbosh; 441 442 -import javax.xml.namespace.QName; 443 - 444 /** 445 * Qualified name of an attribute of the wrapper element. This class is 446 * analagous to the {@code javax.xml.namespace.QName} class. 447