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