001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.http;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.io.UnsupportedEncodingException;
022    import java.util.HashMap;
023    import java.util.Map;
024    
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Message;
027    import org.apache.camel.RuntimeCamelException;
028    import org.apache.camel.component.http.helper.GZIPHelper;
029    import org.apache.camel.component.http.helper.HttpProducerHelper;
030    import org.apache.camel.converter.stream.CachedOutputStream;
031    import org.apache.camel.impl.DefaultProducer;
032    import org.apache.camel.spi.HeaderFilterStrategy;
033    import org.apache.camel.util.ExchangeHelper;
034    import org.apache.camel.util.IOHelper;
035    import org.apache.camel.util.ObjectHelper;
036    import org.apache.commons.httpclient.Header;
037    import org.apache.commons.httpclient.HttpClient;
038    import org.apache.commons.httpclient.HttpMethod;
039    import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
040    import org.apache.commons.httpclient.methods.RequestEntity;
041    import org.apache.commons.httpclient.methods.StringRequestEntity;
042    import org.apache.commons.logging.Log;
043    import org.apache.commons.logging.LogFactory;
044    
045    /**
046     * @version $Revision: 946854 $
047     */
048    public class HttpProducer extends DefaultProducer {
049        private static final transient Log LOG = LogFactory.getLog(HttpProducer.class);
050        private HttpClient httpClient;
051        private boolean throwException;
052    
053        public HttpProducer(HttpEndpoint endpoint) {
054            super(endpoint);
055            this.httpClient = endpoint.createHttpClient();
056            this.throwException = endpoint.isThrowExceptionOnFailure();
057        }
058    
059        public void process(Exchange exchange) throws Exception {
060            if (((HttpEndpoint)getEndpoint()).isBridgeEndpoint()) {
061                exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE);
062            }
063            HttpMethod method = createMethod(exchange);
064            Message in = exchange.getIn();
065            HeaderFilterStrategy strategy = getEndpoint().getHeaderFilterStrategy();
066    
067            // propagate headers as HTTP headers
068            for (Map.Entry<String, Object> entry : in.getHeaders().entrySet()) {
069                String headerValue = in.getHeader(entry.getKey(), String.class);
070                if (strategy != null && !strategy.applyFilterToCamelHeaders(entry.getKey(), headerValue, exchange)) {
071                    method.addRequestHeader(entry.getKey(), headerValue);
072                }
073            }
074    
075            // lets store the result in the output message.
076            try {
077                if (LOG.isDebugEnabled()) {
078                    LOG.debug("Executing http " + method.getName() + " method: " + method.getURI().toString());
079                }
080                int responseCode = executeMethod(method);
081                if (LOG.isDebugEnabled()) {
082                    LOG.debug("Http responseCode: " + responseCode);
083                }
084    
085                if (!throwException) {
086                    // if we do not use failed exception then populate response for all response codes
087                    populateResponse(exchange, method, in, strategy, responseCode);
088                } else {
089                    if (responseCode >= 100 && responseCode < 300) {
090                        // only populate response for OK response
091                        populateResponse(exchange, method, in, strategy, responseCode);
092                    } else {
093                        // operation failed so populate exception to throw
094                        throw populateHttpOperationFailedException(exchange, method, responseCode);
095                    }
096                }
097            } finally {
098                method.releaseConnection();
099            }
100        }
101    
102        @Override
103        public HttpEndpoint getEndpoint() {
104            return (HttpEndpoint) super.getEndpoint();
105        }
106    
107        protected void populateResponse(Exchange exchange, HttpMethod method, Message in, HeaderFilterStrategy strategy, int responseCode) throws IOException {
108            Message answer = exchange.getOut();
109    
110            answer.setHeaders(in.getHeaders());
111            answer.setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode);
112            answer.setBody(extractResponseBody(method, exchange));
113    
114            // propagate HTTP response headers
115            Header[] headers = method.getResponseHeaders();
116            for (Header header : headers) {
117                String name = header.getName();
118                String value = header.getValue();
119                if (name.toLowerCase().equals("content-type")) {
120                    name = Exchange.CONTENT_TYPE;
121                }
122                if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value, exchange)) {
123                    answer.setHeader(name, value);
124                }
125            }
126        }
127    
128        protected HttpOperationFailedException populateHttpOperationFailedException(Exchange exchange, HttpMethod method, int responseCode) throws IOException {
129            HttpOperationFailedException exception;
130            String uri = method.getURI().toString();
131            String statusText = method.getStatusLine() != null ? method.getStatusLine().getReasonPhrase() : null;
132            Map<String, String> headers = extractResponseHeaders(method.getResponseHeaders());
133            InputStream is = extractResponseBody(method, exchange);
134            // make a defensive copy of the response body in the exception so its detached from the cache
135            String copy = null;
136            if (is != null) {
137                copy = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, is);
138            }
139    
140            if (responseCode >= 300 && responseCode < 400) {
141                String redirectLocation;
142                Header locationHeader = method.getResponseHeader("location");
143                if (locationHeader != null) {
144                    redirectLocation = locationHeader.getValue();
145                    exception = new HttpOperationFailedException(uri, responseCode, statusText, redirectLocation, headers, copy);
146                } else {
147                    // no redirect location
148                    exception = new HttpOperationFailedException(uri, responseCode, statusText, null, headers, copy);
149                }
150            } else {
151                // internal server error (error code 500)
152                exception = new HttpOperationFailedException(uri, responseCode, statusText, null, headers, copy);
153            }
154    
155            return exception;
156        }
157    
158        /**
159         * Strategy when executing the method (calling the remote server).
160         *
161         * @param method    the method to execute
162         * @return the response code
163         * @throws IOException can be thrown
164         */
165        protected int executeMethod(HttpMethod method) throws IOException {
166            return httpClient.executeMethod(method);
167        }
168    
169        /**
170         * Extracts the response headers
171         *
172         * @param responseHeaders the headers
173         * @return the extracted headers or <tt>null</tt> if no headers existed
174         */
175        protected static Map<String, String> extractResponseHeaders(Header[] responseHeaders) {
176            if (responseHeaders == null || responseHeaders.length == 0) {
177                return null;
178            }
179    
180            Map<String, String> answer = new HashMap<String, String>();
181            for (Header header : responseHeaders) {
182                answer.put(header.getName(), header.getValue());
183            }
184    
185            return answer;
186        }
187    
188        /**
189         * Extracts the response from the method as a InputStream.
190         *
191         * @param method  the method that was executed
192         * @return  the response as a stream
193         * @throws IOException can be thrown
194         */
195        protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException {
196            InputStream is = method.getResponseBodyAsStream();
197            if (is == null) {
198                return null;
199            }
200    
201            Header header = method.getRequestHeader(Exchange.CONTENT_ENCODING);        
202            String contentEncoding = header != null ? header.getValue() : null;
203            
204            if (!exchange.getProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.FALSE, Boolean.class)) {
205                is = GZIPHelper.uncompressGzip(contentEncoding, is);
206            }
207            
208            // Honor the character encoding
209            header = method.getResponseHeader("content-type");
210            if (header != null) {
211                String contentType = header.getValue();
212                // find the charset and set it to the Exchange
213                int index = contentType.indexOf("charset=");
214                if (index > 0) {
215                    String charset = contentType.substring(index + 8);
216                    exchange.setProperty(Exchange.CHARSET_NAME, charset);
217                }
218            }
219            return doExtractResponseBody(is, exchange);
220        }
221    
222        private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
223            // As httpclient is using a AutoCloseInputStream, it will be closed when the connection is closed
224            // we need to cache the stream for it.
225            try {
226                CachedOutputStream cos = new CachedOutputStream(exchange);
227                IOHelper.copy(is, cos);
228                return cos.getInputStream();
229            } finally {
230                ObjectHelper.close(is, "Extracting response body", LOG);            
231            }
232        }
233    
234        /**
235         * Creates the HttpMethod to use to call the remote server, either its GET or POST.
236         *
237         * @param exchange  the exchange
238         * @return the created method as either GET or POST
239         */
240        protected HttpMethod createMethod(Exchange exchange) {
241    
242            String url = HttpProducerHelper.createURL(exchange, getEndpoint());
243    
244            RequestEntity requestEntity = createRequestEntity(exchange);
245            HttpMethods methodToUse = HttpProducerHelper.createMethod(exchange, getEndpoint(), requestEntity != null);
246            HttpMethod method = methodToUse.createMethod(url);
247    
248            // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
249            String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
250            if (queryString == null) {
251                queryString = getEndpoint().getHttpUri().getQuery();
252            }
253            if (queryString != null) {
254                method.setQueryString(queryString);
255            }
256    
257            if (methodToUse.isEntityEnclosing()) {
258                ((EntityEnclosingMethod)method).setRequestEntity(requestEntity);
259                if (requestEntity != null && requestEntity.getContentType() == null) {
260                    if (LOG.isDebugEnabled()) {
261                        LOG.debug("No Content-Type provided for URL: " + url + " with exchange: " + exchange);
262                    }
263                }
264            }
265    
266            return method;
267        }
268    
269        /**
270         * Creates a holder object for the data to send to the remote server.
271         *
272         * @param exchange  the exchange with the IN message with data to send
273         * @return the data holder
274         */
275        protected RequestEntity createRequestEntity(Exchange exchange) {
276            Message in = exchange.getIn();
277            if (in.getBody() == null) {
278                return null;
279            }
280    
281            RequestEntity answer = in.getBody(RequestEntity.class);        
282            if (answer == null) {
283                try {
284                    String data = in.getBody(String.class);
285                    if (data != null) {
286                        String contentType = ExchangeHelper.getContentType(exchange);
287                        String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
288                        answer = new StringRequestEntity(data, contentType, charset);
289                    }
290                } catch (UnsupportedEncodingException e) {
291                    throw new RuntimeCamelException(e);
292                }
293            }
294            return answer;
295        }
296    
297        public HttpClient getHttpClient() {
298            return httpClient;
299        }
300    
301        public void setHttpClient(HttpClient httpClient) {
302            this.httpClient = httpClient;
303        }
304    }