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