/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.http.inbound;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.integration.http.converter.MultipartAwareFormHttpMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpRequestHandler;
/**
* Inbound Messaging Gateway that handles HTTP Requests. May be configured as a bean in the Application Context and
* delegated to from a simple HttpRequestHandlerServlet in <code>web.xml</code> where the servlet and bean both have the
* same name. If the {@link #expectReply} property is set to true, a response can generated from a reply Message.
* Otherwise, the gateway will play the role of a unidirectional Channel Adapter with a simple status-based response
* (e.g. 200 OK).
* <p>
* The default supported request methods are GET and POST, but the list of values can be configured with the
* {@link RequestMapping#methods} property. The payload generated from a GET request (or HEAD or OPTIONS if supported) will
* be a {@link MultiValueMap} containing the parameter values. For a request containing a body (e.g. a POST), the type
* of the payload is determined by the {@link #setRequestPayloadType(Class) request payload type}.
* <p>
* If the HTTP request is a multipart and a "multipartResolver" bean has been defined in the context, then it will be
* converted by the {@link MultipartAwareFormHttpMessageConverter} as long as the default message converters have not
* been overwritten (although providing a customized instance of the Multipart-aware converter is also an option).
* <p>
* By default a number of {@link HttpMessageConverter}s are already configured. The list can be overridden by calling
* the {@link #setMessageConverters(List)} method.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport
implements HttpRequestHandler {
private volatile boolean convertExceptions;
public HttpRequestHandlingMessagingGateway() {
this(true);
}
public HttpRequestHandlingMessagingGateway(boolean expectReply) {
super(expectReply);
}
/**
* Flag to determine if conversion and writing out of message handling exceptions should be attempted (default
* false, in which case they will simply be re-thrown). If the flag is true and no message converter can convert the
* exception a new exception will be thrown.
*
* @param convertExceptions the flag to set
*/
public void setConvertExceptions(boolean convertExceptions) {
this.convertExceptions = convertExceptions;
}
/**
* Handles the HTTP request by generating a Message and sending it to the request channel. If this gateway's
* 'expectReply' property is true, it will also generate a response from the reply Message once received. That
* response will be written by the {@link HttpMessageConverter}s.
*/
public final void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws ServletException, IOException {
Object responseContent = null;
Message<?> responseMessage;
final ServletServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
final ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
try {
responseMessage = super.doHandleRequest(servletRequest, servletResponse);
if (responseMessage != null) {
responseContent = setupResponseAndConvertReply(response, responseMessage);
}
}
catch (Exception e) {
responseContent = handleExceptionInternal(e);
}
if (responseContent != null) {
if (responseContent instanceof HttpStatus) {
response.setStatusCode((HttpStatus) responseContent);
}
else {
this.writeResponse(responseContent, response, request.getHeaders().getAccept());
}
}
else {
setStatusCodeIfNeeded(response);
}
}
private Object handleExceptionInternal(Exception e) throws IOException {
if (this.convertExceptions && isExpectReply()) {
return e;
}
else {
if (e instanceof IOException) {
throw (IOException) e;
}
else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
else {
throw new MessagingException("error occurred handling HTTP request", e);
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void writeResponse(Object content, ServletServerHttpResponse response, List<MediaType> acceptTypes)
throws IOException {
if (CollectionUtils.isEmpty(acceptTypes)) {
acceptTypes = Collections.singletonList(MediaType.ALL);
}
for (HttpMessageConverter converter : this.getMessageConverters()) {
for (MediaType acceptType : acceptTypes) {
if (converter.canWrite(content.getClass(), acceptType)) {
converter.write(content, acceptType, response);
return;
}
}
}
throw new MessagingException("Could not convert reply: no suitable HttpMessageConverter found for type ["
+ content.getClass().getName() + "] and accept types [" + acceptTypes + "]");
}
}