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