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