Skip to content

4.1 Customize user agent #724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions driver/src/main/java/org/neo4j/driver/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,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;

/**
Expand Down Expand Up @@ -98,6 +100,7 @@ public class Config

private final boolean isMetricsEnabled;
private final int eventLoopThreads;
private final String userAgent;

private Config( ConfigBuilder builder )
{
Expand All @@ -108,6 +111,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();

Expand Down Expand Up @@ -261,6 +265,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
*/
Expand All @@ -272,6 +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 = 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();
Expand Down Expand Up @@ -727,6 +740,40 @@ 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;
}

/**
* 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,13 @@
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
* established to a server, specifically as part of the INIT request.
*/
public class ConnectionSettings
{
private 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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
14 changes: 14 additions & 0 deletions driver/src/test/java/org/neo4j/driver/ConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<Transaction> txAction, String expectedErrorMessage )
throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions driver/src/test/resources/hello_with_custom_user_agent.script
Original file line number Diff line number Diff line change
@@ -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: <EXIT>