Spring REST 端点返回 StreamingResponseBody:30 秒后 AsyncRequestTimeoutException
Spring REST endpoint returning StreamingResponseBody: AsyncRequestTimeoutException after 30 seconds
我遇到了与描述相同的问题 here and here。
我尝试了给出的答案及其组合,但 none 解决了我的问题。
当我尝试 this 回答时,30 秒后,下载从头开始重新开始,而不是超时,然后又过了 30 秒,然后超时。
我正在通过访问 Google Chrome 中的 REST 端点并尝试从那里下载文件来进行测试。
Here 我有显示此错误的项目。
提前致谢。
编辑: 来源:
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>
更新: 尝试 Manuel 的回答(提交到分支 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();
})
);
}
}
它抛出 AsyncRequestTimeoutException
和这个:
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(WebAsyncManager.java:315) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError[=18=](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]
更新: 尝试 Manuel 的更新答案(致力于分支 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();
});
}
}
解决问题:
将 @RepositoryRestController
更改为例如 @RestController
。
如果您使用 @RepositoryRestController
,超时将设置为 RequestMappingHandlerAdapter
。但是在请求下载时,RepositoryRestHandlerAdapter
会处理请求,因为注释要求他这样做。
如果您使用 @RestController
,那么(正确的)RequestMappingHandlerAdapter
将处理下载,超时设置为 -1。
原答案:
您可以尝试 declarative/explicit 定义超时,返回一个 org.springframework.web.context.request.async.WebAsyncTask。
如果提议设置一个Callable<V>
with a timeout
:
那么您的 DefaultController 可能如下所示:
public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub() throws IOException {
...
new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(myTimeOutAsLong, callable);
}
更新:
- 请从您的 REST 控制器方法中删除
HttpServletResponse
参数。可以肯定的是,HttpServletResponse
中的 OutputStream
不会干扰 StreamingResponseBody
中的 OutputStream
。
- 关于错误“没有 AsyncContext 无法分派”:
我遇到了与描述相同的问题 here and here。
我尝试了给出的答案及其组合,但 none 解决了我的问题。
当我尝试 this 回答时,30 秒后,下载从头开始重新开始,而不是超时,然后又过了 30 秒,然后超时。
我正在通过访问 Google Chrome 中的 REST 端点并尝试从那里下载文件来进行测试。
Here 我有显示此错误的项目。
提前致谢。
编辑: 来源:
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>
更新: 尝试 Manuel 的回答(提交到分支 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();
})
);
}
}
它抛出 AsyncRequestTimeoutException
和这个:
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(WebAsyncManager.java:315) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError[=18=](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]
更新: 尝试 Manuel 的更新答案(致力于分支 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();
});
}
}
解决问题:
将 @RepositoryRestController
更改为例如 @RestController
。
如果您使用 @RepositoryRestController
,超时将设置为 RequestMappingHandlerAdapter
。但是在请求下载时,RepositoryRestHandlerAdapter
会处理请求,因为注释要求他这样做。
如果您使用 @RestController
,那么(正确的)RequestMappingHandlerAdapter
将处理下载,超时设置为 -1。
原答案:
您可以尝试 declarative/explicit 定义超时,返回一个 org.springframework.web.context.request.async.WebAsyncTask。
如果提议设置一个Callable<V>
with a timeout
:
那么您的 DefaultController 可能如下所示:
public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub() throws IOException {
...
new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(myTimeOutAsLong, callable);
}
更新:
- 请从您的 REST 控制器方法中删除
HttpServletResponse
参数。可以肯定的是,HttpServletResponse
中的OutputStream
不会干扰StreamingResponseBody
中的OutputStream
。 - 关于错误“没有 AsyncContext 无法分派”: