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    }