From 90f01a675fba5a6085849feda340cb510e37fd7d Mon Sep 17 00:00:00 2001 From: Gregory Woods Date: Tue, 2 Jun 2020 10:42:06 +0100 Subject: [PATCH 1/2] Allow user to customize user_agent string used by the server to identify the connected client --- .../main/java/org/neo4j/driver/Config.java | 27 +++++++++++++++++++ .../driver/internal/ConnectionSettings.java | 2 +- .../neo4j/driver/internal/DriverFactory.java | 2 +- .../java/org/neo4j/driver/ConfigTest.java | 14 ++++++++++ .../internal/DirectDriverBoltKitTest.java | 19 +++++++++++++ .../hello_with_custom_user_agent.script | 13 +++++++++ 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 driver/src/test/resources/hello_with_custom_user_agent.script diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 525d230acb..24385ee265 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -30,6 +30,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; @@ -98,6 +99,7 @@ public class Config private final boolean isMetricsEnabled; private final int eventLoopThreads; + private final String userAgent; private Config( ConfigBuilder builder ) { @@ -108,6 +110,7 @@ private Config( ConfigBuilder builder ) this.maxConnectionLifetimeMillis = builder.maxConnectionLifetimeMillis; this.maxConnectionPoolSize = builder.maxConnectionPoolSize; this.connectionAcquisitionTimeoutMillis = builder.connectionAcquisitionTimeoutMillis; + this.userAgent = builder.userAgent; this.securitySettings = builder.securitySettingsBuilder.build(); @@ -261,6 +264,14 @@ public boolean isMetricsEnabled() return isMetricsEnabled; } + /** + * @return the user_agent configured for this driver + */ + public String userAgent() + { + return userAgent; + } + /** * Used to build new config instances */ @@ -272,6 +283,7 @@ public static class ConfigBuilder private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST; private long maxConnectionLifetimeMillis = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME; private long connectionAcquisitionTimeoutMillis = PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT; + private String userAgent = ConnectionSettings.DEFAULT_USER_AGENT; private final SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder = new SecuritySettings.SecuritySettingsBuilder(); private int routingFailureLimit = RoutingSettings.DEFAULT.maxRoutingFailures(); private long routingRetryDelayMillis = RoutingSettings.DEFAULT.retryTimeoutDelay(); @@ -727,6 +739,21 @@ public ConfigBuilder withEventLoopThreads( int size ) return this; } + /** + * Configure the user_agent field sent to the server to identify the connected client. + * @param userAgent the string to configure user_agent. + * @return this builder. + */ + public ConfigBuilder withUserAgent( String userAgent ) + { + if ( userAgent == null || userAgent.isEmpty() ) + { + throw new IllegalArgumentException( "The user_agent string must not be empty" ); + } + this.userAgent = userAgent; + return this; + } + /** * Create a config instance from this builder. * diff --git a/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java b/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java index a0e01f3646..79eb29d363 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java +++ b/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java @@ -29,7 +29,7 @@ */ public class ConnectionSettings { - private static final String DEFAULT_USER_AGENT = format( "neo4j-java/%s", driverVersion() ); + public static final String DEFAULT_USER_AGENT = format( "neo4j-java/%s", driverVersion() ); /** * Extracts the driver version from the driver jar MANIFEST.MF file. diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index 694aa2039b..544a8d587b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -103,7 +103,7 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan MetricsProvider metricsProvider, Config config, boolean ownsEventLoopGroup, RoutingContext routingContext ) { Clock clock = createClock(); - ConnectionSettings settings = new ConnectionSettings( authToken, config.connectionTimeoutMillis() ); + ConnectionSettings settings = new ConnectionSettings( authToken, config.userAgent(), config.connectionTimeoutMillis() ); ChannelConnector connector = createConnector( settings, securityPlan, config, clock, routingContext ); PoolSettings poolSettings = new PoolSettings( config.maxConnectionPoolSize(), config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(), diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 2c44496001..3a9f4830ab 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -315,4 +315,18 @@ void shouldErrorWithIllegalEventLoopThreadsSize( int value ) throws Throwable { assertThrows( IllegalArgumentException.class, () -> Config.builder().withEventLoopThreads( value ).build() ); } + + @Test + void shouldChangeUserAgent() + { + Config config = Config.builder().withUserAgent( "AwesomeDriver" ).build(); + assertThat( config.userAgent(), equalTo( "AwesomeDriver" ) ); + } + + @Test + void shouldErrorWithInvalidUserAgent() + { + assertThrows( IllegalArgumentException.class, () -> Config.builder().withUserAgent( null ).build() ); + assertThrows( IllegalArgumentException.class, () -> Config.builder().withUserAgent( "" ).build() ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java index c9d130c0e3..79358a8017 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java @@ -598,6 +598,25 @@ void shouldBeAbleHandleNOOPsDuringRunCypher() throws Exception assertThat( server.exitStatus(), equalTo( 0 ) ); } + @Test + void shouldSendCustomerUserAgentInHelloMessage() throws Exception + { + StubServer server = stubController.startStub( "hello_with_custom_user_agent.script", 9001 ); + + Config config = Config.builder().withUserAgent( "AwesomeClient" ).build(); + + try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", config ); + Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) + { + List names = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( 0 ).asString() ); + assertEquals( asList( "Foo", "Bar" ), names ); + } + finally + { + assertEquals( 0, server.exitStatus() ); + } + } + private static void testTxCloseErrorPropagation( String script, Consumer txAction, String expectedErrorMessage ) throws Exception { diff --git a/driver/src/test/resources/hello_with_custom_user_agent.script b/driver/src/test/resources/hello_with_custom_user_agent.script new file mode 100644 index 0000000000..e1beea7401 --- /dev/null +++ b/driver/src/test/resources/hello_with_custom_user_agent.script @@ -0,0 +1,13 @@ +!: BOLT 3 +!: AUTO RESET +!: AUTO GOODBYE + +C: HELLO {"scheme": "none", "user_agent": "AwesomeClient", "routing": null} +S: SUCCESS {"server": "Neo4j/3.5.0", "connection_id": "bolt-123456789"} +C: RUN "MATCH (n) RETURN n.name" {} {} + PULL_ALL +S: SUCCESS {"fields": ["n.name"]} + RECORD ["Foo"] + RECORD ["Bar"] + SUCCESS {} +S: From 196569b8460fab861baf670dd588f52005af2027 Mon Sep 17 00:00:00 2001 From: Gregory Woods Date: Wed, 3 Jun 2020 13:24:57 +0100 Subject: [PATCH 2/2] Review feedback --- .../main/java/org/neo4j/driver/Config.java | 22 +++++++++++++- .../driver/internal/ConnectionSettings.java | 29 ------------------- .../integration/ChannelConnectorImplIT.java | 2 +- .../integration/ConnectionHandlingIT.java | 2 +- .../async/pool/ConnectionPoolImplIT.java | 2 +- .../async/pool/NettyChannelPoolIT.java | 2 +- 6 files changed, 25 insertions(+), 34 deletions(-) diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 24385ee265..296e27fad9 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -41,6 +41,7 @@ import org.neo4j.driver.util.Immutable; import org.neo4j.driver.util.Resource; +import static java.lang.String.format; import static org.neo4j.driver.Logging.javaUtilLogging; /** @@ -283,7 +284,7 @@ public static class ConfigBuilder private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST; private long maxConnectionLifetimeMillis = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME; private long connectionAcquisitionTimeoutMillis = PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT; - private String userAgent = ConnectionSettings.DEFAULT_USER_AGENT; + private String userAgent = format( "neo4j-java/%s", driverVersion() ); private final SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder = new SecuritySettings.SecuritySettingsBuilder(); private int routingFailureLimit = RoutingSettings.DEFAULT.maxRoutingFailures(); private long routingRetryDelayMillis = RoutingSettings.DEFAULT.retryTimeoutDelay(); @@ -754,6 +755,25 @@ public ConfigBuilder withUserAgent( String userAgent ) return this; } + /** + * Extracts the driver version from the driver jar MANIFEST.MF file. + */ + private static String driverVersion() + { + // "Session" is arbitrary - the only thing that matters is that the class we use here is in the + // 'org.neo4j.driver' package, because that is where the jar manifest specifies the version. + // This is done as part of the build, adding a MANIFEST.MF file to the generated jarfile. + Package pkg = Session.class.getPackage(); + if ( pkg != null && pkg.getImplementationVersion() != null ) + { + return pkg.getImplementationVersion(); + } + + // If there is no version, we're not running from a jar file, but from raw compiled class files. + // This should only happen during development, so call the version 'dev'. + return "dev"; + } + /** * Create a config instance from this builder. * diff --git a/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java b/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java index 79eb29d363..19a6335b00 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java +++ b/driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java @@ -19,9 +19,6 @@ package org.neo4j.driver.internal; import org.neo4j.driver.AuthToken; -import org.neo4j.driver.Session; - -import static java.lang.String.format; /** * The connection settings are used whenever a new connection is @@ -29,27 +26,6 @@ */ public class ConnectionSettings { - public static final String DEFAULT_USER_AGENT = format( "neo4j-java/%s", driverVersion() ); - - /** - * Extracts the driver version from the driver jar MANIFEST.MF file. - */ - private static String driverVersion() - { - // "Session" is arbitrary - the only thing that matters is that the class we use here is in the - // 'org.neo4j.driver' package, because that is where the jar manifest specifies the version. - // This is done as part of the build, adding a MANIFEST.MF file to the generated jarfile. - Package pkg = Session.class.getPackage(); - if ( pkg != null && pkg.getImplementationVersion() != null ) - { - return pkg.getImplementationVersion(); - } - - // If there is no version, we're not running from a jar file, but from raw compiled class files. - // This should only happen during development, so call the version 'dev'. - return "dev"; - } - private final AuthToken authToken; private final String userAgent; private final int connectTimeoutMillis; @@ -61,11 +37,6 @@ public ConnectionSettings( AuthToken authToken, String userAgent, int connectTim this.connectTimeoutMillis = connectTimeoutMillis; } - public ConnectionSettings( AuthToken authToken, int connectTimeoutMillis ) - { - this( authToken, DEFAULT_USER_AGENT, connectTimeoutMillis ); - } - public AuthToken authToken() { return authToken; diff --git a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java index 8942adb4d5..4a32d20cab 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java @@ -231,7 +231,7 @@ private ChannelConnectorImpl newConnector( AuthToken authToken, int connectTimeo private ChannelConnectorImpl newConnector( AuthToken authToken, SecurityPlan securityPlan, int connectTimeoutMillis ) { - ConnectionSettings settings = new ConnectionSettings( authToken, connectTimeoutMillis ); + ConnectionSettings settings = new ConnectionSettings( authToken, "test", connectTimeoutMillis ); return new ChannelConnectorImpl( settings, securityPlan, DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY ); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index fc61e0b878..7ca305ff85 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -449,7 +449,7 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan MetricsProvider ignored, Config config, boolean ownsEventLoopGroup, RoutingContext routingContext ) { - ConnectionSettings connectionSettings = new ConnectionSettings( authToken, 1000 ); + ConnectionSettings connectionSettings = new ConnectionSettings( authToken, "test", 1000 ); PoolSettings poolSettings = new PoolSettings( config.maxConnectionPoolSize(), config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(), config.idleTimeBeforeConnectionTest() ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java index d8974bb977..ece8451345 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java @@ -145,7 +145,7 @@ void shouldFailToAcquireConnectionWhenPoolIsClosed() private ConnectionPoolImpl newPool() throws Exception { FakeClock clock = new FakeClock(); - ConnectionSettings connectionSettings = new ConnectionSettings( neo4j.authToken(), 5000 ); + ConnectionSettings connectionSettings = new ConnectionSettings( neo4j.authToken(), "test", 5000 ); ChannelConnector connector = new ChannelConnectorImpl( connectionSettings, SecurityPlanImpl.insecure(), DEV_NULL_LOGGING, clock, RoutingContext.EMPTY ); PoolSettings poolSettings = newSettings(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index de66394c85..2fca7ecbf5 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -182,7 +182,7 @@ private NettyChannelPool newPool( AuthToken authToken ) private NettyChannelPool newPool( AuthToken authToken, int maxConnections ) { - ConnectionSettings settings = new ConnectionSettings( authToken, 5_000 ); + ConnectionSettings settings = new ConnectionSettings( authToken, "test", 5_000 ); ChannelConnectorImpl connector = new ChannelConnectorImpl( settings, SecurityPlanImpl.insecure(), DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY ); return new NettyChannelPool( neo4j.address(), connector, bootstrap, poolHandler, ChannelHealthChecker.ACTIVE,