Springboot TomcatEmbeddedServletContainer KeepAliveTimeout无效

时间:2016-03-18 05:24:09

标签: java spring tomcat spring-boot

我已将spring boot embeded tomcat server中的keep alive timeout设置为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环境,其中我有一个安装在AWS EC2实例中的ELB和HAProxy。

根据AWS doc,它表示 ELB的默认空闲连接超时为60秒。所以我把它增加到最多30分钟。

此外它说,

  

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

所以将嵌入式tomcat保持活动超时像上面的代码片段一样增加到30.2分钟

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

参考:AWS dev guide

1 个答案:

答案 0 :(得分:6)

您似乎希望关闭可能在移动设备上发生的废弃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(Bad Gateway)状态代码表示服务器,而     充当网关或代理,收到来自的无效响应     尝试完成请求时访问的入站服务器。

客户端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);
    }
}