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