Home | History | Annotate | Download | only in jbosh
      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