Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable async support in a Springboot application packaged and deployed as WAR

The following REST endpoint shown below works as expected when my SpringBoot application is run from an executable JAR. That is, it returns the text "My test response" to the client. However, when I package the same application as a WAR and deploy to Tomcat (8.0.29) it throws the following exception:

There was an unexpected error (type=Internal Server Error, status=500). Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.

package my.rest.controllers;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@RestController
@RequestMapping("/api/file")
public class FileContentRestController {

static final int BUFFER = 2048;

@RequestMapping(value = "/content", method = RequestMethod.GET)
@ResponseBody
public StreamingResponseBody getFileContent(HttpServletResponse response) {
    response.setContentType("text/plain");
    response.setCharacterEncoding("UTF-8");

        final InputStream portalFileStream = new ByteArrayInputStream("My test response".getBytes());
        return (OutputStream outputStream) -> {
            int n;
            byte[] buffer = new byte[1024];
            while ((n = portalFileStream.read(buffer)) > -1) {
                outputStream.write(buffer, 0, n);
            }
            portalFileStream.close();
        };

}

}

My understanding from here and elsewhere is that SpringBoot enables async support on all the filters and servlets registered by SpringBoot. It would certainly seems to be the case when run from a standalone JAR with the embedded Tomcat container.

How can I ensure async support is enabled when deploying as a WAR?

My SpringBoot application is configured thus:

package my;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class MyRestApp extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return configureApplication(builder);
    }

    public static void main(String[] args) throws JsonProcessingException {
        configureApplication(new SpringApplicationBuilder()).run(args);
    }

    private static SpringApplicationBuilder configureApplication(SpringApplicationBuilder builder) {
        return builder.sources(MyRestApp.class).bannerMode(Banner.Mode.OFF);
    }

}

with MVC configured thus:

package my;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@EnableAsync
public class MyMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(-1);
        configurer.setTaskExecutor(asyncTaskExecutor());
    }

    @Bean
    public AsyncTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("SpringAsyncThread-");
        executor.initialize();
        return executor;
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS");
            }
        };
    }

}

Finally, the application is built and packaged using Maven with the following POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.acme</groupId>
    <artifactId>my-rest-app</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>my-rest-app</name>
    <description></description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
            </resource>
            <resource>
                <directory>${project.build.directory}/generated-resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
like image 293
Gareth Hughes Avatar asked Nov 14 '16 14:11

Gareth Hughes


4 Answers

Well the exception you parsed says it all:

There was an unexpected error (type=Internal Server Error, status=500). Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.

Thus you need to enable it either in your web.xml or (and because you are using a spring-boot application) you have to configure a specific bean.

Maybe this code snippet for your AppConfig would help

@Bean
public ServletRegistrationBean dispatcherServlet() {
    ServletRegistrationBean registration = new ServletRegistrationBean(new DispatcherServlet(), "/");
    registration.setAsyncSupported(true);
    return registration;
}
like image 173
smsnheck Avatar answered Oct 13 '22 09:10

smsnheck


My external Tomcat instance was the one installed and configured with NetBeans. In the %CATALINA_BASE%\conf folder there is a web.xml that applies to all applications deployed to the server. In there was a filter named HTTPMonitorFilter which did not support async requests. Adding true to that filter definition solved my problem.

like image 25
Gareth Hughes Avatar answered Oct 13 '22 11:10

Gareth Hughes


I had the same problem in my springboot application.

Async support must be enabled on a servlet and for all filters involved in async......

I just add

@WebFilter(asyncSupported = true)

in my filters. The default is false:

/**
* Declares whether the filter supports asynchronous operation mode. *
* @see javax.servlet.ServletRequest#startAsync * @see javax.servlet.ServletRequest#startAsync(ServletRequest, * ServletResponse) */
boolean asyncSupported() default false;

like image 33
L.yan Avatar answered Oct 13 '22 09:10

L.yan


I had the same problem. Running the app with in an embedded tomcat server ( with maven-tomcat7-plugin ) works fine, no errors. Deployed to an external tomcat server (the spring boot app packaged as a .WAR) :

There was an unexpected error (type=Internal Server Error, status=500). Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.

This is the only solution that worked for me. I had to create the file src/main/webapp/WEB-INF/web.xml with the following contents :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0" metadata-complete="true">

    <filter>
            <filter-name>cacheControlFilter</filter-name>
            <filter-class>xxx.yourimplementation.CacheControlFilter</filter-class>
            <async-supported>true</async-supported>
            <init-param>
                <param-name>css,html,js</param-name>
                <param-value>cache-control=1800,edge-control=1800</param-value>
            </init-param>
            <init-param>
                <param-name>ajax,htm</param-name>
                <param-value>cache-control=0,edge-control=0</param-value>
            </init-param>
            <init-param>
                <param-name>doc,gif,ico,jpe,jpeg,jpg,pdf,png,swf</param-name>
                <param-value>cache-control=1800,edge-control=7200</param-value>
            </init-param>
    </filter>

</webapp>

This fixed the problem for me. Hope it helps!

like image 25
Justin K Avatar answered Oct 13 '22 10:10

Justin K