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.File; 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.PrintWriter; 023 import java.io.Serializable; 024 import java.net.URLDecoder; 025 import java.util.Enumeration; 026 import java.util.Iterator; 027 import java.util.Map; 028 import javax.activation.DataHandler; 029 import javax.servlet.ServletOutputStream; 030 import javax.servlet.http.HttpServletRequest; 031 import javax.servlet.http.HttpServletResponse; 032 033 import org.apache.camel.Endpoint; 034 import org.apache.camel.Exchange; 035 import org.apache.camel.InvalidPayloadException; 036 import org.apache.camel.Message; 037 import org.apache.camel.RuntimeCamelException; 038 import org.apache.camel.StreamCache; 039 import org.apache.camel.component.http.helper.CamelFileDataSource; 040 import org.apache.camel.component.http.helper.HttpHelper; 041 import org.apache.camel.spi.HeaderFilterStrategy; 042 import org.apache.camel.util.GZIPHelper; 043 import org.apache.camel.util.IOHelper; 044 import org.apache.camel.util.MessageHelper; 045 import org.apache.camel.util.ObjectHelper; 046 import org.slf4j.Logger; 047 import org.slf4j.LoggerFactory; 048 049 /** 050 * Binding between {@link HttpMessage} and {@link HttpServletResponse}. 051 * 052 * @version 053 */ 054 public class DefaultHttpBinding implements HttpBinding { 055 056 private static final transient Logger LOG = LoggerFactory.getLogger(DefaultHttpBinding.class); 057 private boolean useReaderForPayload; 058 private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy(); 059 private HttpEndpoint endpoint; 060 061 @Deprecated 062 public DefaultHttpBinding() { 063 } 064 065 @Deprecated 066 public DefaultHttpBinding(HeaderFilterStrategy headerFilterStrategy) { 067 this.headerFilterStrategy = headerFilterStrategy; 068 } 069 070 public DefaultHttpBinding(HttpEndpoint endpoint) { 071 this.endpoint = endpoint; 072 this.headerFilterStrategy = endpoint.getHeaderFilterStrategy(); 073 } 074 075 public void readRequest(HttpServletRequest request, HttpMessage message) { 076 LOG.trace("readRequest {}", request); 077 078 // lets force a parse of the body and headers 079 message.getBody(); 080 // populate the headers from the request 081 Map<String, Object> headers = message.getHeaders(); 082 083 //apply the headerFilterStrategy 084 Enumeration names = request.getHeaderNames(); 085 while (names.hasMoreElements()) { 086 String name = (String)names.nextElement(); 087 String value = request.getHeader(name); 088 // use http helper to extract parameter value as it may contain multiple values 089 Object extracted = HttpHelper.extractHttpParameterValue(value); 090 // mapping the content-type 091 if (name.toLowerCase().equals("content-type")) { 092 name = Exchange.CONTENT_TYPE; 093 } 094 if (headerFilterStrategy != null 095 && !headerFilterStrategy.applyFilterToExternalHeaders(name, extracted, message.getExchange())) { 096 HttpHelper.appendHeader(headers, name, extracted); 097 } 098 } 099 100 if (request.getCharacterEncoding() != null) { 101 headers.put(Exchange.HTTP_CHARACTER_ENCODING, request.getCharacterEncoding()); 102 message.getExchange().setProperty(Exchange.CHARSET_NAME, request.getCharacterEncoding()); 103 } 104 105 try { 106 populateRequestParameters(request, message); 107 } catch (Exception e) { 108 throw new RuntimeCamelException("Cannot read request parameters due " + e.getMessage(), e); 109 } 110 111 Object body = message.getBody(); 112 // reset the stream cache if the body is the instance of StreamCache 113 if (body instanceof StreamCache) { 114 ((StreamCache)body).reset(); 115 } 116 117 // store the method and query and other info in headers 118 headers.put(Exchange.HTTP_METHOD, request.getMethod()); 119 headers.put(Exchange.HTTP_QUERY, request.getQueryString()); 120 headers.put(Exchange.HTTP_URL, request.getRequestURL()); 121 headers.put(Exchange.HTTP_URI, request.getRequestURI()); 122 headers.put(Exchange.HTTP_PATH, request.getPathInfo()); 123 headers.put(Exchange.CONTENT_TYPE, request.getContentType()); 124 125 if (LOG.isTraceEnabled()) { 126 LOG.trace("HTTP method {}", request.getMethod()); 127 LOG.trace("HTTP query {}", request.getQueryString()); 128 LOG.trace("HTTP url {}", request.getRequestURL()); 129 LOG.trace("HTTP uri {}", request.getRequestURI()); 130 LOG.trace("HTTP path {}", request.getPathInfo()); 131 LOG.trace("HTTP content-type {}", request.getContentType()); 132 } 133 134 // if content type is serialized java object, then de-serialize it to a Java object 135 if (request.getContentType() != null && HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(request.getContentType())) { 136 try { 137 InputStream is = endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo(InputStream.class, body); 138 Object object = HttpHelper.deserializeJavaObjectFromStream(is); 139 if (object != null) { 140 message.setBody(object); 141 } 142 } catch (Exception e) { 143 throw new RuntimeCamelException("Cannot deserialize body to Java object", e); 144 } 145 } 146 147 populateAttachments(request, message); 148 } 149 150 protected void populateRequestParameters(HttpServletRequest request, HttpMessage message) throws Exception { 151 //we populate the http request parameters without checking the request method 152 Map<String, Object> headers = message.getHeaders(); 153 Enumeration names = request.getParameterNames(); 154 while (names.hasMoreElements()) { 155 String name = (String)names.nextElement(); 156 // there may be multiple values for the same name 157 String[] values = request.getParameterValues(name); 158 LOG.trace("HTTP parameter {} = {}", name, values); 159 160 if (values != null) { 161 for (String value : values) { 162 if (headerFilterStrategy != null 163 && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) { 164 HttpHelper.appendHeader(headers, name, value); 165 } 166 } 167 } 168 } 169 170 LOG.trace("HTTP method {} with Content-Type {}", request.getMethod(), request.getContentType()); 171 172 if (request.getMethod().equals("POST") && request.getContentType() != null 173 && request.getContentType().startsWith(HttpConstants.CONTENT_TYPE_WWW_FORM_URLENCODED)) { 174 String charset = request.getCharacterEncoding(); 175 if (charset == null) { 176 charset = "UTF-8"; 177 } 178 // Push POST form params into the headers to retain compatibility with DefaultHttpBinding 179 String body = message.getBody(String.class); 180 for (String param : body.split("&")) { 181 String[] pair = param.split("=", 2); 182 if (pair.length == 2) { 183 String name = URLDecoder.decode(pair[0], charset); 184 String value = URLDecoder.decode(pair[1], charset); 185 if (headerFilterStrategy != null 186 && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) { 187 HttpHelper.appendHeader(headers, name, value); 188 } 189 } else { 190 throw new IllegalArgumentException("Invalid parameter, expected to be a pair but was " + param); 191 } 192 } 193 } 194 } 195 196 protected void populateAttachments(HttpServletRequest request, HttpMessage message) { 197 // check if there is multipart files, if so will put it into DataHandler 198 Enumeration names = request.getAttributeNames(); 199 while (names.hasMoreElements()) { 200 String name = (String) names.nextElement(); 201 Object object = request.getAttribute(name); 202 LOG.trace("HTTP attachment {} = {}", name, object); 203 if (object instanceof File) { 204 String fileName = request.getParameter(name); 205 message.addAttachment(fileName, new DataHandler(new CamelFileDataSource((File)object, fileName))); 206 } 207 } 208 } 209 210 public void writeResponse(Exchange exchange, HttpServletResponse response) throws IOException { 211 if (exchange.isFailed()) { 212 if (exchange.getException() != null) { 213 doWriteExceptionResponse(exchange.getException(), response); 214 } else { 215 // it must be a fault, no need to check for the fault flag on the message 216 doWriteFaultResponse(exchange.getOut(), response, exchange); 217 } 218 } else { 219 // just copy the protocol relates header 220 copyProtocolHeaders(exchange.getIn(), exchange.getOut()); 221 Message out = exchange.getOut(); 222 if (out != null) { 223 doWriteResponse(out, response, exchange); 224 } 225 } 226 } 227 228 private void copyProtocolHeaders(Message request, Message response) { 229 if (request.getHeader(Exchange.CONTENT_ENCODING) != null) { 230 String contentEncoding = request.getHeader(Exchange.CONTENT_ENCODING, String.class); 231 response.setHeader(Exchange.CONTENT_ENCODING, contentEncoding); 232 } 233 if (checkChunked(response, response.getExchange())) { 234 response.setHeader(Exchange.TRANSFER_ENCODING, "chunked"); 235 } 236 } 237 238 public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException { 239 // 500 for internal server error 240 response.setStatus(500); 241 242 if (endpoint != null && endpoint.isTransferException()) { 243 // transfer the exception as a serialized java object 244 HttpHelper.writeObjectToServletResponse(response, exception); 245 } else { 246 // write stacktrace as plain text 247 response.setContentType("text/plain"); 248 PrintWriter pw = response.getWriter(); 249 exception.printStackTrace(pw); 250 pw.flush(); 251 } 252 } 253 254 public void doWriteFaultResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { 255 doWriteResponse(message, response, exchange); 256 } 257 258 public void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { 259 // set the status code in the response. Default is 200. 260 if (message.getHeader(Exchange.HTTP_RESPONSE_CODE) != null) { 261 int code = message.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class); 262 response.setStatus(code); 263 } 264 // set the content type in the response. 265 String contentType = MessageHelper.getContentType(message); 266 if (MessageHelper.getContentType(message) != null) { 267 response.setContentType(contentType); 268 } 269 270 // append headers 271 // must use entrySet to ensure case of keys is preserved 272 for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { 273 String key = entry.getKey(); 274 Object value = entry.getValue(); 275 // use an iterator as there can be multiple values. (must not use a delimiter) 276 final Iterator it = ObjectHelper.createIterator(value, null); 277 while (it.hasNext()) { 278 String headerValue = exchange.getContext().getTypeConverter().convertTo(String.class, it.next()); 279 if (headerValue != null && headerFilterStrategy != null 280 && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, exchange)) { 281 response.addHeader(key, headerValue); 282 } 283 } 284 } 285 286 // write the body. 287 if (message.getBody() != null) { 288 if (GZIPHelper.isGzip(message)) { 289 doWriteGZIPResponse(message, response, exchange); 290 } else { 291 doWriteDirectResponse(message, response, exchange); 292 } 293 } 294 } 295 296 protected void doWriteDirectResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { 297 // if content type is serialized Java object, then serialize and write it to the response 298 String contentType = message.getHeader(Exchange.CONTENT_TYPE, String.class); 299 if (contentType != null && HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) { 300 try { 301 Object object = message.getMandatoryBody(Serializable.class); 302 HttpHelper.writeObjectToServletResponse(response, object); 303 // object is written so return 304 return; 305 } catch (InvalidPayloadException e) { 306 throw new IOException(e); 307 } 308 } 309 310 // other kind of content type 311 InputStream is = null; 312 if (checkChunked(message, exchange)) { 313 is = message.getBody(InputStream.class); 314 } 315 if (is != null) { 316 ServletOutputStream os = response.getOutputStream(); 317 try { 318 // copy directly from input stream to output stream 319 IOHelper.copy(is, os); 320 } finally { 321 IOHelper.close(os); 322 IOHelper.close(is); 323 } 324 } else { 325 // not convertable as a stream so try as a String 326 String data = message.getBody(String.class); 327 if (data != null) { 328 // set content length before we write data 329 response.setContentLength(data.length()); 330 response.getWriter().print(data); 331 response.getWriter().flush(); 332 } 333 } 334 } 335 336 protected boolean checkChunked(Message message, Exchange exchange) { 337 boolean answer = true; 338 if (message.getHeader(Exchange.HTTP_CHUNKED) == null) { 339 // check the endpoint option 340 Endpoint endpoint = exchange.getFromEndpoint(); 341 if (endpoint instanceof HttpEndpoint) { 342 answer = ((HttpEndpoint)endpoint).isChunked(); 343 } 344 } else { 345 answer = message.getHeader(Exchange.HTTP_CHUNKED, boolean.class); 346 } 347 return answer; 348 } 349 350 protected void doWriteGZIPResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { 351 byte[] bytes; 352 try { 353 bytes = message.getMandatoryBody(byte[].class); 354 } catch (InvalidPayloadException e) { 355 throw ObjectHelper.wrapRuntimeCamelException(e); 356 } 357 358 byte[] data = GZIPHelper.compressGZIP(bytes); 359 ServletOutputStream os = response.getOutputStream(); 360 try { 361 response.setContentLength(data.length); 362 os.write(data); 363 os.flush(); 364 } finally { 365 IOHelper.close(os); 366 } 367 } 368 369 public Object parseBody(HttpMessage httpMessage) throws IOException { 370 // lets assume the body is a reader 371 HttpServletRequest request = httpMessage.getRequest(); 372 // Need to handle the GET Method which has no inputStream 373 if ("GET".equals(request.getMethod())) { 374 return null; 375 } 376 if (isUseReaderForPayload()) { 377 // use reader to read the response body 378 return request.getReader(); 379 } else { 380 // reade the response body from servlet request 381 return HttpHelper.readResponseBodyFromServletRequest(request, httpMessage.getExchange()); 382 } 383 } 384 385 public boolean isUseReaderForPayload() { 386 return useReaderForPayload; 387 } 388 389 public void setUseReaderForPayload(boolean useReaderForPayload) { 390 this.useReaderForPayload = useReaderForPayload; 391 } 392 393 public HeaderFilterStrategy getHeaderFilterStrategy() { 394 return headerFilterStrategy; 395 } 396 397 public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { 398 this.headerFilterStrategy = headerFilterStrategy; 399 } 400 401 }