1 /* 2 * Copyright 2009 Guenther Niess 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.kenai.jbosh; 18 19 import java.io.IOException; 20 import java.util.concurrent.locks.Lock; 21 import java.util.concurrent.locks.ReentrantLock; 22 23 import org.apache.http.HttpEntity; 24 import org.apache.http.HttpResponse; 25 import org.apache.http.client.HttpClient; 26 import org.apache.http.client.methods.HttpPost; 27 import org.apache.http.entity.ByteArrayEntity; 28 29 import org.apache.http.protocol.BasicHttpContext; 30 import org.apache.http.protocol.HttpContext; 31 import org.apache.http.util.EntityUtils; 32 33 final class ApacheHTTPResponse implements HTTPResponse { 34 35 /////////////////////////////////////////////////////////////////////////// 36 // Constants: 37 38 /** 39 * Name of the accept encoding header. 40 */ 41 private static final String ACCEPT_ENCODING = "Accept-Encoding"; 42 43 /** 44 * Value to use for the ACCEPT_ENCODING header. 45 */ 46 private static final String ACCEPT_ENCODING_VAL = 47 ZLIBCodec.getID() + ", " + GZIPCodec.getID(); 48 49 /** 50 * Name of the character set to encode the body to/from. 51 */ 52 private static final String CHARSET = "UTF-8"; 53 54 /** 55 * Content type to use when transmitting the body data. 56 */ 57 private static final String CONTENT_TYPE = "text/xml; charset=utf-8"; 58 59 /////////////////////////////////////////////////////////////////////////// 60 // Class variables: 61 62 /** 63 * Lock used for internal synchronization. 64 */ 65 private final Lock lock = new ReentrantLock(); 66 67 /** 68 * The execution state of an HTTP process. 69 */ 70 private final HttpContext context; 71 72 /** 73 * HttpClient instance to use to communicate. 74 */ 75 private final HttpClient client; 76 77 /** 78 * The HTTP POST request is sent to the server. 79 */ 80 private final HttpPost post; 81 82 /** 83 * A flag which indicates if the transmission was already done. 84 */ 85 private boolean sent; 86 87 /** 88 * Exception to throw when the response data is attempted to be accessed, 89 * or {@code null} if no exception should be thrown. 90 */ 91 private BOSHException toThrow; 92 93 /** 94 * The response body which was received from the server or {@code null} 95 * if that has not yet happened. 96 */ 97 private AbstractBody body; 98 99 /** 100 * The HTTP response status code. 101 */ 102 private int statusCode; 103 104 /////////////////////////////////////////////////////////////////////////// 105 // Constructors: 106 107 /** 108 * Create and send a new request to the upstream connection manager, 109 * providing deferred access to the results to be returned. 110 * 111 * @param client client instance to use when sending the request 112 * @param cfg client configuration 113 * @param params connection manager parameters from the session creation 114 * response, or {@code null} if the session has not yet been established 115 * @param request body of the client request 116 */ 117 ApacheHTTPResponse( 118 final HttpClient client, 119 final BOSHClientConfig cfg, 120 final CMSessionParams params, 121 final AbstractBody request) { 122 super(); 123 this.client = client; 124 this.context = new BasicHttpContext(); 125 this.post = new HttpPost(cfg.getURI().toString()); 126 this.sent = false; 127 128 try { 129 String xml = request.toXML(); 130 byte[] data = xml.getBytes(CHARSET); 131 132 String encoding = null; 133 if (cfg.isCompressionEnabled() && params != null) { 134 AttrAccept accept = params.getAccept(); 135 if (accept != null) { 136 if (accept.isAccepted(ZLIBCodec.getID())) { 137 encoding = ZLIBCodec.getID(); 138 data = ZLIBCodec.encode(data); 139 } else if (accept.isAccepted(GZIPCodec.getID())) { 140 encoding = GZIPCodec.getID(); 141 data = GZIPCodec.encode(data); 142 } 143 } 144 } 145 146 ByteArrayEntity entity = new ByteArrayEntity(data); 147 entity.setContentType(CONTENT_TYPE); 148 if (encoding != null) { 149 entity.setContentEncoding(encoding); 150 } 151 post.setEntity(entity); 152 if (cfg.isCompressionEnabled()) { 153 post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL); 154 } 155 } catch (Exception e) { 156 toThrow = new BOSHException("Could not generate request", e); 157 } 158 } 159 160 /////////////////////////////////////////////////////////////////////////// 161 // HTTPResponse interface methods: 162 163 /** 164 * Abort the client transmission and response processing. 165 */ 166 public void abort() { 167 if (post != null) { 168 post.abort(); 169 toThrow = new BOSHException("HTTP request aborted"); 170 } 171 } 172 173 /** 174 * Wait for and then return the response body. 175 * 176 * @return body of the response 177 * @throws InterruptedException if interrupted while awaiting the response 178 * @throws BOSHException on communication failure 179 */ 180 public AbstractBody getBody() throws InterruptedException, BOSHException { 181 if (toThrow != null) { 182 throw(toThrow); 183 } 184 lock.lock(); 185 try { 186 if (!sent) { 187 awaitResponse(); 188 } 189 } finally { 190 lock.unlock(); 191 } 192 return body; 193 } 194 195 /** 196 * Wait for and then return the response HTTP status code. 197 * 198 * @return HTTP status code of the response 199 * @throws InterruptedException if interrupted while awaiting the response 200 * @throws BOSHException on communication failure 201 */ 202 public int getHTTPStatus() throws InterruptedException, BOSHException { 203 if (toThrow != null) { 204 throw(toThrow); 205 } 206 lock.lock(); 207 try { 208 if (!sent) { 209 awaitResponse(); 210 } 211 } finally { 212 lock.unlock(); 213 } 214 return statusCode; 215 } 216 217 /////////////////////////////////////////////////////////////////////////// 218 // Package-private methods: 219 220 /** 221 * Await the response, storing the result in the instance variables of 222 * this class when they arrive. 223 * 224 * @throws InterruptedException if interrupted while awaiting the response 225 * @throws BOSHException on communication failure 226 */ 227 private synchronized void awaitResponse() throws BOSHException { 228 HttpEntity entity = null; 229 try { 230 HttpResponse httpResp = client.execute(post, context); 231 entity = httpResp.getEntity(); 232 byte[] data = EntityUtils.toByteArray(entity); 233 String encoding = entity.getContentEncoding() != null ? 234 entity.getContentEncoding().getValue() : 235 null; 236 if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) { 237 data = ZLIBCodec.decode(data); 238 } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) { 239 data = GZIPCodec.decode(data); 240 } 241 body = StaticBody.fromString(new String(data, CHARSET)); 242 statusCode = httpResp.getStatusLine().getStatusCode(); 243 sent = true; 244 } catch (IOException iox) { 245 abort(); 246 toThrow = new BOSHException("Could not obtain response", iox); 247 throw(toThrow); 248 } catch (RuntimeException ex) { 249 abort(); 250 throw(ex); 251 } 252 } 253 } 254