Skip to content

Deprecate server.connection-timeout and create server-specific configuration keys #18473

Closed
@joedj

Description

@joedj

https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html documents server.connection-timeout as "Time that connectors wait for another HTTP request before closing the connection. When not set, the connector's container-specific default is used. Use a value of -1 to indicate no (that is, an infinite) timeout".

Support for this property was added for Netty, in Issue #15368 / PR #15385

This was done using ChannelOption.CONNECT_TIMEOUT_MILLIS, which does not implement the described functionality. As far as I can tell, CONNECT_TIMEOUT_MILLIS is the timeout for the TCP connection handshake.

The correct way to implement the documented functionality (i.e. an idle/keep-alive timeout) appears to be using Netty's IdleStateHandler.

An example of doing this in a WebServerFactoryCustomizer might look something like this:

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.concurrent.TimeUnit.NANOSECONDS;

@Configuration
public class NettyConfig {

    @Bean
    public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> idleTimeoutCustomizer(
            @Value("${server.netty.idle-timeout}") Duration idleTimeout
    ) {
        return factory ->
                factory.addServerCustomizers(server ->
                        server.tcpConfiguration(tcp ->
                                tcp.bootstrap(bootstrap -> bootstrap.childHandler(new ChannelInitializer<Channel>() {
                                    @Override
                                    protected void initChannel(Channel channel) {
                                        channel.pipeline().addLast(
                                                new IdleStateHandler(0, 0, idleTimeout.toNanos(), NANOSECONDS) {
                                                    private final AtomicBoolean closed = new AtomicBoolean();
                                                    @Override
                                                    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
                                                        if (closed.compareAndSet(false, true)) {
                                                            ctx.close();
                                                        }
                                                    }
                                                }
                                        );
                                    }
                                }))));
    }

}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions