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.zip.GZIPInputStream;
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: 779489 $
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            if (responseCode >= 300 && responseCode < 400) {
124                String redirectLocation;
125                Header locationHeader = method.getResponseHeader("location");
126                if (locationHeader != null) {
127                    redirectLocation = locationHeader.getValue();
128                    exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), redirectLocation, headers, is);
129                } else {
130                    // no redirect location
131                    exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, is);
132                }
133            } else {
134                // internal server error (error code 500)
135                exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, is);
136            }
137            return exception;
138        }
139    
140        /**
141         * Strategy when executing the method (calling the remote server).
142         *
143         * @param method    the method to execute
144         * @return the response code
145         * @throws IOException can be thrown
146         */
147        protected int executeMethod(HttpMethod method) throws IOException {
148            return httpClient.executeMethod(method);
149        }
150    
151        /**
152         * Extracts the response from the method as a InputStream.
153         *
154         * @param method  the method that was executed
155         * @return  the response as a stream
156         * @throws IOException can be thrown
157         */
158        protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException {
159            InputStream is = method.getResponseBodyAsStream();
160            if (is == null) {
161                return null;
162            }
163    
164            Header header = method.getRequestHeader(HttpConstants.CONTENT_ENCODING);
165            String contentEncoding = header != null ? header.getValue() : null;
166    
167            is = GZIPHelper.toGZIPInputStream(contentEncoding, is);
168            return doExtractResponseBody(is, exchange);
169        }
170    
171        private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
172            try {
173                CachedOutputStream cos = new CachedOutputStream(exchange.getContext().getProperties());
174                IOHelper.copy(is, cos);
175                return cos.getInputStream();
176            } finally {
177                ObjectHelper.close(is, "Extracting response body", LOG);            
178            }
179        }
180    
181        /**
182         * Creates the HttpMethod to use to call the remote server, either its GET or POST.
183         *
184         * @param exchange  the exchange
185         * @return the created method as either GET or POST
186         */
187        protected HttpMethod createMethod(Exchange exchange) {
188            // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
189            String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
190            if (queryString == null) {
191                queryString = ((HttpEndpoint)getEndpoint()).getHttpUri().getQuery();
192            }
193            RequestEntity requestEntity = createRequestEntity(exchange);
194    
195            // compute what method to use either GET or POST
196            HttpMethods methodToUse;
197            HttpMethods m = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
198            if (m != null) {
199                // always use what end-user provides in a header
200                methodToUse = m;
201            } else if (queryString != null) {
202                // if a query string is provided then use GET
203                methodToUse = HttpMethods.GET;
204            } else {
205                // fallback to POST if data, otherwise GET
206                methodToUse = requestEntity != null ? HttpMethods.POST : HttpMethods.GET;
207            }
208    
209            String uri = exchange.getIn().getHeader(Exchange.HTTP_URI, String.class);
210            if (uri == null) {
211                uri = ((HttpEndpoint)getEndpoint()).getHttpUri().toString();
212            }
213    
214            // append HTTP_PATH to HTTP_URI if it is provided in the header
215            String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class);
216            if (path != null) {
217                // make sure that there is exactly one "/" between HTTP_URI and HTTP_PATH
218                if (!uri.endsWith("/")) {
219                    uri = uri + "/";
220                }
221                if (path.startsWith("/")) {
222                    path = path.substring(1);
223                }
224                uri = uri.concat(path);
225            }
226    
227            HttpMethod method = methodToUse.createMethod(uri);
228    
229            if (queryString != null) {
230                method.setQueryString(queryString);
231            }
232            if (methodToUse.isEntityEnclosing()) {
233                ((EntityEnclosingMethod)method).setRequestEntity(requestEntity);
234                if (requestEntity != null && requestEntity.getContentType() == null) {
235                    if (LOG.isDebugEnabled()) {
236                        LOG.debug("No Content-Type provided for URI: " + uri + " with exchange: " + exchange);
237                    }
238                }
239            }
240    
241            return method;
242        }
243    
244        /**
245         * Creates a holder object for the data to send to the remote server.
246         *
247         * @param exchange  the exchange with the IN message with data to send
248         * @return the data holder
249         */
250        protected RequestEntity createRequestEntity(Exchange exchange) {
251            Message in = exchange.getIn();
252            if (in.getBody() == null) {
253                return null;
254            }
255    
256            RequestEntity answer = in.getBody(RequestEntity.class);        
257            if (answer == null) {
258                try {
259                    String data = in.getBody(String.class);
260                    if (data != null) {
261                        String contentType = ExchangeHelper.getContentType(exchange);
262                        String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
263                        answer = new StringRequestEntity(data, contentType, charset);
264                    }
265                } catch (UnsupportedEncodingException e) {
266                    throw new RuntimeCamelException(e);
267                }
268            }
269            return answer;
270        }
271    
272        public HttpClient getHttpClient() {
273            return httpClient;
274        }
275    
276        public void setHttpClient(HttpClient httpClient) {
277            this.httpClient = httpClient;
278        }
279    }