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.ahc;
018
019 import java.io.ByteArrayInputStream;
020 import java.io.ByteArrayOutputStream;
021 import java.io.File;
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.io.Serializable;
025 import java.io.UnsupportedEncodingException;
026 import java.util.LinkedHashMap;
027 import java.util.List;
028 import java.util.Map;
029
030 import com.ning.http.client.BodyGenerator;
031 import com.ning.http.client.HttpResponseHeaders;
032 import com.ning.http.client.HttpResponseStatus;
033 import com.ning.http.client.Request;
034 import com.ning.http.client.RequestBuilder;
035 import com.ning.http.client.generators.ByteArrayBodyGenerator;
036 import com.ning.http.client.generators.FileBodyGenerator;
037 import com.ning.http.client.generators.InputStreamBodyGenerator;
038 import org.apache.camel.CamelExchangeException;
039 import org.apache.camel.Exchange;
040 import org.apache.camel.Message;
041 import org.apache.camel.component.ahc.helper.AhcHelper;
042 import org.apache.camel.component.file.GenericFile;
043 import org.apache.camel.converter.IOConverter;
044 import org.apache.camel.spi.HeaderFilterStrategy;
045 import org.apache.camel.util.ExchangeHelper;
046 import org.apache.camel.util.GZIPHelper;
047 import org.apache.camel.util.IOHelper;
048 import org.slf4j.Logger;
049 import org.slf4j.LoggerFactory;
050
051 public class DefaultAhcBinding implements AhcBinding {
052
053 protected final transient Logger log = LoggerFactory.getLogger(this.getClass());
054
055 @Override
056 public Request prepareRequest(AhcEndpoint endpoint, Exchange exchange) throws CamelExchangeException {
057 if (endpoint.isBridgeEndpoint()) {
058 exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE);
059 }
060
061 RequestBuilder builder = new RequestBuilder();
062 try {
063 String url = AhcHelper.createURL(exchange, endpoint);
064 log.trace("Setting url {}", url);
065 builder.setUrl(url);
066 } catch (Exception e) {
067 throw new CamelExchangeException("Error creating URL", exchange, e);
068 }
069 String method = extractMethod(exchange);
070 log.trace("Setting method {}", method);
071 builder.setMethod(method);
072
073 populateHeaders(builder, endpoint, exchange);
074 populateBody(builder, endpoint, exchange);
075
076 return builder.build();
077 }
078
079 protected String extractMethod(Exchange exchange) {
080 // prefer method from header
081 String method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, String.class);
082 if (method != null) {
083 return method;
084 }
085
086 // if there is a body then do a POST otherwise a GET
087 boolean hasBody = exchange.getIn().getBody() != null;
088 return hasBody ? "POST" : "GET";
089 }
090
091 protected void populateHeaders(RequestBuilder builder, AhcEndpoint endpoint, Exchange exchange) {
092 HeaderFilterStrategy strategy = endpoint.getHeaderFilterStrategy();
093
094 // propagate headers as HTTP headers
095 for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
096 String headerValue = exchange.getIn().getHeader(entry.getKey(), String.class);
097 if (strategy != null && !strategy.applyFilterToCamelHeaders(entry.getKey(), headerValue, exchange)) {
098 log.trace("Adding header {} = {}", entry.getKey(), headerValue);
099 builder.addHeader(entry.getKey(), headerValue);
100 }
101 }
102 }
103
104 protected void populateBody(RequestBuilder builder, AhcEndpoint endpoint, Exchange exchange) throws CamelExchangeException {
105 Message in = exchange.getIn();
106 if (in.getBody() == null) {
107 return;
108 }
109
110 String contentType = ExchangeHelper.getContentType(exchange);
111 BodyGenerator body = in.getBody(BodyGenerator.class);
112 String charset = IOConverter.getCharsetName(exchange, false);
113
114 if (body == null) {
115 try {
116 Object data = in.getBody();
117 if (data != null) {
118 if (contentType != null && AhcConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) {
119 // serialized java object
120 Serializable obj = in.getMandatoryBody(Serializable.class);
121 // write object to output stream
122 ByteArrayOutputStream bos = new ByteArrayOutputStream();
123 AhcHelper.writeObjectToStream(bos, obj);
124 byte[] bytes = bos.toByteArray();
125 body = new ByteArrayBodyGenerator(bytes);
126 IOHelper.close(bos);
127 } else if (data instanceof File || data instanceof GenericFile) {
128 // file based (could potentially also be a FTP file etc)
129 File file = in.getBody(File.class);
130 if (file != null) {
131 body = new FileBodyGenerator(file);
132 }
133 } else if (data instanceof String) {
134 // be a bit careful with String as any type can most likely be converted to String
135 // so we only do an instanceof check and accept String if the body is really a String
136 // do not fallback to use the default charset as it can influence the request
137 // (for example application/x-www-form-urlencoded forms being sent)
138 if (charset != null) {
139 body = new ByteArrayBodyGenerator(((String) data).getBytes(charset));
140 } else {
141 body = new ByteArrayBodyGenerator(((String) data).getBytes());
142 }
143 }
144 // fallback as input stream
145 if (body == null) {
146 // force the body as an input stream since this is the fallback
147 InputStream is = in.getMandatoryBody(InputStream.class);
148 body = new InputStreamBodyGenerator(is);
149 }
150 }
151 } catch (UnsupportedEncodingException e) {
152 throw new CamelExchangeException("Error creating BodyGenerator from message body", exchange, e);
153 } catch (IOException e) {
154 throw new CamelExchangeException("Error serializing message body", exchange, e);
155 }
156 }
157
158 if (body != null) {
159 log.trace("Setting body {}", body);
160 builder.setBody(body);
161 }
162 if (charset != null) {
163 log.trace("Setting body charset {}", charset);
164 builder.setBodyEncoding(charset);
165 }
166 // must set content type, even if its null, otherwise it may default to
167 // application/x-www-form-urlencoded which may not be your intention
168 log.trace("Setting Content-Type {}", contentType);
169 builder.setHeader(Exchange.CONTENT_TYPE, contentType);
170 }
171
172 @Override
173 public void onThrowable(AhcEndpoint endpoint, Exchange exchange, Throwable t) throws Exception {
174 exchange.setException(t);
175 }
176
177 @Override
178 public void onStatusReceived(AhcEndpoint endpoint, Exchange exchange, HttpResponseStatus responseStatus) throws Exception {
179 exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, responseStatus.getStatusCode());
180 }
181
182 @Override
183 public void onHeadersReceived(AhcEndpoint endpoint, Exchange exchange, HttpResponseHeaders headers) throws Exception {
184 for (Map.Entry<String, List<String>> entry : headers.getHeaders().entrySet()) {
185 String key = entry.getKey();
186 List<String> value = entry.getValue();
187 if (value.size() == 1) {
188 exchange.getOut().getHeaders().put(key, value.get(0));
189 } else {
190 exchange.getOut().getHeaders().put(key, value);
191 }
192 }
193 }
194
195 @Override
196 public void onComplete(AhcEndpoint endpoint, Exchange exchange, String url, ByteArrayOutputStream os, int contentLength,
197 int statusCode, String statusText) throws Exception {
198 // copy from output stream to input stream
199 os.flush();
200 os.close();
201 InputStream is = new ByteArrayInputStream(os.toByteArray());
202
203 String contentEncoding = exchange.getOut().getHeader(Exchange.CONTENT_ENCODING, String.class);
204 if (!exchange.getProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.FALSE, Boolean.class)) {
205 is = GZIPHelper.uncompressGzip(contentEncoding, is);
206 }
207
208 // Honor the character encoding
209 String contentType = exchange.getOut().getHeader(Exchange.CONTENT_TYPE, String.class);
210 if (contentType != null) {
211 // find the charset and set it to the Exchange
212 AhcHelper.setCharsetFromContentType(contentType, exchange);
213 }
214
215 Object body = is;
216 // if content type is a serialized java object then de-serialize it back to a Java object
217 if (contentType != null && contentType.equals(AhcConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT)) {
218 body = AhcHelper.deserializeJavaObjectFromStream(is);
219 }
220
221 if (!endpoint.isThrowExceptionOnFailure()) {
222 // if we do not use failed exception then populate response for all response codes
223 populateResponse(exchange, body, contentLength, statusCode);
224 } else {
225 if (statusCode >= 100 && statusCode < 300) {
226 // only populate response for OK response
227 populateResponse(exchange, body, contentLength, statusCode);
228 } else {
229 // operation failed so populate exception to throw
230 throw populateHttpOperationFailedException(endpoint, exchange, url, body, contentLength, statusCode, statusText);
231 }
232 }
233 }
234
235 private Exception populateHttpOperationFailedException(AhcEndpoint endpoint, Exchange exchange, String url,
236 Object body, int contentLength,
237 int statusCode, String statusText) {
238 Exception answer;
239
240 if (endpoint.isTransferException() && body != null && body instanceof Exception) {
241 // if the response was a serialized exception then use that
242 return (Exception) body;
243 }
244
245 // make a defensive copy of the response body in the exception so its detached from the cache
246 String copy = null;
247 if (body != null) {
248 copy = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, body);
249 }
250
251 Map<String, String> headers = extractResponseHeaders(exchange);
252
253 if (statusCode >= 300 && statusCode < 400) {
254 String redirectLocation = exchange.getOut().getHeader("Location", String.class);
255 if (redirectLocation != null) {
256 answer = new AhcOperationFailedException(url, statusCode, statusText, redirectLocation, headers, copy);
257 } else {
258 // no redirect location
259 answer = new AhcOperationFailedException(url, statusCode, statusText, null, headers, copy);
260 }
261 } else {
262 // internal server error (error code 500)
263 answer = new AhcOperationFailedException(url, statusCode, statusText, null, headers, copy);
264 }
265
266 return answer;
267 }
268
269 private Map<String, String> extractResponseHeaders(Exchange exchange) {
270 Map<String, String> answer = new LinkedHashMap<String, String>();
271 for (Map.Entry<String, Object> entry : exchange.getOut().getHeaders().entrySet()) {
272 String key = entry.getKey();
273 String value = exchange.getContext().getTypeConverter().convertTo(String.class, entry.getValue());
274 if (value != null) {
275 answer.put(key, value);
276 }
277 }
278 return answer;
279 }
280
281 private void populateResponse(Exchange exchange, Object body, int contentLength, int responseCode) {
282 exchange.getOut().setBody(body);
283 exchange.getOut().setHeader(Exchange.CONTENT_LENGTH, contentLength);
284 }
285 }