Skip to content

Commit a3cd00d

Browse files
authored
4.1 Customize user agent (#724)
Allow user to customize user_agent string used by the server to identify the connected client
1 parent 4a8363e commit a3cd00d

File tree

10 files changed

+98
-34
lines changed

10 files changed

+98
-34
lines changed

driver/src/main/java/org/neo4j/driver/Config.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.neo4j.driver.exceptions.ServiceUnavailableException;
3131
import org.neo4j.driver.exceptions.SessionExpiredException;
3232
import org.neo4j.driver.exceptions.TransientException;
33+
import org.neo4j.driver.internal.ConnectionSettings;
3334
import org.neo4j.driver.internal.SecuritySettings;
3435
import org.neo4j.driver.internal.async.pool.PoolSettings;
3536
import org.neo4j.driver.internal.cluster.RoutingSettings;
@@ -40,6 +41,7 @@
4041
import org.neo4j.driver.util.Immutable;
4142
import org.neo4j.driver.util.Resource;
4243

44+
import static java.lang.String.format;
4345
import static org.neo4j.driver.Logging.javaUtilLogging;
4446

4547
/**
@@ -98,6 +100,7 @@ public class Config
98100

99101
private final boolean isMetricsEnabled;
100102
private final int eventLoopThreads;
103+
private final String userAgent;
101104

102105
private Config( ConfigBuilder builder )
103106
{
@@ -108,6 +111,7 @@ private Config( ConfigBuilder builder )
108111
this.maxConnectionLifetimeMillis = builder.maxConnectionLifetimeMillis;
109112
this.maxConnectionPoolSize = builder.maxConnectionPoolSize;
110113
this.connectionAcquisitionTimeoutMillis = builder.connectionAcquisitionTimeoutMillis;
114+
this.userAgent = builder.userAgent;
111115

112116
this.securitySettings = builder.securitySettingsBuilder.build();
113117

@@ -261,6 +265,14 @@ public boolean isMetricsEnabled()
261265
return isMetricsEnabled;
262266
}
263267

268+
/**
269+
* @return the user_agent configured for this driver
270+
*/
271+
public String userAgent()
272+
{
273+
return userAgent;
274+
}
275+
264276
/**
265277
* Used to build new config instances
266278
*/
@@ -272,6 +284,7 @@ public static class ConfigBuilder
272284
private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST;
273285
private long maxConnectionLifetimeMillis = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME;
274286
private long connectionAcquisitionTimeoutMillis = PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT;
287+
private String userAgent = format( "neo4j-java/%s", driverVersion() );
275288
private final SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder = new SecuritySettings.SecuritySettingsBuilder();
276289
private int routingFailureLimit = RoutingSettings.DEFAULT.maxRoutingFailures();
277290
private long routingRetryDelayMillis = RoutingSettings.DEFAULT.retryTimeoutDelay();
@@ -727,6 +740,40 @@ public ConfigBuilder withEventLoopThreads( int size )
727740
return this;
728741
}
729742

743+
/**
744+
* Configure the user_agent field sent to the server to identify the connected client.
745+
* @param userAgent the string to configure user_agent.
746+
* @return this builder.
747+
*/
748+
public ConfigBuilder withUserAgent( String userAgent )
749+
{
750+
if ( userAgent == null || userAgent.isEmpty() )
751+
{
752+
throw new IllegalArgumentException( "The user_agent string must not be empty" );
753+
}
754+
this.userAgent = userAgent;
755+
return this;
756+
}
757+
758+
/**
759+
* Extracts the driver version from the driver jar MANIFEST.MF file.
760+
*/
761+
private static String driverVersion()
762+
{
763+
// "Session" is arbitrary - the only thing that matters is that the class we use here is in the
764+
// 'org.neo4j.driver' package, because that is where the jar manifest specifies the version.
765+
// This is done as part of the build, adding a MANIFEST.MF file to the generated jarfile.
766+
Package pkg = Session.class.getPackage();
767+
if ( pkg != null && pkg.getImplementationVersion() != null )
768+
{
769+
return pkg.getImplementationVersion();
770+
}
771+
772+
// If there is no version, we're not running from a jar file, but from raw compiled class files.
773+
// This should only happen during development, so call the version 'dev'.
774+
return "dev";
775+
}
776+
730777
/**
731778
* Create a config instance from this builder.
732779
*

driver/src/main/java/org/neo4j/driver/internal/ConnectionSettings.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,13 @@
1919
package org.neo4j.driver.internal;
2020

2121
import org.neo4j.driver.AuthToken;
22-
import org.neo4j.driver.Session;
23-
24-
import static java.lang.String.format;
2522

2623
/**
2724
* The connection settings are used whenever a new connection is
2825
* established to a server, specifically as part of the INIT request.
2926
*/
3027
public class ConnectionSettings
3128
{
32-
private static final String DEFAULT_USER_AGENT = format( "neo4j-java/%s", driverVersion() );
33-
34-
/**
35-
* Extracts the driver version from the driver jar MANIFEST.MF file.
36-
*/
37-
private static String driverVersion()
38-
{
39-
// "Session" is arbitrary - the only thing that matters is that the class we use here is in the
40-
// 'org.neo4j.driver' package, because that is where the jar manifest specifies the version.
41-
// This is done as part of the build, adding a MANIFEST.MF file to the generated jarfile.
42-
Package pkg = Session.class.getPackage();
43-
if ( pkg != null && pkg.getImplementationVersion() != null )
44-
{
45-
return pkg.getImplementationVersion();
46-
}
47-
48-
// If there is no version, we're not running from a jar file, but from raw compiled class files.
49-
// This should only happen during development, so call the version 'dev'.
50-
return "dev";
51-
}
52-
5329
private final AuthToken authToken;
5430
private final String userAgent;
5531
private final int connectTimeoutMillis;
@@ -61,11 +37,6 @@ public ConnectionSettings( AuthToken authToken, String userAgent, int connectTim
6137
this.connectTimeoutMillis = connectTimeoutMillis;
6238
}
6339

64-
public ConnectionSettings( AuthToken authToken, int connectTimeoutMillis )
65-
{
66-
this( authToken, DEFAULT_USER_AGENT, connectTimeoutMillis );
67-
}
68-
6940
public AuthToken authToken()
7041
{
7142
return authToken;

driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan
103103
MetricsProvider metricsProvider, Config config, boolean ownsEventLoopGroup, RoutingContext routingContext )
104104
{
105105
Clock clock = createClock();
106-
ConnectionSettings settings = new ConnectionSettings( authToken, config.connectionTimeoutMillis() );
106+
ConnectionSettings settings = new ConnectionSettings( authToken, config.userAgent(), config.connectionTimeoutMillis() );
107107
ChannelConnector connector = createConnector( settings, securityPlan, config, clock, routingContext );
108108
PoolSettings poolSettings = new PoolSettings( config.maxConnectionPoolSize(),
109109
config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(),

driver/src/test/java/org/neo4j/driver/ConfigTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,18 @@ void shouldErrorWithIllegalEventLoopThreadsSize( int value ) throws Throwable
315315
{
316316
assertThrows( IllegalArgumentException.class, () -> Config.builder().withEventLoopThreads( value ).build() );
317317
}
318+
319+
@Test
320+
void shouldChangeUserAgent()
321+
{
322+
Config config = Config.builder().withUserAgent( "AwesomeDriver" ).build();
323+
assertThat( config.userAgent(), equalTo( "AwesomeDriver" ) );
324+
}
325+
326+
@Test
327+
void shouldErrorWithInvalidUserAgent()
328+
{
329+
assertThrows( IllegalArgumentException.class, () -> Config.builder().withUserAgent( null ).build() );
330+
assertThrows( IllegalArgumentException.class, () -> Config.builder().withUserAgent( "" ).build() );
331+
}
318332
}

driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ private ChannelConnectorImpl newConnector( AuthToken authToken, int connectTimeo
231231
private ChannelConnectorImpl newConnector( AuthToken authToken, SecurityPlan securityPlan,
232232
int connectTimeoutMillis )
233233
{
234-
ConnectionSettings settings = new ConnectionSettings( authToken, connectTimeoutMillis );
234+
ConnectionSettings settings = new ConnectionSettings( authToken, "test", connectTimeoutMillis );
235235
return new ChannelConnectorImpl( settings, securityPlan, DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY );
236236
}
237237

driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan
449449
MetricsProvider ignored, Config config, boolean ownsEventLoopGroup,
450450
RoutingContext routingContext )
451451
{
452-
ConnectionSettings connectionSettings = new ConnectionSettings( authToken, 1000 );
452+
ConnectionSettings connectionSettings = new ConnectionSettings( authToken, "test", 1000 );
453453
PoolSettings poolSettings = new PoolSettings( config.maxConnectionPoolSize(),
454454
config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(),
455455
config.idleTimeBeforeConnectionTest() );

driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,25 @@ void shouldBeAbleHandleNOOPsDuringRunCypher() throws Exception
598598
assertThat( server.exitStatus(), equalTo( 0 ) );
599599
}
600600

601+
@Test
602+
void shouldSendCustomerUserAgentInHelloMessage() throws Exception
603+
{
604+
StubServer server = stubController.startStub( "hello_with_custom_user_agent.script", 9001 );
605+
606+
Config config = Config.builder().withUserAgent( "AwesomeClient" ).build();
607+
608+
try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", config );
609+
Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) )
610+
{
611+
List<String> names = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( 0 ).asString() );
612+
assertEquals( asList( "Foo", "Bar" ), names );
613+
}
614+
finally
615+
{
616+
assertEquals( 0, server.exitStatus() );
617+
}
618+
}
619+
601620
private static void testTxCloseErrorPropagation( String script, Consumer<Transaction> txAction, String expectedErrorMessage )
602621
throws Exception
603622
{

driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ void shouldFailToAcquireConnectionWhenPoolIsClosed()
145145
private ConnectionPoolImpl newPool() throws Exception
146146
{
147147
FakeClock clock = new FakeClock();
148-
ConnectionSettings connectionSettings = new ConnectionSettings( neo4j.authToken(), 5000 );
148+
ConnectionSettings connectionSettings = new ConnectionSettings( neo4j.authToken(), "test", 5000 );
149149
ChannelConnector connector = new ChannelConnectorImpl( connectionSettings, SecurityPlanImpl.insecure(),
150150
DEV_NULL_LOGGING, clock, RoutingContext.EMPTY );
151151
PoolSettings poolSettings = newSettings();

driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ private NettyChannelPool newPool( AuthToken authToken )
182182

183183
private NettyChannelPool newPool( AuthToken authToken, int maxConnections )
184184
{
185-
ConnectionSettings settings = new ConnectionSettings( authToken, 5_000 );
185+
ConnectionSettings settings = new ConnectionSettings( authToken, "test", 5_000 );
186186
ChannelConnectorImpl connector = new ChannelConnectorImpl( settings, SecurityPlanImpl.insecure(), DEV_NULL_LOGGING,
187187
new FakeClock(), RoutingContext.EMPTY );
188188
return new NettyChannelPool( neo4j.address(), connector, bootstrap, poolHandler, ChannelHealthChecker.ACTIVE,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
!: BOLT 3
2+
!: AUTO RESET
3+
!: AUTO GOODBYE
4+
5+
C: HELLO {"scheme": "none", "user_agent": "AwesomeClient", "routing": null}
6+
S: SUCCESS {"server": "Neo4j/3.5.0", "connection_id": "bolt-123456789"}
7+
C: RUN "MATCH (n) RETURN n.name" {} {}
8+
PULL_ALL
9+
S: SUCCESS {"fields": ["n.name"]}
10+
RECORD ["Foo"]
11+
RECORD ["Bar"]
12+
SUCCESS {}
13+
S: <EXIT>

0 commit comments

Comments
 (0)