| /* |
| * 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); |
| } |
| } |
| } |