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.ByteArrayInputStream;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.UnsupportedEncodingException;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import org.apache.camel.Exchange;
027    import org.apache.camel.Message;
028    import org.apache.camel.RuntimeCamelException;
029    import org.apache.camel.component.http.helper.GZIPHelper;
030    import org.apache.camel.component.http.helper.HttpProducerHelper;
031    import org.apache.camel.converter.stream.CachedOutputStream;
032    import org.apache.camel.impl.DefaultProducer;
033    import org.apache.camel.spi.HeaderFilterStrategy;
034    import org.apache.camel.util.ExchangeHelper;
035    import org.apache.camel.util.IOHelper;
036    import org.apache.camel.util.ObjectHelper;
037    import org.apache.commons.httpclient.Header;
038    import org.apache.commons.httpclient.HttpClient;
039    import org.apache.commons.httpclient.HttpMethod;
040    import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
041    import org.apache.commons.httpclient.methods.RequestEntity;
042    import org.apache.commons.httpclient.methods.StringRequestEntity;
043    import org.apache.commons.logging.Log;
044    import org.apache.commons.logging.LogFactory;
045    
046    /**
047     * @version $Revision: 835332 $
048     */
049    public class HttpProducer extends DefaultProducer {
050        private static final transient Log LOG = LogFactory.getLog(HttpProducer.class);
051        private HttpClient httpClient;
052        private boolean throwException;
053    
054        public HttpProducer(HttpEndpoint endpoint) {
055            super(endpoint);
056            this.httpClient = endpoint.createHttpClient();
057            this.throwException = endpoint.isThrowExceptionOnFailure();
058        }
059    
060        public void process(Exchange exchange) throws Exception {
061            HttpMethod method = createMethod(exchange);
062            Message in = exchange.getIn();
063            HeaderFilterStrategy strategy = getEndpoint().getHeaderFilterStrategy();
064    
065            // propagate headers as HTTP headers
066            for (String headerName : in.getHeaders().keySet()) {
067                String headerValue = in.getHeader(headerName, String.class);
068                if (strategy != null && !strategy.applyFilterToCamelHeaders(headerName, headerValue, exchange)) {
069                    method.addRequestHeader(headerName, headerValue);
070                }
071            }
072    
073            // lets store the result in the output message.
074            try {
075                if (LOG.isDebugEnabled()) {
076                    LOG.debug("Executing http " + method.getName() + " method: " + method.getURI().toString());
077                }
078                int responseCode = executeMethod(method);
079                if (LOG.isDebugEnabled()) {
080                    LOG.debug("Http responseCode: " + responseCode);
081                }
082    
083                if (!throwException) {
084                    // if we do not use failed exception then populate response for all response codes
085                    populateResponse(exchange, method, in, strategy, responseCode);
086                } else {
087                    if (responseCode >= 100 && responseCode < 300) {
088                        // only populate response for OK response
089                        populateResponse(exchange, method, in, strategy, responseCode);
090                    } else {
091                        // operation failed so populate exception to throw
092                        throw populateHttpOperationFailedException(exchange, method, responseCode);
093                    }
094                }
095            } finally {
096                method.releaseConnection();
097            }
098        }
099    
100        @Override
101        public HttpEndpoint getEndpoint() {
102            return (HttpEndpoint) super.getEndpoint();
103        }
104    
105        protected void populateResponse(Exchange exchange, HttpMethod method, Message in, HeaderFilterStrategy strategy, int responseCode) throws IOException {
106            Message answer = exchange.getOut();
107    
108            answer.setHeaders(in.getHeaders());
109            answer.setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode);
110            answer.setBody(extractResponseBody(method, exchange));
111    
112            // propagate HTTP response headers
113            Header[] headers = method.getResponseHeaders();
114            for (Header header : headers) {
115                String name = header.getName();
116                String value = header.getValue();
117                if (name.toLowerCase().equals("content-type")) {
118                    name = Exchange.CONTENT_TYPE;
119                }
120                if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value, exchange)) {
121                    answer.setHeader(name, value);
122                }
123            }
124        }
125    
126        protected HttpOperationFailedException populateHttpOperationFailedException(Exchange exchange, HttpMethod method, int responseCode) throws IOException {
127            HttpOperationFailedException exception;
128            String uri = method.getURI().toString();
129            String statusText = method.getStatusLine() != null ? method.getStatusLine().getReasonPhrase() : null;
130            Map<String, String> headers = extractResponseHeaders(method.getResponseHeaders());
131            InputStream is = extractResponseBody(method, exchange);
132            // make a defensive copy of the response body in the exception so its detached from the cache
133            String copy = null;
134            if (is != null) {
135                copy = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, is);
136            }
137    
138            if (responseCode >= 300 && responseCode < 400) {
139                String redirectLocation;
140                Header locationHeader = method.getResponseHeader("location");
141                if (locationHeader != null) {
142                    redirectLocation = locationHeader.getValue();
143                    exception = new HttpOperationFailedException(uri, responseCode, statusText, redirectLocation, headers, copy);
144                } else {
145                    // no redirect location
146                    exception = new HttpOperationFailedException(uri, responseCode, statusText, null, headers, copy);
147                }
148            } else {
149                // internal server error (error code 500)
150                exception = new HttpOperationFailedException(uri, responseCode, statusText, null, headers, copy);
151            }
152    
153            return exception;
154        }
155    
156        /**
157         * Strategy when executing the method (calling the remote server).
158         *
159         * @param method    the method to execute
160         * @return the response code
161         * @throws IOException can be thrown
162         */
163        protected int executeMethod(HttpMethod method) throws IOException {
164            return httpClient.executeMethod(method);
165        }
166    
167        /**
168         * Extracts the response headers
169         *
170         * @param responseHeaders the headers
171         * @return the extracted headers or <tt>null</tt> if no headers existed
172         */
173        protected static Map<String, String> extractResponseHeaders(Header[] responseHeaders) {
174            if (responseHeaders == null || responseHeaders.length == 0) {
175                return null;
176            }
177    
178            Map<String, String> answer = new HashMap<String, String>();
179            for (Header header : responseHeaders) {
180                answer.put(header.getName(), header.getValue());
181            }
182    
183            return answer;
184        }
185    
186        /**
187         * Extracts the response from the method as a InputStream.
188         *
189         * @param method  the method that was executed
190         * @return  the response as a stream
191         * @throws IOException can be thrown
192         */
193        protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException {
194            InputStream is = method.getResponseBodyAsStream();
195            if (is == null) {
196                return null;
197            }
198    
199            Header header = method.getRequestHeader(Exchange.CONTENT_ENCODING);
200            String contentEncoding = header != null ? header.getValue() : null;
201    
202            is = GZIPHelper.toGZIPInputStream(contentEncoding, is);
203            return doExtractResponseBody(is, exchange);
204        }
205    
206        private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
207            try {
208                CachedOutputStream cos = new CachedOutputStream(exchange);
209                IOHelper.copy(is, cos);
210                return cos.getInputStream();
211            } finally {
212                ObjectHelper.close(is, "Extracting response body", LOG);            
213            }
214        }
215    
216        /**
217         * Creates the HttpMethod to use to call the remote server, either its GET or POST.
218         *
219         * @param exchange  the exchange
220         * @return the created method as either GET or POST
221         */
222        protected HttpMethod createMethod(Exchange exchange) {
223    
224            String url = HttpProducerHelper.createURL(exchange, getEndpoint());
225    
226            RequestEntity requestEntity = createRequestEntity(exchange);
227            HttpMethods methodToUse = HttpProducerHelper.createMethod(exchange, getEndpoint(), requestEntity != null);
228            HttpMethod method = methodToUse.createMethod(url);
229    
230            // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
231            String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
232            if (queryString == null) {
233                queryString = getEndpoint().getHttpUri().getQuery();
234            }
235            if (queryString != null) {
236                method.setQueryString(queryString);
237            }
238    
239            if (methodToUse.isEntityEnclosing()) {
240                ((EntityEnclosingMethod)method).setRequestEntity(requestEntity);
241                if (requestEntity != null && requestEntity.getContentType() == null) {
242                    if (LOG.isDebugEnabled()) {
243                        LOG.debug("No Content-Type provided for URL: " + url + " with exchange: " + exchange);
244                    }
245                }
246            }
247    
248            return method;
249        }
250    
251        /**
252         * Creates a holder object for the data to send to the remote server.
253         *
254         * @param exchange  the exchange with the IN message with data to send
255         * @return the data holder
256         */
257        protected RequestEntity createRequestEntity(Exchange exchange) {
258            Message in = exchange.getIn();
259            if (in.getBody() == null) {
260                return null;
261            }
262    
263            RequestEntity answer = in.getBody(RequestEntity.class);        
264            if (answer == null) {
265                try {
266                    String data = in.getBody(String.class);
267                    if (data != null) {
268                        String contentType = ExchangeHelper.getContentType(exchange);
269                        String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
270                        answer = new StringRequestEntity(data, contentType, charset);
271                    }
272                } catch (UnsupportedEncodingException e) {
273                    throw new RuntimeCamelException(e);
274                }
275            }
276            return answer;
277        }
278    
279        public HttpClient getHttpClient() {
280            return httpClient;
281        }
282    
283        public void setHttpClient(HttpClient httpClient) {
284            this.httpClient = httpClient;
285        }
286    }