/** * A variant of {@link #ReactorNettyTcpClient(String, int, ReactorNettyCodec)} * that still manages the lifecycle of the {@link TcpClient} and underlying * resources, but allows for direct configuration of other properties of the * client through a {@code Function<TcpClient, TcpClient>}. * @param clientConfigurer the configurer function * @param codec for encoding and decoding the input/output byte streams * @since 5.1.3 * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec */ public ReactorNettyTcpClient(Function<TcpClient, TcpClient> clientConfigurer, ReactorNettyCodec<P> codec) { Assert.notNull(codec, "ReactorNettyCodec is required"); this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); this.loopResources = LoopResources.create("tcp-client-loop"); this.poolResources = ConnectionProvider.elastic("tcp-client-pool"); this.codec = codec; this.tcpClient = clientConfigurer.apply(TcpClient .create(this.poolResources) .runOn(this.loopResources, false) .doOnConnected(conn -> this.channelGroup.add(conn.channel()))); }
@Override public void destroy() { if (this.useGlobalResources) { HttpResources.disposeLoopsAndConnections(); } else { try { ConnectionProvider provider = this.connectionProvider; if (provider != null && this.manageConnectionProvider) { provider.dispose(); } } catch (Throwable ex) { // ignore } try { LoopResources resources = this.loopResources; if (resources != null && this.manageLoopResources) { resources.dispose(); } } catch (Throwable ex) { // ignore } } }
@Override public ListenableFuture<Void> shutdown() { if (this.stopping) { SettableListenableFuture<Void> future = new SettableListenableFuture<>(); future.set(null); return future; } this.stopping = true; Mono<Void> result; if (this.channelGroup != null) { result = FutureMono.from(this.channelGroup.close()); if (this.loopResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.loopResources.disposeLater()); } if (this.poolResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.poolResources.disposeLater()); } result = result.onErrorResume(ex -> Mono.empty()).then(stopScheduler()); } else { result = stopScheduler(); } return new MonoToListenableFutureAdapter<>(result); }
connectionProvider = ConnectionProvider.newConnection(); } else if (pool.getType() == FIXED) { connectionProvider = ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout()); } else { connectionProvider = ConnectionProvider.elastic(pool.getName());
@Override protected Mono<? extends Connection> bind(Bootstrap b) { //Default group and channel if (b.config() .group() == null) { UdpResources loopResources = UdpResources.get(); EventLoopGroup elg = loopResources.onClient(LoopResources.DEFAULT_NATIVE); b.group(elg) .channel(loopResources.onDatagramChannel(elg)); } return ConnectionProvider.newConnection() .acquire(b); } }
/** * Create a new {@link ConnectionProvider} to cache and reuse a fixed maximum * number of {@link Connection}. * <p>A Fixed {@link ConnectionProvider} will open up to the given max connection value. * Further connections will be pending acquisition indefinitely. * * @param name the connection pool name * @param maxConnections the maximum number of connections before starting pending * acquisition on existing ones * * @return a new {@link ConnectionProvider} to cache and reuse a fixed maximum * number of {@link Connection} */ static ConnectionProvider fixed(String name, int maxConnections) { return fixed(name, maxConnections, DEFAULT_POOL_ACQUIRE_TIMEOUT); }
/** * Creates TcpClient for target address. * * @param address connect address * @return tcp client */ private TcpClient newTcpClient(Address address) { return TcpClient.create(ConnectionProvider.newConnection()) .runOn(loopResources) .host(address.host()) .port(address.port()) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout()) .bootstrap(b -> BootstrapHandlers.updateConfiguration(b, "outbound", channelInitializer)); }
@Test public void testIssue416() { TestResources resources = TestResources.get(); TestResources.set(ConnectionProvider.fixed("test")); assertThat(resources.provider.isDisposed()).isTrue(); assertThat(resources.loops.isDisposed()).isFalse(); TestResources.set(LoopResources.create("test")); assertThat(resources.loops.isDisposed()).isTrue(); assertThat(resources.isDisposed()).isTrue(); }
try { final InetSocketAddress address = InetSocketAddress.createUnresolved("localhost", echoServerPort); ConnectionProvider pool = ConnectionProvider.fixed("fixedPoolTwoAcquire", 2); StepVerifier.create(pool.acquire(bootstrap)) .verifyErrorMatches(msg -> msg.getMessage().contains("Connection refused")); StepVerifier.create(pool.acquire(bootstrap)) .verifyErrorMatches(msg -> msg.getMessage().contains("Connection refused")); final PooledConnection c1 = (PooledConnection) pool.acquire(bootstrap) .block(); final PooledConnection c2 = (PooledConnection) pool.acquire(bootstrap) .block(); final PooledConnection c3 = (PooledConnection) pool.acquire(bootstrap) .block(); final PooledConnection c4 = (PooledConnection) pool.acquire(bootstrap) .block();
@Test public void localResources() throws Exception { this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.afterPropertiesSet(); ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider(); LoopResources loopResources = this.resourceFactory.getLoopResources(); assertNotSame(HttpResources.get(), connectionProvider); assertNotSame(HttpResources.get(), loopResources); // The below does not work since ConnectionPoolProvider simply checks if pool is empty. // assertFalse(connectionProvider.isDisposed()); assertFalse(loopResources.isDisposed()); this.resourceFactory.destroy(); assertTrue(connectionProvider.isDisposed()); assertTrue(loopResources.isDisposed()); }
@Override public Mono<? extends Connection> acquire(Bootstrap bootstrap) { return defaultProvider.acquire(bootstrap); }
@Test public void contentHeader() { ConnectionProvider fixed = ConnectionProvider.fixed("test", 1); HttpClient client = HttpClient.create(fixed) .wiretap(true) .headers(h -> h.add("content-length", "1")); HttpResponseStatus r = client.request(HttpMethod.GET) .uri("http://google.com") .send(ByteBufFlux.fromString(Mono.just(" "))) .responseSingle((res, buf) -> Mono.just(res.status())) .block(Duration.ofSeconds(30)); client.request(HttpMethod.GET) .uri("http://google.com") .send(ByteBufFlux.fromString(Mono.just(" "))) .responseSingle((res, buf) -> Mono.just(res.status())) .block(Duration.ofSeconds(30)); Assert.assertEquals(r, HttpResponseStatus.BAD_REQUEST); fixed.dispose(); }
@Override protected Mono<? extends Connection> connect(Bootstrap b) { //Default group and channel if (b.config() .group() == null) { UdpResources loopResources = UdpResources.get(); EventLoopGroup elg = loopResources.onClient(LoopResources.DEFAULT_NATIVE); b.group(elg) .channel(loopResources.onDatagramChannel(elg)); } return ConnectionProvider.newConnection() .acquire(b); } }
/** * a new {@link ConnectionProvider} to cache and reuse a fixed maximum * number of {@link Connection}. * <p>A Fixed {@link ConnectionProvider} will open up to the given max number of * processors observed by this jvm (minimum 4). * Further connections will be pending acquisition indefinitely. * * @param name the connection pool name * * @return a new {@link ConnectionProvider} to cache and reuse a fixed maximum * number of {@link Connection} */ static ConnectionProvider fixed(String name) { return fixed(name, DEFAULT_POOL_MAX_CONNECTIONS); }
@Test public void tcpClientHandlesLineFeedDataFixedPool() throws InterruptedException { Consumer<? super Connection> channelInit = c -> c .addHandler("codec", new LineBasedFrameDecoder(8 * 1024)); // ConnectionProvider p = ConnectionProvider.fixed // ("tcpClientHandlesLineFeedDataFixedPool", 1); ConnectionProvider p = ConnectionProvider.newConnection(); tcpClientHandlesLineFeedData( TcpClient.create(p) .host("localhost") .port(echoServerPort) .doOnConnected(channelInit) ); }
@Override public boolean isDisposed() { return defaultLoops.isDisposed() && defaultProvider.isDisposed(); }
@Override public Mono<? extends Connection> acquire(Bootstrap bootstrap) { return defaultProvider.acquire(bootstrap); }
@Test public void simpleClientPooling() { ConnectionProvider p = ConnectionProvider.fixed("test", 1); AtomicReference<Channel> ch1 = new AtomicReference<>(); AtomicReference<Channel> ch2 = new AtomicReference<>(); HttpResponseStatus r = HttpClient.create(p) .doOnResponse((res, c) -> ch1.set(c.channel())) .wiretap(true) .get() .uri("http://google.com/unsupportedURI") .responseSingle((res, buf) -> buf.thenReturn(res.status())) .block(Duration.ofSeconds(30)); HttpClient.create(p) .doOnResponse((res, c) -> ch2.set(c.channel())) .wiretap(true) .get() .uri("http://google.com/unsupportedURI") .responseSingle((res, buf) -> buf.thenReturn(res.status())) .block(Duration.ofSeconds(30)); AtomicBoolean same = new AtomicBoolean(); same.set(ch1.get() == ch2.get()); Assert.assertTrue(same.get()); Assert.assertEquals(r, HttpResponseStatus.NOT_FOUND); p.dispose(); }
/** * Simple constructor with the host and port to use to connect to. * <p>This constructor manages the lifecycle of the {@link TcpClient} and * underlying resources such as {@link ConnectionProvider}, * {@link LoopResources}, and {@link ChannelGroup}. * <p>For full control over the initialization and lifecycle of the * TcpClient, use {@link #ReactorNettyTcpClient(TcpClient, ReactorNettyCodec)}. * @param host the host to connect to * @param port the port to connect to * @param codec for encoding and decoding the input/output byte streams * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec */ public ReactorNettyTcpClient(String host, int port, ReactorNettyCodec<P> codec) { Assert.notNull(host, "host is required"); Assert.notNull(codec, "ReactorNettyCodec is required"); this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); this.loopResources = LoopResources.create("tcp-client-loop"); this.poolResources = ConnectionProvider.elastic("tcp-client-pool"); this.codec = codec; this.tcpClient = TcpClient.create(this.poolResources) .host(host).port(port) .runOn(this.loopResources, false) .doOnConnected(conn -> this.channelGroup.add(conn.channel())); }