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.FileInputStream;
021 import java.io.FileNotFoundException;
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.io.PrintWriter;
025 import java.io.UnsupportedEncodingException;
026 import java.net.URLDecoder;
027 import java.util.Enumeration;
028 import java.util.Map;
029
030 import javax.activation.DataHandler;
031 import javax.activation.FileDataSource;
032 import javax.activation.FileTypeMap;
033 import javax.servlet.ServletOutputStream;
034 import javax.servlet.http.HttpServletRequest;
035 import javax.servlet.http.HttpServletResponse;
036
037 import org.apache.camel.Endpoint;
038 import org.apache.camel.Exchange;
039 import org.apache.camel.InvalidPayloadException;
040 import org.apache.camel.Message;
041 import org.apache.camel.StreamCache;
042 import org.apache.camel.component.http.helper.CamelFileDataSource;
043 import org.apache.camel.component.http.helper.GZIPHelper;
044 import org.apache.camel.converter.stream.CachedOutputStream;
045 import org.apache.camel.spi.HeaderFilterStrategy;
046 import org.apache.camel.util.IOHelper;
047 import org.apache.camel.util.MessageHelper;
048 import org.apache.camel.util.ObjectHelper;
049
050 /**
051 * Binding between {@link HttpMessage} and {@link HttpServletResponse}.
052 *
053 * @version $Revision: 999444 $
054 */
055 public class DefaultHttpBinding implements HttpBinding {
056
057 private boolean useReaderForPayload;
058 private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy();
059
060 public DefaultHttpBinding() {
061 }
062
063 public DefaultHttpBinding(HeaderFilterStrategy headerFilterStrategy) {
064 this.headerFilterStrategy = headerFilterStrategy;
065 }
066
067 public void readRequest(HttpServletRequest request, HttpMessage message) {
068
069 // lets force a parse of the body and headers
070 message.getBody();
071 // populate the headers from the request
072 Map<String, Object> headers = message.getHeaders();
073
074 //apply the headerFilterStrategy
075 Enumeration names = request.getHeaderNames();
076 while (names.hasMoreElements()) {
077 String name = (String)names.nextElement();
078 Object value = request.getHeader(name);
079 // mapping the content-type
080 if (name.toLowerCase().equals("content-type")) {
081 name = Exchange.CONTENT_TYPE;
082 }
083 if (headerFilterStrategy != null
084 && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) {
085 headers.put(name, value);
086 }
087 }
088
089 if (request.getCharacterEncoding() != null) {
090 headers.put(Exchange.HTTP_CHARACTER_ENCODING, request.getCharacterEncoding());
091 message.getExchange().setProperty(Exchange.CHARSET_NAME, request.getCharacterEncoding());
092 }
093
094 popluateRequestParameters(request, message);
095
096 Object body = message.getBody();
097 // reset the stream cache if the body is the instance of StreamCache
098 if (body instanceof StreamCache) {
099 ((StreamCache)body).reset();
100 }
101
102 // store the method and query and other info in headers
103 headers.put(Exchange.HTTP_METHOD, request.getMethod());
104 headers.put(Exchange.HTTP_QUERY, request.getQueryString());
105 headers.put(Exchange.HTTP_URL, request.getRequestURL());
106 headers.put(Exchange.HTTP_URI, request.getRequestURI());
107 headers.put(Exchange.HTTP_PATH, request.getPathInfo());
108 headers.put(Exchange.CONTENT_TYPE, request.getContentType());
109
110 popluateAttachments(request, message);
111 }
112
113 protected void popluateRequestParameters(HttpServletRequest request, HttpMessage message) {
114 //we populate the http request parameters without checking the request method
115 Map<String, Object> headers = message.getHeaders();
116 Enumeration names = request.getParameterNames();
117 while (names.hasMoreElements()) {
118 String name = (String)names.nextElement();
119 Object value = request.getParameter(name);
120 if (headerFilterStrategy != null
121 && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) {
122 headers.put(name, value);
123 }
124 }
125
126 if (request.getMethod().equals("POST") && request.getContentType() != null && request.getContentType().startsWith("application/x-www-form-urlencoded")) {
127 String charset = request.getCharacterEncoding();
128 if (charset == null) {
129 charset = "UTF-8";
130 }
131 // Push POST form params into the headers to retain compatibility with DefaultHttpBinding
132 String body = message.getBody(String.class);
133 try {
134 for (String param : body.split("&")) {
135 String[] pair = param.split("=", 2);
136 String name = URLDecoder.decode(pair[0], charset);
137 String value = URLDecoder.decode(pair[1], charset);
138 if (headerFilterStrategy != null
139 && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) {
140 headers.put(name, value);
141 }
142 }
143 } catch (UnsupportedEncodingException e) {
144 throw new RuntimeException(e);
145 }
146 }
147
148 }
149
150 protected void popluateAttachments(HttpServletRequest request, HttpMessage message) {
151 // check if there is multipart files, if so will put it into DataHandler
152 Enumeration names = request.getAttributeNames();
153 while (names.hasMoreElements()) {
154 String name = (String) names.nextElement();
155 Object object = request.getAttribute(name);
156 if (object instanceof File) {
157 String fileName = request.getParameter(name);
158 message.addAttachment(fileName, new DataHandler(new CamelFileDataSource((File)object, fileName)));
159 }
160 }
161 }
162
163 public void writeResponse(Exchange exchange, HttpServletResponse response) throws IOException {
164 if (exchange.isFailed()) {
165 if (exchange.getException() != null) {
166 doWriteExceptionResponse(exchange.getException(), response);
167 } else {
168 // it must be a fault, no need to check for the fault flag on the message
169 doWriteFaultResponse(exchange.getOut(), response, exchange);
170 }
171 } else {
172 // just copy the protocol relates header
173 copyProtocolHeaders(exchange.getIn(), exchange.getOut());
174 Message out = exchange.getOut();
175 if (out != null) {
176 doWriteResponse(out, response, exchange);
177 }
178 }
179 }
180
181 private void copyProtocolHeaders(Message request, Message response) {
182 if (request.getHeader(Exchange.CONTENT_ENCODING) != null) {
183 String contentEncoding = request.getHeader(Exchange.CONTENT_ENCODING, String.class);
184 response.setHeader(Exchange.CONTENT_ENCODING, contentEncoding);
185 }
186 if (checkChunked(response, response.getExchange())) {
187 response.setHeader(Exchange.TRANSFER_ENCODING, "chunked");
188 }
189 }
190
191 public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
192 response.setStatus(500); // 500 for internal server error
193 response.setContentType("text/plain");
194
195 // append the stacktrace as response
196 PrintWriter pw = response.getWriter();
197 exception.printStackTrace(pw);
198
199 pw.flush();
200 }
201
202 public void doWriteFaultResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
203 doWriteResponse(message, response, exchange);
204 }
205
206 public void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
207 // set the status code in the response. Default is 200.
208 if (message.getHeader(Exchange.HTTP_RESPONSE_CODE) != null) {
209 int code = message.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
210 response.setStatus(code);
211 }
212 // set the content type in the response.
213 String contentType = MessageHelper.getContentType(message);
214 if (MessageHelper.getContentType(message) != null) {
215 response.setContentType(contentType);
216 }
217
218 // append headers
219 // must use entrySet to ensure case of keys is preserved
220 for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) {
221 String key = entry.getKey();
222 Object value = entry.getValue();
223 if (value != null && headerFilterStrategy != null
224 && !headerFilterStrategy.applyFilterToCamelHeaders(key, value, exchange)) {
225 response.setHeader(key, value.toString());
226 }
227 }
228
229 // write the body.
230 if (message.getBody() != null) {
231 if (GZIPHelper.isGzip(message)) {
232 doWriteGZIPResponse(message, response, exchange);
233 } else {
234 doWriteDirectResponse(message, response, exchange);
235 }
236 }
237 }
238
239 protected void doWriteDirectResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
240 InputStream is = null;
241 if (checkChunked(message, exchange)) {
242 is = message.getBody(InputStream.class);
243 }
244 if (is != null) {
245 ServletOutputStream os = response.getOutputStream();
246 try {
247 // copy directly from input stream to output stream
248 IOHelper.copy(is, os);
249 } finally {
250 try {
251 os.close();
252 } catch (Exception e) {
253 // ignore, maybe client have disconnected or timed out
254 }
255 try {
256 is.close();
257 } catch (Exception e) {
258 // ignore, maybe client have disconnected or timed out
259 }
260 }
261 } else {
262 // not convertable as a stream so try as a String
263 String data = message.getBody(String.class);
264 if (data != null) {
265 // set content length before we write data
266 response.setContentLength(data.length());
267 response.getWriter().print(data);
268 response.getWriter().flush();
269 }
270 }
271 }
272
273 protected boolean checkChunked(Message message, Exchange exchange) {
274 boolean answer = true;
275 if (message.getHeader(Exchange.HTTP_CHUNKED) == null) {
276 // check the endpoint option
277 Endpoint endpoint = exchange.getFromEndpoint();
278 if (endpoint instanceof HttpEndpoint) {
279 answer = ((HttpEndpoint)endpoint).isChunked();
280 }
281 } else {
282 answer = message.getHeader(Exchange.HTTP_CHUNKED, boolean.class);
283 }
284 return answer;
285 }
286
287 protected void doWriteGZIPResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
288 byte[] bytes;
289 try {
290 bytes = message.getMandatoryBody(byte[].class);
291 } catch (InvalidPayloadException e) {
292 throw ObjectHelper.wrapRuntimeCamelException(e);
293 }
294
295 byte[] data = GZIPHelper.compressGZIP(bytes);
296 ServletOutputStream os = response.getOutputStream();
297 try {
298 response.setContentLength(data.length);
299 os.write(data);
300 os.flush();
301 } finally {
302 os.close();
303 }
304 }
305
306 public Object parseBody(HttpMessage httpMessage) throws IOException {
307 // lets assume the body is a reader
308 HttpServletRequest request = httpMessage.getRequest();
309 // Need to handle the GET Method which has no inputStream
310 if ("GET".equals(request.getMethod())) {
311 return null;
312 }
313 if (isUseReaderForPayload()) {
314 return request.getReader();
315 } else {
316 // otherwise use input stream and we need to cache it first
317 InputStream is = HttpConverter.toInputStream(request, httpMessage.getExchange());
318 if (is == null) {
319 return is;
320 }
321 // convert the input stream to StreamCache if the stream cache is not disabled
322 if (httpMessage.getExchange().getProperty(Exchange.DISABLE_HTTP_STREAM_CACHE, Boolean.FALSE, Boolean.class)) {
323 return is;
324 } else {
325 try {
326 CachedOutputStream cos = new CachedOutputStream(httpMessage.getExchange());
327 IOHelper.copy(is, cos);
328 return cos.getStreamCache();
329
330 } finally {
331 is.close();
332 }
333 }
334
335 }
336 }
337
338 public boolean isUseReaderForPayload() {
339 return useReaderForPayload;
340 }
341
342 public void setUseReaderForPayload(boolean useReaderForPayload) {
343 this.useReaderForPayload = useReaderForPayload;
344 }
345
346 public HeaderFilterStrategy getHeaderFilterStrategy() {
347 return headerFilterStrategy;
348 }
349
350 public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
351 this.headerFilterStrategy = headerFilterStrategy;
352 }
353
354 }