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