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