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.Arrays;
024    import java.util.HashSet;
025    import java.util.Set;
026    
027    import org.apache.camel.Exchange;
028    import org.apache.camel.Message;
029    import org.apache.camel.NoTypeConversionAvailableException;
030    import org.apache.camel.Producer;
031    import org.apache.camel.RuntimeCamelException;
032    import org.apache.camel.component.http.helper.GZIPHelper;
033    import org.apache.camel.converter.stream.CachedOutputStream;
034    import org.apache.camel.impl.DefaultProducer;
035    import org.apache.camel.spi.HeaderFilterStrategy;
036    import org.apache.camel.util.ExchangeHelper;
037    import org.apache.camel.util.IOHelper;
038    import org.apache.camel.util.ObjectHelper;
039    import org.apache.commons.httpclient.Header;
040    import org.apache.commons.httpclient.HttpClient;
041    import org.apache.commons.httpclient.HttpMethod;
042    import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
043    import org.apache.commons.httpclient.methods.RequestEntity;
044    import org.apache.commons.httpclient.methods.StringRequestEntity;
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogFactory;
047    
048    import static org.apache.camel.component.http.HttpMethods.HTTP_METHOD;
049    
050    /**
051     * @version $Revision: 797918 $
052     */
053    public class HttpProducer extends DefaultProducer<HttpExchange> implements Producer<HttpExchange> {
054        public static final String HTTP_URI = "http.uri";
055        public static final String HTTP_RESPONSE_CODE = "http.responseCode";
056        public static final String QUERY = "org.apache.camel.component.http.query";    
057        // This should be a set of lower-case strings
058        @Deprecated
059        public static final Set<String> HEADERS_TO_SKIP =
060            new HashSet<String>(Arrays.asList("content-length", "content-type", HTTP_RESPONSE_CODE.toLowerCase()));
061        private static final transient Log LOG = LogFactory.getLog(HttpProducer.class);
062        private HttpClient httpClient;
063    
064        public HttpProducer(HttpEndpoint endpoint) {
065            super(endpoint);
066            httpClient = endpoint.createHttpClient();
067        }
068    
069        public void process(Exchange exchange) throws Exception {
070            HttpMethod method = createMethod(exchange);
071            Message in = exchange.getIn();
072            HeaderFilterStrategy strategy = ((HttpEndpoint)getEndpoint()).getHeaderFilterStrategy();
073    
074            // propagate headers as HTTP headers
075            for (String headerName : in.getHeaders().keySet()) {
076                String headerValue = in.getHeader(headerName, String.class);
077                if (strategy != null && !strategy.applyFilterToCamelHeaders(headerName, headerValue)) {
078                    method.addRequestHeader(headerName, headerValue);
079                }
080            }
081    
082            // lets store the result in the output message.
083            try {
084                if (LOG.isDebugEnabled()) {
085                    LOG.debug("Executing http " + method.getName() + " method: " + method.getURI().toString());
086                }
087                int responseCode = executeMethod(method);
088                if (LOG.isDebugEnabled()) {
089                    LOG.debug("Http responseCode: " + responseCode);
090                }
091    
092                if (responseCode >= 100 && responseCode < 300) {
093                    Message answer = exchange.getOut(true);
094    
095                    answer.setHeaders(in.getHeaders());
096                    answer.setHeader(HTTP_RESPONSE_CODE, responseCode);
097                    answer.setBody(extractResponseBody(method, exchange));
098    
099                    // propagate HTTP response headers
100                    Header[] headers = method.getResponseHeaders();
101                    for (Header header : headers) {
102                        String name = header.getName();
103                        String value = header.getValue();
104                        if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value)) {
105                            answer.setHeader(name, value);
106                        }
107                    }
108                } else {
109                    HttpOperationFailedException exception = null;
110                    Header[] headers = method.getResponseHeaders();
111                    InputStream is = extractResponseBody(method, exchange);
112    
113                    // make a defensive copy of the response body in the exception so its detached from the cache
114                    InputStream copy = null;
115                    if (is != null) {
116                        copy = new ByteArrayInputStream(exchange.getContext().getTypeConverter().convertTo(byte[].class, is));
117                        // close original stream so it can delete the temporary file if it has been spooled to disk by the CachedOutputStream
118                        is.close();
119                    }
120    
121                    if (responseCode >= 300 && responseCode < 400) {
122                        String redirectLocation;
123                        Header locationHeader = method.getResponseHeader("location");
124                        if (locationHeader != null) {
125                            redirectLocation = locationHeader.getValue();
126                            exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), redirectLocation, headers, copy);
127                        } else {
128                            // no redirect location
129                            exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy);
130                        }
131                    } else {
132                        // internal server error (error code 500)
133                        exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy);
134                    }
135    
136                    if (exception != null) {                    
137                        throw exception;
138                    }
139                }
140    
141            } finally {
142                method.releaseConnection();
143            }
144        }
145    
146        /**
147         * Strategy when executing the method (calling the remote server).
148         *
149         * @param method    the method to execute
150         * @return the response code
151         * @throws IOException can be thrown
152         */
153        protected int executeMethod(HttpMethod method) throws IOException {
154            return httpClient.executeMethod(method);
155        }
156    
157        /**
158         * Extracts the response from the method as a InputStream.
159         *
160         * @param method  the method that was executed
161         * @return  the response as a stream
162         * @throws IOException can be thrown
163         */
164        protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException {
165            InputStream is = method.getResponseBodyAsStream();
166            if (is == null) {
167                return null;
168            }
169    
170            Header header = method.getRequestHeader(GZIPHelper.CONTENT_ENCODING);
171            String contentEncoding = header != null ? header.getValue() : null;
172    
173            is = GZIPHelper.toGZIPInputStream(contentEncoding, is);
174            return doExtractResponseBody(is, exchange);
175        }
176    
177        private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
178            CachedOutputStream cos = null;
179            try {
180                cos = new CachedOutputStream(exchange.getContext().getProperties());
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(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(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(HTTP_URI, String.class);
217            if (uri == null) {
218                uri = ((HttpEndpoint)getEndpoint()).getHttpUri().toString();
219            }
220    
221            HttpMethod method = methodToUse.createMethod(uri);
222    
223            if (queryString != null) {
224                method.setQueryString(queryString);
225            }
226            if (methodToUse.isEntityEnclosing()) {
227                ((EntityEnclosingMethod)method).setRequestEntity(requestEntity);
228                if (requestEntity != null && requestEntity.getContentType() == null) {
229                    LOG.warn("Missing the ContentType in the request entity for the URI " + uri + ". The method is " + method);
230                }
231            }
232    
233            return method;
234        }
235    
236        /**
237         * Creates a holder object for the data to send to the remote server.
238         *
239         * @param exchange  the exchange with the IN message with data to send
240         * @return the data holder
241         */
242        protected RequestEntity createRequestEntity(Exchange exchange) {
243            Message in = exchange.getIn();
244            if (in.getBody() == null) {
245                return null;
246            }
247    
248            RequestEntity answer = null;
249            try {
250                answer = in.getBody(RequestEntity.class);
251            } catch (NoTypeConversionAvailableException ex) {
252                // ignore
253            }
254            if (answer == null) {
255                try {
256                    String data = in.getBody(String.class);
257                    if (data != null) {
258                        String contentType = ExchangeHelper.getContentType(exchange);
259                        String charset = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
260                        return new StringRequestEntity(data, contentType, charset);
261                    } else {
262                        // no data
263                        return null;
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    
280    }