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