【问题标题】:Springboot TomcatEmbeddedServletContainer KeepAliveTimeout not workingSpringboot TomcatEmbeddedServletContainer KeepAliveTimeout 不起作用
【发布时间】:2016-07-04 18:09:27
【问题描述】:

我已将 Spring Boot 嵌入式 tomcat 服务器中的保持活动超时设置为 30 秒。所以我在Application.java下面使用,

@Bean   
public EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    TomcatEmbeddedServletContainerFactory containerFactory = new TomcatEmbeddedServletContainerFactory();
    containerFactory
            .addConnectorCustomizers(new TomcatConnectorCustomizer() {
                @Override
                public void customize(Connector connector) {
                    ((AbstractProtocol) connector.getProtocolHandler())
                            .setKeepAliveTimeout(30000);
                }
            });

    return containerFactory;
}

然后我从我的休息控制器休眠一个请求线程 40 秒。但是当我通过邮递员发出请求时,它成功返回 HTTP 状态代码 200,而不是它应该返回网关超时错误。

我尝试了 setConnectionTimeout 和 setKeepAliveTimeout,但没有成功。

我在这里错过了什么?

编辑问题:我最初的问题

让我解释一下我最初的问题,这导致我提出上述问题。

我有一个很长的投票过程,通常运行大约 5 分钟以上。

所以当我为 longpoll 调用 Rest API 时会发生什么,在 2.2 分钟后,我在浏览器中收到 504 http 错误。

我正在使用 AWS 环境,其中我有一个 ELB 和一个安装在 AWS EC2 实例中的 HAProxy。

根据 AWS 文档,它说 ELB 的默认空闲连接超时为 60 秒。所以我把它增加到30分钟。

而且它说,

如果您使用 HTTP 和 HTTPS 侦听器,我们建议您启用 您的 EC2 实例的 keep-alive 选项。 您可以启用keep-alive in 您的 Web 服务器设置 或 EC2 的内核设置 实例。

因此将嵌入式 tomcat 保持活动超时像上面的代码 sn-p 增加到 30.2 分钟

所以现在我希望我的长轮询请求能够完成,而不会出现 504 错误。但是我仍然在浏览器中收到 504 错误?

参考:AWS dev guide

【问题讨论】:

  • 尝试增加客户端的请求超时时间。
  • 通过 jQuery 例如:$.ajax({ url: address.com/path, timeout: 40000, success: function(args) { } })
  • 谢谢。那么这如何覆盖服务器超时?我的意思是,如果服务器不支持例如 1 分钟的实时请求,那么这将如何工作
  • 你的方法是正确的,但我认为客户自己中止了连接。
  • @Harshana,我不认为我理解你的问题或你想要做什么。连接超时适用于空闲连接,因此您的服务器将永远不会超时并在活动请求时返回响应。如果你有其他进程在你的控制器中,你将不得不在你的控制器中有一些东西超时。在客户端,如果您想这样做,则需要为每个应用程序进行设置。对于邮递员,我找到了这个页面getpostman.com/docs/settings。你的最终目标是什么?

标签: java spring tomcat spring-boot


【解决方案1】:

您似乎想关闭可能发生在移动设备上的废弃 HTTP 连接。

@RestController
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
        TomcatEmbeddedServletContainerFactory containerFactory = new TomcatEmbeddedServletContainerFactory();
        containerFactory
                .addConnectorCustomizers(new TomcatConnectorCustomizer() {
                    @Override
                    public void customize(Connector connector) {
                        ((AbstractProtocol) connector.getProtocolHandler()).setConnectionTimeout(100);
                    }
                });

        return containerFactory;
    }

    @RequestMapping
    public String echo(@RequestBody String body) {
        return body;
    }
}

为了快速运行我的测试,连接超时设置为 100 毫秒。数据在chunks 中发送。在每个块之间,正在运行的线程暂停 x 毫秒。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
@WebIntegrationTest("server.port:19000")
public class DemoApplicationTests {

    private static final int CHUNK_SIZE = 1;
    private static final String HOST = "http://localhost:19000/echo";

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void slowConnection() throws Exception {
        final HttpURLConnection connection = openChunkedConnection();
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());

        writeAndWait(500, out, "chunk1");
        writeAndWait(1, out, "chunk2");

        out.close();

        expectedException.expect(IOException.class);
        expectedException.expectMessage("Server returned HTTP response code: 400 for URL: " + HOST);

        assertResponse("chunk1chunk2=", connection);
    }

    @Test
    public void fastConnection() throws Exception {
        final HttpURLConnection connection = openChunkedConnection();
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());

        writeAndWait(1, out, "chunk1");
        writeAndWait(1, out, "chunk2");

        out.close();

        assertResponse("chunk1chunk2=", connection);
    }

    private void assertResponse(String expected, HttpURLConnection connection) throws IOException {
        Scanner scanner = new Scanner(connection.getInputStream()).useDelimiter("\\A");
        Assert.assertEquals(expected, scanner.next());
    }

    private void writeAndWait(int millis, OutputStreamWriter out, String body) throws IOException, InterruptedException {
        out.write(body);
        Thread.sleep(millis);
    }

    private HttpURLConnection openChunkedConnection() throws IOException {
        final URL url = new URL(HOST);
        final HttpURLConnection  connection = (HttpURLConnection) url.openConnection();
        connection.setDoOutput(true);
        connection.setChunkedStreamingMode(CHUNK_SIZE);
        return connection;
    }
}

将包org.apache.catalina.core的日志级别设置为DEBUG

logging.level.org.apache.catalina.core=DEBUG

您可以看到SocketTimeoutException 用于slowConnection 测试。

我不知道您为什么要将 HTTP 状态代码 502 作为错误响应状态。 HTTP 502 说:

502(坏网关)状态码表示服务器,而 作为网关或代理,从 它在尝试完成请求时访问的入站服务器。

客户端Postman 调用您的服务器应用程序。我在两者之间看不到任何网关或代理。

如果您只是将您的问题压缩到最低限度并且实际上您想自己构建一个代理,您可以考虑使用Netflix Zuul


23.03.2016 更新:

这就是 OP 在 Stackoverflow 上提出问题的根本原因:

我对 longpolling 所做的是,从服务 api 中,我将线程休眠一段时间并唤醒它,然后一次又一次地执行此操作,直到某些 db 状态完成。

该实现实际上阻止了 Tomcat 工作线程处理新的 HTTP 请求。因此,您的请求吞吐量会随着每次额外的长时间运行操作而降低。

我建议将长时间运行的操作卸载到单独的线程中。客户端(浏览器)发起一个新的请求来获取结果。 根据处理状态,服务器返回结果或通知/错误/警告/。

这是一个非常简单的例子:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;

@RestController
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    private ExecutorService executorService = Executors.newFixedThreadPool(10);
    private Map<String, String> results = new ConcurrentHashMap<>();

    @RequestMapping(path = "put/{key}", method = RequestMethod.POST)
    public ResponseEntity<Void> put(@PathVariable String key) {
        executorService.submit(() -> {
            try {
                //simulate a long running process
                Thread.sleep(10000);
                results.put(key, "success");
            } catch (InterruptedException e) {
                results.put(key, "error " + e.getMessage());
                Thread.currentThread().interrupt();
            }
        });
        return new ResponseEntity<>(CREATED);
    }

    @RequestMapping(path = "get/{key}", method = RequestMethod.GET)
    public ResponseEntity<String> get(@PathVariable String key) {
        final String result = results.get(key);
        return new ResponseEntity<>(result, result == null ? NOT_FOUND : OK);
    }
}

【讨论】:

  • 感谢您的详细回答,您的回答帮助我获得了新知识。但我仍然对我的问题有疑问。我认为我问这个问题的方式误导了你。所以我在编辑问题中添加了我原来的问题。请看一下
  • 是的,你的问题误导了我。文档有时可能已经过时。我会将 Tomcat 和 Web 服务器的 keep-alive 时间增加到 10 分钟,然后看看会发生什么。
  • 好吧,我增加了 ELB 和 tomcat(如代码所示)30 分钟,但仍然在 2.2 分钟内获得 503。顺便说一句,当您说tomcat和Web服务器时,您指的是相同的权利吗?因为springboot嵌入的tomcat就是这里的web服务器。
  • 是的,我的意思是 Web 服务器 == 嵌入式 Tomcat。问题是您的浏览器和服务器之间是否有额外的网络服务器。顺便说一句:我认为将普通的 HTTP 连接打开大约 30 分钟并不是一个好主意。您应该考虑切换到轮询或 websockets。
  • 显然我还必须设置 HAProxy 的超时值。顺便说一句,您使用 HAProxy 吗?如果不是,您使用什么作为负载均衡器?
猜你喜欢
  • 2017-08-03
  • 2018-12-08
  • 2018-10-02
  • 2019-04-28
  • 2021-12-10
  • 2021-01-19
  • 1970-01-01
  • 1970-01-01
  • 2017-08-10
相关资源
最近更新 更多