Spring REST端点在30秒后返回StreamingResponseBody:AsyncRequestTimeoutException

问题描述 投票:0回答:1

我有与herehere相同的问题。

我尝试了给出的答案及其组合,但没有一个解决我的问题。

[当我尝试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$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]

更新:尝试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();
                });
    }
}
spring-boot asynchronous spring-rest
1个回答
0
投票

要解决问题:

@RepositoryRestController更改为例如@RestController

如果使用@RepositoryRestController,将为RequestMappingHandlerAdapter设置超时。但是在请求下载后,RepositoryRestHandlerAdapter会处理该请求,因为注释要求他这样做。

如果使用@RestController,则(正确的)RequestMappingHandlerAdapter将处理下载,超时设置为-1。

原始答案:

您可以尝试通过返回org.springframework.web.context.request.async.WebAsyncTask来声明/明确定义超时。

如果提供设置Callable<V> with a timeout

然后您的DefaultController可能看起来像:

Callable<V>

更新:

  1. 请从您的REST控制器方法中删除timeout参数。只需确保public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub() throws IOException { ... new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(myTimeOutAsLong, callable); } 中的HttpServletResponse不会干扰OutputStream中的HttpServletResponse
  2. 关于错误“如果没有AsyncContext,则无法分派:OutputStream
© www.soinside.com 2019 - 2024. All rights reserved.