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