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.http4;
018
019 import java.io.File;
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.UnsupportedEncodingException;
023 import java.net.URI;
024 import java.net.URISyntaxException;
025 import java.util.HashMap;
026 import java.util.Map;
027
028 import org.apache.camel.Exchange;
029 import org.apache.camel.InvalidPayloadException;
030 import org.apache.camel.Message;
031 import org.apache.camel.RuntimeCamelException;
032 import org.apache.camel.component.file.GenericFile;
033 import org.apache.camel.component.http4.helper.GZIPHelper;
034 import org.apache.camel.component.http4.helper.HttpProducerHelper;
035 import org.apache.camel.converter.IOConverter;
036 import org.apache.camel.converter.stream.CachedOutputStream;
037 import org.apache.camel.impl.DefaultProducer;
038 import org.apache.camel.spi.HeaderFilterStrategy;
039 import org.apache.camel.util.ExchangeHelper;
040 import org.apache.camel.util.IOHelper;
041 import org.apache.commons.logging.Log;
042 import org.apache.commons.logging.LogFactory;
043 import org.apache.http.Header;
044 import org.apache.http.HttpEntity;
045 import org.apache.http.HttpResponse;
046 import org.apache.http.client.HttpClient;
047 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
048 import org.apache.http.client.methods.HttpRequestBase;
049 import org.apache.http.client.methods.HttpUriRequest;
050 import org.apache.http.entity.FileEntity;
051 import org.apache.http.entity.InputStreamEntity;
052 import org.apache.http.entity.StringEntity;
053 import org.apache.http.params.CoreProtocolPNames;
054
055 /**
056 * @version $Revision: 1021687 $
057 */
058 public class HttpProducer extends DefaultProducer {
059 private static final transient Log LOG = LogFactory.getLog(HttpProducer.class);
060 private HttpClient httpClient;
061 private boolean throwException;
062
063 public HttpProducer(HttpEndpoint endpoint) {
064 super(endpoint);
065 this.httpClient = endpoint.getHttpClient();
066 this.throwException = endpoint.isThrowExceptionOnFailure();
067 }
068
069 public void process(Exchange exchange) throws Exception {
070 if (((HttpEndpoint)getEndpoint()).isBridgeEndpoint()) {
071 exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE);
072 }
073 HttpRequestBase httpRequest = createMethod(exchange);
074 Message in = exchange.getIn();
075 String httpProtocolVersion = in.getHeader(Exchange.HTTP_PROTOCOL_VERSION, String.class);
076 if (httpProtocolVersion != null) {
077 // set the HTTP protocol version
078 httpRequest.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpProducerHelper.parserHttpVersion(httpProtocolVersion));
079 }
080 HeaderFilterStrategy strategy = getEndpoint().getHeaderFilterStrategy();
081
082 // propagate headers as HTTP headers
083 for (Map.Entry<String, Object> entry : in.getHeaders().entrySet()) {
084 String headerValue = in.getHeader(entry.getKey(), String.class);
085 if (strategy != null && !strategy.applyFilterToCamelHeaders(entry.getKey(), headerValue, exchange)) {
086 httpRequest.addHeader(entry.getKey(), headerValue);
087 }
088 }
089
090 // lets store the result in the output message.
091 HttpResponse httpResponse = null;
092 try {
093 if (LOG.isDebugEnabled()) {
094 LOG.debug("Executing http " + httpRequest.getMethod() + " method: " + httpRequest.getURI().toString());
095 }
096 httpResponse = executeMethod(httpRequest);
097 int responseCode = httpResponse.getStatusLine().getStatusCode();
098 if (LOG.isDebugEnabled()) {
099 LOG.debug("Http responseCode: " + responseCode);
100 }
101
102 if (throwException && (responseCode < 100 || responseCode >= 300)) {
103 throw populateHttpOperationFailedException(exchange, httpRequest, httpResponse, responseCode);
104 } else {
105 populateResponse(exchange, httpRequest, httpResponse, in, strategy, responseCode);
106 }
107 } finally {
108 if (httpResponse != null && httpResponse.getEntity() != null) {
109 try {
110 httpResponse.getEntity().consumeContent();
111 } catch (IOException e) {
112 // nothing we could do
113 }
114 }
115 }
116 }
117
118 @Override
119 public HttpEndpoint getEndpoint() {
120 return (HttpEndpoint) super.getEndpoint();
121 }
122
123 protected void populateResponse(Exchange exchange, HttpRequestBase httpRequest, HttpResponse httpResponse, Message in, HeaderFilterStrategy strategy, int responseCode) throws IOException {
124 Message answer = exchange.getOut();
125
126 answer.setHeaders(in.getHeaders());
127 answer.setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode);
128 answer.setBody(extractResponseBody(httpRequest, httpResponse, exchange));
129
130 // propagate HTTP response headers
131 Header[] headers = httpResponse.getAllHeaders();
132 for (Header header : headers) {
133 String name = header.getName();
134 String value = header.getValue();
135 if (name.toLowerCase().equals("content-type")) {
136 name = Exchange.CONTENT_TYPE;
137 }
138 if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value, exchange)) {
139 answer.setHeader(name, value);
140 }
141 }
142 }
143
144 protected HttpOperationFailedException populateHttpOperationFailedException(Exchange exchange, HttpRequestBase httpRequest, HttpResponse httpResponse, int responseCode) throws IOException {
145 HttpOperationFailedException exception;
146 String uri = httpRequest.getURI().toString();
147 String statusText = httpResponse.getStatusLine() != null ? httpResponse.getStatusLine().getReasonPhrase() : null;
148 Map<String, String> headers = extractResponseHeaders(httpResponse.getAllHeaders());
149 InputStream is = extractResponseBody(httpRequest, httpResponse, exchange);
150 // make a defensive copy of the response body in the exception so its detached from the cache
151 String copy = null;
152 if (is != null) {
153 copy = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, is);
154 }
155
156 Header locationHeader = httpResponse.getFirstHeader("location");
157 if (locationHeader != null && (responseCode >= 300 && responseCode < 400)) {
158 exception = new HttpOperationFailedException(uri, responseCode, statusText, locationHeader.getValue(), headers, copy);
159 } else {
160 exception = new HttpOperationFailedException(uri, responseCode, statusText, null, headers, copy);
161 }
162
163 return exception;
164 }
165
166 /**
167 * Strategy when executing the method (calling the remote server).
168 *
169 * @param httpRequest the http Request to execute
170 * @return the response
171 * @throws IOException can be thrown
172 */
173 protected HttpResponse executeMethod(HttpUriRequest httpRequest) throws IOException {
174 return httpClient.execute(httpRequest);
175 }
176
177 /**
178 * Extracts the response headers
179 *
180 * @param responseHeaders the headers
181 * @return the extracted headers or <tt>null</tt> if no headers existed
182 */
183 protected static Map<String, String> extractResponseHeaders(Header[] responseHeaders) {
184 if (responseHeaders == null || responseHeaders.length == 0) {
185 return null;
186 }
187
188 Map<String, String> answer = new HashMap<String, String>();
189 for (Header header : responseHeaders) {
190 answer.put(header.getName(), header.getValue());
191 }
192
193 return answer;
194 }
195
196 /**
197 * Extracts the response from the method as a InputStream.
198 *
199 * @param httpRequest the method that was executed
200 * @return the response as a stream
201 * @throws IOException can be thrown
202 */
203 protected static InputStream extractResponseBody(HttpRequestBase httpRequest, HttpResponse httpResponse, Exchange exchange) throws IOException {
204 HttpEntity entity = httpResponse.getEntity();
205 if (entity == null) {
206 return null;
207 }
208
209 InputStream is = entity.getContent();
210 if (is == null) {
211 return null;
212 }
213
214 Header header = httpResponse.getFirstHeader(Exchange.CONTENT_ENCODING);
215 String contentEncoding = header != null ? header.getValue() : null;
216
217 if (!exchange.getProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.FALSE, Boolean.class)) {
218 is = GZIPHelper.uncompressGzip(contentEncoding, is);
219 }
220 // Honor the character encoding
221 header = httpRequest.getFirstHeader("content-type");
222 if (header != null) {
223 String contentType = header.getValue();
224 // find the charset and set it to the Exchange
225 int index = contentType.indexOf("charset=");
226 if (index > 0) {
227 String charset = contentType.substring(index + 8);
228 exchange.setProperty(Exchange.CHARSET_NAME, IOConverter.normalizeCharset(charset));
229 }
230 }
231 return doExtractResponseBody(is, exchange);
232 }
233
234 private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException {
235 // As httpclient is using a AutoCloseInputStream, it will be closed when the connection is closed
236 // we need to cache the stream for it.
237 try {
238 // This CachedOutputStream will not be closed when the exchange is onCompletion
239 CachedOutputStream cos = new CachedOutputStream(exchange, false);
240 IOHelper.copy(is, cos);
241 // When the InputStream is closed, the CachedOutputStream will be closed
242 return cos.getWrappedInputStream();
243 } finally {
244 IOHelper.close(is, "Extracting response body", LOG);
245 }
246 }
247
248 /**
249 * Creates the HttpMethod to use to call the remote server, either its GET or POST.
250 *
251 * @param exchange the exchange
252 * @return the created method as either GET or POST
253 * @throws URISyntaxException is thrown if the URI is invalid
254 * @throws org.apache.camel.InvalidPayloadException is thrown if message body cannot
255 * be converted to a type supported by HttpClient
256 */
257 protected HttpRequestBase createMethod(Exchange exchange) throws URISyntaxException, InvalidPayloadException {
258 String url = HttpProducerHelper.createURL(exchange, getEndpoint());
259 URI uri = new URI(url);
260
261 HttpEntity requestEntity = createRequestEntity(exchange);
262 HttpMethods methodToUse = HttpProducerHelper.createMethod(exchange, getEndpoint(), requestEntity != null);
263
264 // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
265 String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
266 if (queryString == null) {
267 queryString = getEndpoint().getHttpUri().getRawQuery();
268 }
269
270 StringBuilder builder = new StringBuilder(uri.getScheme()).append("://").append(uri.getHost());
271
272 if (uri.getPort() != -1) {
273 builder.append(":").append(uri.getPort());
274 }
275
276 if (uri.getPath() != null) {
277 builder.append(uri.getRawPath());
278 }
279
280 if (queryString != null) {
281 builder.append('?');
282 builder.append(queryString);
283 }
284
285 HttpRequestBase httpRequest = methodToUse.createMethod(builder.toString());
286
287 if (methodToUse.isEntityEnclosing()) {
288 ((HttpEntityEnclosingRequestBase) httpRequest).setEntity(requestEntity);
289 if (requestEntity != null && requestEntity.getContentType() == null) {
290 if (LOG.isDebugEnabled()) {
291 LOG.debug("No Content-Type provided for URL: " + url + " with exchange: " + exchange);
292 }
293 }
294 }
295
296 return httpRequest;
297 }
298
299 /**
300 * Creates a holder object for the data to send to the remote server.
301 *
302 * @param exchange the exchange with the IN message with data to send
303 * @return the data holder
304 * @throws org.apache.camel.InvalidPayloadException is thrown if message body cannot
305 * be converted to a type supported by HttpClient
306 */
307 protected HttpEntity createRequestEntity(Exchange exchange) throws InvalidPayloadException {
308 Message in = exchange.getIn();
309 if (in.getBody() == null) {
310 return null;
311 }
312
313 HttpEntity answer = in.getBody(HttpEntity.class);
314 if (answer == null) {
315 try {
316 Object data = in.getBody();
317 if (data != null) {
318 String contentType = ExchangeHelper.getContentType(exchange);
319
320 // file based (could potentially also be a FTP file etc)
321 if (data instanceof File || data instanceof GenericFile) {
322 File file = in.getBody(File.class);
323 if (file != null) {
324 answer = new FileEntity(file, contentType);
325 }
326 } else if (data instanceof String) {
327 // be a bit careful with String as any type can most likely be converted to String
328 // so we only do an instanceof check and accept String if the body is really a String
329 // do not fallback to use the default charset as it can influence the request
330 // (for example application/x-www-form-urlencoded forms being sent)
331 String charset = IOConverter.getCharsetName(exchange, false);
332 answer = new StringEntity((String)data, charset);
333 if (contentType != null) {
334 ((StringEntity)answer).setContentType(contentType);
335 }
336 }
337
338 // fallback as input stream
339 if (answer == null) {
340 // force the body as an input stream since this is the fallback
341 in.getMandatoryBody(InputStream.class);
342 answer = new InputStreamEntity(in.getBody(InputStream.class), -1);
343 if (contentType != null) {
344 ((InputStreamEntity)answer).setContentType(contentType);
345 }
346 }
347 }
348 } catch (UnsupportedEncodingException e) {
349 throw new RuntimeCamelException(e);
350 }
351 }
352 return answer;
353 }
354
355 public HttpClient getHttpClient() {
356 return httpClient;
357 }
358
359 public void setHttpClient(HttpClient httpClient) {
360 this.httpClient = httpClient;
361 }
362
363 }