问题
I have the same problem as described here and here.
I tried the answers given and combinations thereof but none solved my issue.
When I tried this answer, after 30 seconds, instead of the timeout, the download restarted from the beginning and then, after 30 more seconds, then it timed out.
I'm testing by visiting the REST endpoint in Google Chrome and trying to download a file from there.
Here I have the project that displays this error.
Thanks in advance.
Edit: here's the source:
src\main\java\io\github\guiritter\transferer_local\TransfererLocalApplication.java
package io.github.guiritter.transferer_local;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class TransfererLocalApplication {
public static void main(String[] args) {
SpringApplication.run(TransfererLocalApplication.class, args);
}
}
src\main\java\io\github\guiritter\transferer_local\DefaultController.java
package io.github.guiritter.transferer_local;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.webmvc.RepositoryRestController;
// import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RepositoryRestController
@RequestMapping("api")
public class DefaultController {
@Value("${fileName}")
private String fileName;
@Value("${filePath}")
private String filePath;
@GetMapping("download")
public StreamingResponseBody downloadHub(HttpServletResponse response) throws IOException {
File file = new File(filePath + fileName);
response.setContentType(APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setHeader("Content-Length", file.length() + "");
InputStream inputStream = new FileInputStream(file);
return outputStream -> {
int nRead;
byte[] data = new byte[1024*1024];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, nRead);
}
inputStream.close();
};
}
// @GetMapping("download")
// public ResponseEntity<StreamingResponseBody> downloadHub(HttpServletResponse response) throws IOException {
// File file = new File(filePath + fileName);
// response.setContentType(APPLICATION_OCTET_STREAM_VALUE);
// response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// response.setHeader("Content-Length", file.length() + "");
// InputStream inputStream = new FileInputStream(file);
// return ResponseEntity.ok(outputStream -> {
// int nRead;
// byte[] data = new byte[1024*1024];
// while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
// outputStream.write(data, 0, nRead);
// }
// inputStream.close();
// });
// }
}
src\main\java\io\github\guiritter\transferer_local\AsyncConfiguration.java
package io.github.guiritter.transferer_local;
// import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public AsyncTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Integer.MAX_VALUE);
executor.setThreadNamePrefix("io.github.guiritter.transferer_local.async_executor_thread.");
return executor;
}
/** Configure async support for Spring MVC. */
@Bean
public WebMvcConfigurer webMvcConfigurerAdapter(
AsyncTaskExecutor taskExecutor) {
return new WebMvcConfigurer() {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer
.setDefaultTimeout(Long.MAX_VALUE)
.setTaskExecutor(taskExecutor);
configureAsyncSupport(configurer);
}
};
}
// @Autowired
// private AsyncTaskExecutor taskExecutor;
// /** Configure async support for Spring MVC. */
// @Bean
// public WebMvcConfigurer webMvcConfigurerAdapter() {
// return new WebMvcConfigurer() {
// @Override
// public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// configurer
// .setDefaultTimeout(Long.MAX_VALUE)
// .setTaskExecutor(taskExecutor);
// configureAsyncSupport(configurer);
// }
// };
// }
}
src\main\java\io\github\guiritter\transferer_local\MyConfiguration.java
package io.github.guiritter.transferer_local;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@EnableAsync
public class MyConfiguration implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(-1);
}
}
src\main\resources\application.properties
server.port=8081
fileName=large_file_name.txt
filePath=C:\\path\\to\\large\\file\\
# spring.mvc.async.request-timeout = 9223372036854775807
# spring.mvc.async.request-timeout = 2147483647
spring.mvc.async.request-timeout = -1
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.guiritter</groupId>
<artifactId>transferer-local</artifactId>
<version>1.0.0</version>
<name>TransfererLocal</name>
<description>Enables local network file transfer</description>
<properties>
<java.version>14</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-rest-webmvc -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Update: trying Manuel's answer (commited to branch answer_Manuel):
src\main\java\io\github\guiritter\transferer_local\DefaultController.java
package io.github.guiritter.transferer_local;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.async.WebAsyncTask;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RepositoryRestController
@RequestMapping("api")
public class DefaultController {
@Value("${fileName}")
private String fileName;
@Value("${filePath}")
private String filePath;
@GetMapping("download")
public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub(HttpServletResponse response) throws IOException {
File file = new File(filePath + fileName);
response.setContentType(APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setHeader("Content-Length", file.length() + "");
InputStream inputStream = new FileInputStream(file);
return new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(Long.MAX_VALUE, () ->
ResponseEntity.<StreamingResponseBody>ok(outputStream -> {
int nRead;
byte[] data = new byte[1024*1024];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, nRead);
}
inputStream.close();
})
);
}
}
It threw AsyncRequestTimeoutException
and this:
java.lang.IllegalArgumentException: Cannot dispatch without an AsyncContext
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:131) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:391) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$2(WebAsyncManager.java:315) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$0(StandardServletAsyncWebRequest.java:146) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1510) ~[na:na]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java:146) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:49) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:422) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:239) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:237) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Update: trying Manuel's updated answer (commited to branch answer_Manuel_2020-04-06):
src\main\java\io\github\guiritter\transferer_local\DefaultController.java
package io.github.guiritter.transferer_local;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.async.WebAsyncTask;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RepositoryRestController
@RequestMapping("api")
public class DefaultController {
@Value("${fileName}")
private String fileName;
@Value("${filePath}")
private String filePath;
@GetMapping("download")
public ResponseEntity<StreamingResponseBody> downloadHub() throws IOException {
File file = new File(filePath + fileName);
InputStream inputStream = new FileInputStream(file);
return ResponseEntity
.ok()
.contentType(APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment; filename=" + fileName)
.header("Content-Length", file.length() + "")
.<StreamingResponseBody>body(outputStream -> {
int nRead;
byte[] data = new byte[1024*1024];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, nRead);
}
inputStream.close();
});
}
}
回答1:
To fix the problem:
Change @RepositoryRestController
to e.g., @RestController
.
If you use @RepositoryRestController
, the timeout will be set for the RequestMappingHandlerAdapter
. But upon requesting the download, the RepositoryRestHandlerAdapter
handles the request, as the annotation requires him to.
If you use the @RestController
then the (correct) RequestMappingHandlerAdapter
will handle the download, with the timeout set to -1.
The original answer:
You could try to declarative/explicit define the timeout, by returning a org.springframework.web.context.request.async.WebAsyncTask.
If offers to set a Callable<V> with a timeout:
Then your DefaultController could look like:
public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub() throws IOException {
...
new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(myTimeOutAsLong, callable);
}
Update:
- Please remove the
HttpServletResponse
parameter from your REST controller method. Just to be sure, that theOutputStream
from theHttpServletResponse
does not interfere with theOutputStream
fromStreamingResponseBody
. - Regarding the error ´Cannot dispatch without an AsyncContext´: "Cannot dispatch without an AsyncContext" when serving files
来源:https://stackoverflow.com/questions/61030650/spring-rest-endpoint-returning-streamingresponsebody-asyncrequesttimeoutexcepti