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 024 import org.apache.camel.Exchange; 025 import org.apache.camel.Message; 026 import org.apache.camel.RuntimeCamelException; 027 import org.apache.camel.component.http.helper.GZIPHelper; 028 import org.apache.camel.converter.stream.CachedOutputStream; 029 import org.apache.camel.impl.DefaultProducer; 030 import org.apache.camel.spi.HeaderFilterStrategy; 031 import org.apache.camel.util.ExchangeHelper; 032 import org.apache.camel.util.IOHelper; 033 import org.apache.camel.util.ObjectHelper; 034 import org.apache.commons.httpclient.Header; 035 import org.apache.commons.httpclient.HttpClient; 036 import org.apache.commons.httpclient.HttpMethod; 037 import org.apache.commons.httpclient.methods.EntityEnclosingMethod; 038 import org.apache.commons.httpclient.methods.RequestEntity; 039 import org.apache.commons.httpclient.methods.StringRequestEntity; 040 import org.apache.commons.logging.Log; 041 import org.apache.commons.logging.LogFactory; 042 043 /** 044 * @version $Revision: 798865 $ 045 */ 046 public class HttpProducer extends DefaultProducer { 047 private static final transient Log LOG = LogFactory.getLog(HttpProducer.class); 048 private HttpClient httpClient; 049 private boolean throwException; 050 051 public HttpProducer(HttpEndpoint endpoint) { 052 super(endpoint); 053 this.httpClient = endpoint.createHttpClient(); 054 this.throwException = endpoint.isThrowExceptionOnFailure(); 055 } 056 057 public void process(Exchange exchange) throws Exception { 058 HttpMethod method = createMethod(exchange); 059 Message in = exchange.getIn(); 060 HeaderFilterStrategy strategy = ((HttpEndpoint)getEndpoint()).getHeaderFilterStrategy(); 061 062 // propagate headers as HTTP headers 063 for (String headerName : in.getHeaders().keySet()) { 064 String headerValue = in.getHeader(headerName, String.class); 065 if (strategy != null && !strategy.applyFilterToCamelHeaders(headerName, headerValue, exchange)) { 066 method.addRequestHeader(headerName, headerValue); 067 } 068 } 069 070 // lets store the result in the output message. 071 try { 072 if (LOG.isDebugEnabled()) { 073 LOG.debug("Executing http " + method.getName() + " method: " + method.getURI().toString()); 074 } 075 int responseCode = executeMethod(method); 076 if (LOG.isDebugEnabled()) { 077 LOG.debug("Http responseCode: " + responseCode); 078 } 079 080 if (!throwException) { 081 // if we do not use failed exception then populate response for all response codes 082 populateResponse(exchange, method, in, strategy, responseCode); 083 } else { 084 if (responseCode >= 100 && responseCode < 300) { 085 // only populate reponse for OK response 086 populateResponse(exchange, method, in, strategy, responseCode); 087 } else { 088 // operation failed so populate exception to throw 089 throw populateHttpOperationFailedException(exchange, method, responseCode); 090 } 091 } 092 093 } finally { 094 method.releaseConnection(); 095 } 096 } 097 098 protected void populateResponse(Exchange exchange, HttpMethod method, Message in, HeaderFilterStrategy strategy, int responseCode) throws IOException { 099 Message answer = exchange.getOut(); 100 101 answer.setHeaders(in.getHeaders()); 102 answer.setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode); 103 answer.setBody(extractResponseBody(method, exchange)); 104 105 // propagate HTTP response headers 106 Header[] headers = method.getResponseHeaders(); 107 for (Header header : headers) { 108 String name = header.getName(); 109 String value = header.getValue(); 110 if (name.toLowerCase().equals("content-type")) { 111 name = Exchange.CONTENT_TYPE; 112 } 113 if (strategy != null && !strategy.applyFilterToExternalHeaders(name, value, exchange)) { 114 answer.setHeader(name, value); 115 } 116 } 117 } 118 119 protected HttpOperationFailedException populateHttpOperationFailedException(Exchange exchange, HttpMethod method, int responseCode) throws IOException { 120 HttpOperationFailedException exception; 121 Header[] headers = method.getResponseHeaders(); 122 InputStream is = extractResponseBody(method, exchange); 123 // make a defensive copy of the response body in the exception so its detached from the cache 124 InputStream copy = null; 125 if (is != null) { 126 copy = new ByteArrayInputStream(exchange.getContext().getTypeConverter().convertTo(byte[].class, is)); 127 } 128 129 if (responseCode >= 300 && responseCode < 400) { 130 String redirectLocation; 131 Header locationHeader = method.getResponseHeader("location"); 132 if (locationHeader != null) { 133 redirectLocation = locationHeader.getValue(); 134 exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), redirectLocation, headers, copy); 135 } else { 136 // no redirect location 137 exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy); 138 } 139 } else { 140 // internal server error (error code 500) 141 exception = new HttpOperationFailedException(responseCode, method.getStatusLine(), headers, copy); 142 } 143 144 return exception; 145 } 146 147 /** 148 * Strategy when executing the method (calling the remote server). 149 * 150 * @param method the method to execute 151 * @return the response code 152 * @throws IOException can be thrown 153 */ 154 protected int executeMethod(HttpMethod method) throws IOException { 155 return httpClient.executeMethod(method); 156 } 157 158 /** 159 * Extracts the response from the method as a InputStream. 160 * 161 * @param method the method that was executed 162 * @return the response as a stream 163 * @throws IOException can be thrown 164 */ 165 protected static InputStream extractResponseBody(HttpMethod method, Exchange exchange) throws IOException { 166 InputStream is = method.getResponseBodyAsStream(); 167 if (is == null) { 168 return null; 169 } 170 171 Header header = method.getRequestHeader(Exchange.CONTENT_ENCODING); 172 String contentEncoding = header != null ? header.getValue() : null; 173 174 is = GZIPHelper.toGZIPInputStream(contentEncoding, is); 175 return doExtractResponseBody(is, exchange); 176 } 177 178 private static InputStream doExtractResponseBody(InputStream is, Exchange exchange) throws IOException { 179 try { 180 CachedOutputStream cos = new CachedOutputStream(exchange); 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(Exchange.HTTP_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(Exchange.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(Exchange.HTTP_URI, String.class); 217 if (uri == null) { 218 uri = ((HttpEndpoint)getEndpoint()).getHttpUri().toString(); 219 } 220 221 // append HTTP_PATH to HTTP_URI if it is provided in the header 222 String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class); 223 if (path != null) { 224 // make sure that there is exactly one "/" between HTTP_URI and HTTP_PATH 225 if (!uri.endsWith("/")) { 226 uri = uri + "/"; 227 } 228 if (path.startsWith("/")) { 229 path = path.substring(1); 230 } 231 uri = uri.concat(path); 232 } 233 234 HttpMethod method = methodToUse.createMethod(uri); 235 236 if (queryString != null) { 237 method.setQueryString(queryString); 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 URI: " + uri + " 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 }