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