org.neo4j.server.queryapi.driver.LocalChannelConnector Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.server.queryapi.driver;
import static org.neo4j.server.queryapi.driver.LocalChannelDriverFactory.IGNORED_HTTP_DRIVER_URI;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.local.LocalAddress;
import java.time.Clock;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import org.neo4j.driver.AuthTokenManager;
import org.neo4j.driver.Logging;
import org.neo4j.driver.internal.BoltAgent;
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.internal.GqlNotificationConfig;
import org.neo4j.driver.internal.async.connection.ChannelConnectedListener;
import org.neo4j.driver.internal.async.connection.ChannelConnector;
import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilderImpl;
import org.neo4j.driver.internal.async.connection.HandshakeCompletedListener;
import org.neo4j.driver.internal.async.connection.NettyChannelInitializer;
import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler;
import org.neo4j.driver.internal.cluster.RoutingContext;
import org.neo4j.driver.internal.security.SecurityPlan;
import org.neo4j.driver.internal.security.SecurityPlanImpl;
/**
* A {@link ChannelConnector} which enables the driver to connect to bolt server's
* {@link io.netty.channel.local.LocalChannel} interface.
*/
class LocalChannelConnector implements ChannelConnector {
private static final BoltServerAddress IGNORED_ADDRESS = new BoltServerAddress(IGNORED_HTTP_DRIVER_URI);
private final LocalAddress localAddress;
private final Clock clock;
private final Logging logging;
private final String userAgent;
private final BoltAgent boltAgent;
private final GqlNotificationConfig notificationConfig;
private final AuthTokenManager authTokenManager;
private final SecurityPlan securityPlan;
public LocalChannelConnector(
LocalAddress localAddress,
String userAgent,
BoltAgent boltAgent,
AuthTokenManager authTokenManager,
GqlNotificationConfig notificationConfig,
SecurityPlan securityPlan,
Clock clock,
Logging logging) {
this.localAddress = localAddress;
this.userAgent = userAgent;
this.boltAgent = boltAgent;
this.notificationConfig = notificationConfig;
this.authTokenManager = authTokenManager;
this.securityPlan = securityPlan;
this.clock = clock;
this.logging = logging;
}
@Override
public ChannelFuture connect(
BoltServerAddress ignored,
Bootstrap bootstrap,
Function channelFutureExtensionMapper) {
// todo address needed for tracking channels, disable tracking?
var sslContextStage = securityPlan.sslContext();
var channelFutureCompletableFuture = new CompletableFuture();
sslContextStage.whenComplete((sslContext, throwable) -> {
bootstrap.handler(new NettyChannelInitializer(
IGNORED_ADDRESS, SecurityPlanImpl.insecure(), 1000, authTokenManager, sslContext, clock, logging));
ChannelFuture channelConnected = bootstrap.connect(localAddress);
Channel channel = channelConnected.channel();
ChannelPromise handshakeCompleted = channel.newPromise();
ChannelPromise connectionInitialized = channel.newPromise();
installChannelConnectedListeners(channelConnected, handshakeCompleted);
installHandshakeCompletedListeners(handshakeCompleted, connectionInitialized);
channelFutureCompletableFuture.complete(channelFutureExtensionMapper.apply(connectionInitialized));
});
return new DeferredChannelFuture(channelFutureCompletableFuture, logging);
}
private void installChannelConnectedListeners(ChannelFuture channelConnected, ChannelPromise handshakeCompleted) {
var pipeline = channelConnected.channel().pipeline();
// add timeout handler to the pipeline when channel is connected. it's needed to limit amount of time code
// spends in TLS and Bolt handshakes. prevents infinite waiting when database does not respond
channelConnected.addListener(future -> pipeline.addFirst(new ConnectTimeoutHandler(1000)));
// add listener that sends Bolt handshake bytes when channel is connected
channelConnected.addListener(new ChannelConnectedListener(
IGNORED_ADDRESS, new ChannelPipelineBuilderImpl(), handshakeCompleted, logging));
}
private void installHandshakeCompletedListeners(
ChannelPromise handshakeCompleted, ChannelPromise connectionInitialized) {
var pipeline = handshakeCompleted.channel().pipeline();
// remove timeout handler from the pipeline once TLS and Bolt handshakes are completed. regular protocol
// messages will flow next and we do not want to have read timeout for them
handshakeCompleted.addListener(future -> pipeline.remove(ConnectTimeoutHandler.class));
// add listener that sends an INIT message. connection is now fully established. channel pipeline if fully
// set to send/receive messages for a selected protocol version
handshakeCompleted.addListener(new HandshakeCompletedListener(
userAgent, boltAgent, RoutingContext.EMPTY, connectionInitialized, notificationConfig, clock));
}
}