Конечная точка Spring REST, возвращающая StreamingResponseBody: AsyncRequestTimeoutException через 30 секунд - PullRequest
0 голосов
/ 04 апреля 2020

У меня та же проблема, что и описанная здесь и здесь .

Я попробовал данные ответы и их комбинации, но ни одна из них не решила мою проблему.

Когда я попытался этот ответ, через 30 секунд вместо тайм-аута загрузка возобновилась с начала, а затем, еще через 30 секунд, истекло время ожидания.

I ' m тестирую, посетив конечную точку REST в Google Chrome и пытаясь загрузить оттуда файл.

Здесь У меня есть проект, который отображает эту ошибку.

Спасибо заранее.

Редактировать: вот источник:

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 \ git hub \ 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>

Обновление: попытка ответа Мануэля (передано ветке 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]

Обновление: пробует обновленный ответ Мануэля (переданный ветке 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 Ответ

1 голос
/ 04 апреля 2020

Чтобы устранить проблему:

Измените @RepositoryRestController на, например, @RestController.

Если вы используете @RepositoryRestController, время ожидания будет установлено на RequestMappingHandlerAdapter. Но после запроса загрузки RepositoryRestHandlerAdapter обрабатывает запрос, поскольку аннотация требует от него.

Если вы используете @RestController, тогда (правильный) RequestMappingHandlerAdapter будет обрабатывать загрузку с таймаутом установите -1. ​​

Исходный ответ:

Вы можете попытаться декларативно / явно определить время ожидания, вернув org.springframework.web. context.request.asyn c .WebAsyncTask .

Если предлагается установить Callable<V> с timeout:

Тогда ваш DefaultController может выглядит так:

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

Обновление:

  1. Удалите параметр HttpServletResponse из метода контроллера REST. Просто чтобы быть уверенным, что OutputStream из HttpServletResponse не мешает OutputStream из StreamingResponseBody.
  2. Относительно ошибки «Невозможно отправить без AsyncContext´: « Невозможно отправить без AsyncContext »при обслуживании файлов
...