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    
024    import org.apache.camel.Exchange;
025    import org.apache.camel.Message;
026    import org.apache.camel.RuntimeCamelException;
027    import org.apache.camel.component.http.helper.GZIPHelper;
028    import org.apache.camel.converter.stream.CachedOutputStream;
029    import org.apache.camel.impl.DefaultProducer;
030    import org.apache.camel.spi.HeaderFilterStrategy;
031    import org.apache.camel.util.ExchangeHelper;
032    import org.apache.camel.util.IOHelper;
033    import org.apache.camel.util.ObjectHelper;
034    import org.apache.commons.httpclient.Header;
035    import org.apache.commons.httpclient.HttpClient;
036    import org.apache.commons.httpclient.HttpMethod;
037    import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
038    import org.apache.commons.httpclient.methods.RequestEntity;
039    import org.apache.commons.httpclient.methods.StringRequestEntity;
040    import org.apache.commons.logging.Log;
041    import org.apache.commons.logging.LogFactory;
042    
043    /**
044     * @version $Revision: 798865 $
045     */
046    public class HttpProducer extends DefaultProducer {
047        private static final transient Log LOG = LogFactory.getLog(HttpProducer.class);
048        private HttpClient httpClient;
049        private boolean throwException;
050    
051        public HttpProducer(HttpEndpoint endpoint) {
052            super(endpoint);
053            this.httpClient = endpoint.createHttpClient();
054            this.throwException = endpoint.isThrowExceptionOnFailure();
055        }
056    
057        public void process(Exchange exchange) throws Exception {
058            HttpMethod method = createMethod(exchange);
059            Message in = exchange.getIn();
060            HeaderFilterStrategy strategy = ((HttpEndpoint)getEndpoint()).getHeaderFilterStrategy();
061    
062            // propagate headers as HTTP headers
063            for (String headerName : in.getHeaders().keySet()) {
064                String headerValue = in.getHeader(headerName, String.class);
065                if (strategy != null && !strategy.applyFilterToCamelHeaders(headerName, headerValue, exchange)) {
066                    method.addRequestHeader(headerName, headerValue);
067                }
068            }
069    
070            // lets store the result in the output message.
071            try {
072                if (LOG.isDebugEnabled()) {
073                    LOG.debug("Executing http " + method.getName() + " method: " + method.getURI().toString());
074                }
075                int responseCode = executeMethod(method);
076                if (LOG.isDebugEnabled()) {
077                    LOG.debug("Http responseCode: " + responseCode);
078                }
079    
080                if (!throwException) {
081                    // if we do not use failed exception then populate response for all response codes
082                    populateResponse(exchange, method, in, strategy, responseCode);
083                } else {
084                    if (responseCode >= 100 && responseCode < 300) {
085                        // only populate reponse for OK response
086                        populateResponse(exchange, method, in, strategy, responseCode);
087                    } else {
088                        // operation failed so populate exception to throw
089                        throw populateHttpOperationFailedException(exchange, method, responseCode);
090                    }
091                }
092    
093            } finally {
094                method.releaseConnection();
095            }
096        }
097    
098        protected void populateResponse(Exchange exchange, HttpMethod method, Message in, HeaderFilterStrategy strategy, int responseCode) throws IOException {
099            Message answer = exchange.getOut();
100    
101            answer.setHeaders(in.getHeaders());
102            answer.setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode);
103            answer.setBody(extractResponseBody(method, exchange));
104    
105            // propagate HTTP response headers
106            Header[] headers = method.getResponseHeaders();
107            for (Header header : headers) {
108                String name = header.getName();
109                String value = header.getValue();
110                if (name.toLowerCase().equals("content-type")) {
111                    name = Exchange.CONTENT_TYPE;
112                }
113                if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value, exchange)) {
114                    answer.setHeader(name, value);
115                }
116            }
117        }
118    
119        protected HttpOperationFailedException populateHttpOperationFailedException(Exchange exchange, HttpMethod method, int responseCode) throws IOException {
120            HttpOperationFailedException exception;
121            Header[] headers = method.getResponseHeaders();
122            InputStream is = extractResponseBody(method, exchange);
123            // make a defensive copy of the response body in the exception so its detached from the cache
124            InputStream copy = null;
125            if (is != null) {
126                copy = new ByteArrayInputStream(exchange.getContext().getTypeConverter().convertTo(byte[].class, is));
127            }
128    
129            if (responseCode >= 300 && responseCode < 400) {
130                String redirectLocation;
131                Header locationHeader = method.getResponseHeader("location");
132                if (locationHeader != null) {
133                    redirectLocation = locationHeader.getValue();
134                    exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), redirectLocation, headers, copy);
135                } else {
136                    // no redirect location
137                    exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy);
138                }
139            } else {
140                // internal server error (error code 500)
141                exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy);
142            }
143    
144            return exception;
145        }
146    
147        /**
148         * Strategy when executing the method (calling the remote server).
149         *
150         * @param method    the method to execute
151         * @return the response code
152         * @throws IOException can be thrown
153         */
154        protected int executeMethod(HttpMethod method) throws IOException {
155            return httpClient.executeMethod(method);
156        }
157    
158        /**
159         * Extracts the response from the method as a InputStream.
160         *
161         * @param method  the method that was executed
162         * @return  the response as a stream
163         * @throws IOException can be thrown
164         */
165        protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException {
166            InputStream is = method.getResponseBodyAsStream();
167            if (is == null) {
168                return null;
169            }
170    
171            Header header = method.getRequestHeader(Exchange.CONTENT_ENCODING);
172            String contentEncoding = header != null ? header.getValue() : null;
173    
174            is = GZIPHelper.toGZIPInputStream(contentEncoding, is);
175            return doExtractResponseBody(is, exchange);
176        }
177    
178        private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
179            try {
180                CachedOutputStream cos = new CachedOutputStream(exchange);
181                IOHelper.copy(is, cos);
182                return cos.getInputStream();
183            } finally {
184                ObjectHelper.close(is, "Extracting response body", LOG);            
185            }
186        }
187    
188        /**
189         * Creates the HttpMethod to use to call the remote server, either its GET or POST.
190         *
191         * @param exchange  the exchange
192         * @return the created method as either GET or POST
193         */
194        protected HttpMethod createMethod(Exchange exchange) {
195            // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
196            String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
197            if (queryString == null) {
198                queryString = ((HttpEndpoint)getEndpoint()).getHttpUri().getQuery();
199            }
200            RequestEntity requestEntity = createRequestEntity(exchange);
201    
202            // compute what method to use either GET or POST
203            HttpMethods methodToUse;
204            HttpMethods m = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
205            if (m != null) {
206                // always use what end-user provides in a header
207                methodToUse = m;
208            } else if (queryString != null) {
209                // if a query string is provided then use GET
210                methodToUse = HttpMethods.GET;
211            } else {
212                // fallback to POST if data, otherwise GET
213                methodToUse = requestEntity != null ? HttpMethods.POST : HttpMethods.GET;
214            }
215    
216            String uri = exchange.getIn().getHeader(Exchange.HTTP_URI, String.class);
217            if (uri == null) {
218                uri = ((HttpEndpoint)getEndpoint()).getHttpUri().toString();
219            }
220    
221            // append HTTP_PATH to HTTP_URI if it is provided in the header
222            String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class);
223            if (path != null) {
224                // make sure that there is exactly one "/" between HTTP_URI and HTTP_PATH
225                if (!uri.endsWith("/")) {
226                    uri = uri + "/";
227                }
228                if (path.startsWith("/")) {
229                    path = path.substring(1);
230                }
231                uri = uri.concat(path);
232            }
233    
234            HttpMethod method = methodToUse.createMethod(uri);
235    
236            if (queryString != null) {
237                method.setQueryString(queryString);
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 URI: " + uri + " 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    }