本文介绍了Spring REST端点在30秒后返回StreamingResponseBody:AsyncRequestTimeoutException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到的问题与和此处.

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

I tried the answers given and combinations thereof but none solved my issue.

当我尝试答案时,在30秒后(而不是超时),下载从头开始,然后,又过了30秒,然后超时了.

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.

我正在通过访问Google Chrome浏览器中的REST端点并尝试从中下载文件进行测试.

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.

谢谢.

:来源:

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);
    }
}
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();
    //  });
    // }
}
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);
    //      }
    //  };
    // }
}
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);
    }
}
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
<?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分支):

Update: trying Manuel's answer (commited to branch answer_Manuel):

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):

Update: trying Manuel's updated answer (commited to branch answer_Manuel_2020-04-06):

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 会处理该请求,因为注释要求他这样做.

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.

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

If you use the @RestController then the (correct) RequestMappingHandlerAdapter will handle the download, with the timeout set to -1.

原始答案:

您可以尝试通过返回 org.springframework.web.context.request.async.WebAsyncTask .

如果提供设置 Callable< V> ,具有 timeout :

If offers to set a Callable<V> with a timeout:

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

Then your DefaultController could look like:

public WebAsyncTask<ResponseEntity<StreamingResponseBody>> downloadHub() throws IOException {
  ...
  new WebAsyncTask<ResponseEntity<StreamingResponseBody>>(myTimeOutAsLong, callable);
}

更新:

  1. 请从您的REST控制器方法中删除 HttpServletResponse 参数.只需确定, HttpServletResponse 中的 OutputStream 不会干扰 StreamingResponseBody 中的 OutputStream .
  2. 关于错误无法在没有AsyncContext的情况下无法分派":如果没有AsyncContext,则无法调度提供文件时
  1. Please remove the HttpServletResponse parameter from your REST controller method. Just to be sure, that the OutputStream from the HttpServletResponse does not interfere with the OutputStream from StreamingResponseBody.
  2. Regarding the error ´Cannot dispatch without an AsyncContext´: "Cannot dispatch without an AsyncContext" when serving files

这篇关于Spring REST端点在30秒后返回StreamingResponseBody:AsyncRequestTimeoutException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 17:42