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    }