From 94d3f095216b7e3f184fd4ff4145b5d6660fdd15 Mon Sep 17 00:00:00 2001 From: Ken Hu <106191785+kenhuuu@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:27:42 -0700 Subject: [PATCH 1/2] Rename configuration to responseSerializer. This makes it consistent among all the GLVs and ensures that users know that is for response size deserialization only and not request serialization. Assisted-by: Claude Code:claude-opus-4-8 --- CHANGELOG.asciidoc | 6 +-- docs/src/reference/gremlin-variants.asciidoc | 8 ++-- docs/src/upgrade/release-4.x.x.asciidoc | 15 ++++---- gremlin-console/conf/remote-secure.yaml | 2 +- gremlin-console/conf/remote-sigv4.yaml | 2 +- gremlin-console/conf/remote.yaml | 2 +- .../gremlin/console/jsr223/remote.yaml | 2 +- .../src/main/java/examples/Connections.java | 4 +- .../tinkerpop/gremlin/driver/Cluster.java | 37 +++++++++++-------- .../tinkerpop/gremlin/driver/Settings.java | 17 +++++---- .../driver/util/ProfilingApplication.java | 2 +- .../driver/ClientBehaviorIntegrateTest.java | 2 +- .../gremlin/driver/SettingsTest.java | 8 ++-- .../gremlin-java/Connections.java | 4 +- .../lib/driver/connection.ts | 21 ++++++----- .../unit/graphbinary/precise-mode-test.js | 10 ++--- .../test/unit/pdt-registry-test.js | 8 ++-- .../gremlin/driver/remote/RemoteWorld.java | 2 +- .../server/GremlinDriverIntegrateTest.java | 20 +++++----- .../server/GremlinResultSetIntegrateTest.java | 2 +- ...emlinServerSerializationIntegrateTest.java | 2 +- .../test/resources/conf/remote-objects.yaml | 2 +- 22 files changed, 93 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 9df6d199c49..e6d9bdbf33a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -30,12 +30,12 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Enabled building and running with Java 21 and Java 25. * Bumped Groovy to 4.0.32, Hadoop to 3.4.3, Spark to 4.1.2 (Scala 2.13), and Netty to 4.2.7 to support Java 25. * Standardized connection options across all GLVs (Java, Python, .NET, Go, JavaScript) per the TinkerPop 4.x GLV proposal; see the upgrade docs for the full per-driver table. *(breaking)* -** Aligned option names across drivers: `maxConnections` (128), `connectTimeoutMillis` (5000), `readTimeoutMillis` (off), `idleTimeoutMillis` (180000), `keepAliveTimeMillis` (30000), `compression` (on/`deflate`), `batchSize` (64), `bulkResults` (false), `maxResponseHeaderBytes`, `proxy`, and `ssl`. Timeouts use a millisecond-suffixed canonical name with an idiomatic duration companion (Java `Duration`, Go `time.Duration`, .NET `TimeSpan`, Python seconds); JavaScript uses milliseconds only. -** Java: renamed `maxConnectionPoolSize`/`connectionSetupTimeoutMillis`/`idleConnectionTimeoutMillis`/`resultIterationBatchSize` and `RequestOptions.addG`->`traversalSource`; removed `maxResponseContentLength` (responses now stream); added `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `url(String)`, `ssl(SslContext)`. *(breaking)* +** Aligned option names across drivers: `maxConnections` (128), `connectTimeoutMillis` (5000), `readTimeoutMillis` (off), `idleTimeoutMillis` (180000), `keepAliveTimeMillis` (30000), `compression` (on/`deflate`), `batchSize` (64), `bulkResults` (false), `maxResponseHeaderBytes`, `proxy`, `ssl`, and `responseSerializer`. Timeouts use a millisecond-suffixed canonical name with an idiomatic duration companion (Java `Duration`, Go `time.Duration`, .NET `TimeSpan`, Python seconds); JavaScript uses milliseconds only. +** Java: renamed `maxConnectionPoolSize`/`connectionSetupTimeoutMillis`/`idleConnectionTimeoutMillis`/`resultIterationBatchSize`/`serializer` and `RequestOptions.addG`->`traversalSource`; removed `maxResponseContentLength` (responses now stream); added `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `url(String)`, `ssl(SslContext)`. *(breaking)* ** Python: renamed `pool_size`->`max_connections` (8->128) and `ssl_options`->`ssl`; removed `headers` (use interceptors) and `max_content_length`; added the timeout, `compression`, `batch_size`, `proxy`, and `trust_env` options; requires `aiohttp>=3.11`. *(breaking)* ** .NET: renamed `MaxConnectionsPerServer`/`ConnectionTimeout`/`IdleConnectionTimeout`/`KeepAliveInterval`/`EnableCompression` and `Auth.BasicAuth`/`Auth.SigV4Auth`->`Auth.Basic`/`Auth.Sigv4`; `KeepAliveTime` now uses a real TCP keep-alive socket option; added `ReadTimeout`, `MaxResponseHeaderBytes`, `Proxy`, `Ssl`, `BulkResults`. *(breaking)* ** Go: renamed `MaximumConcurrentConnections`/`IdleConnectionTimeout`/`KeepAliveInterval`/`ConnectionTimeout`/`TlsConfig`/`RequestInterceptors`/`EnableCompression`; moved auth helpers into an `auth` sub-package (`auth.Basic`/`auth.SigV4`/`auth.SigV4WithCredentials`); added `ReadTimeout`, `MaxResponseHeaderBytes`, `Proxy`, `BatchSize`, `BulkResults`. *(breaking)* -** JavaScript: adopted `undici` for the default dispatcher; added `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`; removed `headers` (use interceptors) and `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` (TLS via the Node/undici runtime); undici is swapped out in browser bundles. *(breaking)* +** JavaScript: adopted `undici` for the default dispatcher; renamed `reader`->`responseSerializer`; added `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`; removed `headers` (use interceptors) and `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` (TLS via the Node/undici runtime); undici is swapped out in browser bundles. *(breaking)* ** All drivers: compression now defaults on; `connectTimeout` lowered to 5s and applied to transport establishment; `readTimeout` is a streaming-safe idle-read timeout (off by default); `idleTimeout` reaps only pooled connections. * Fixed `gremlin-javascript` `Client.submit()` so that an explicit `bulkResults: false` request option is forwarded to the server instead of being silently dropped. * Fixed `gremlin-dotnet` deflate response decompression, which threw on the server's zlib-framed output because it used `DeflateStream` (raw DEFLATE, RFC 1951) instead of `ZLibStream` (zlib, RFC 1950); the bug was previously masked because compression was off by default. diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 51cb6178dee..a58b16ec940 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -1113,8 +1113,8 @@ The following table describes the various configuration options for the Gremlin |password |The password to submit on requests that require authentication. |_none_ |path |The URL path to the Gremlin Server. |_/gremlin_ |port |The port of the Gremlin Server to connect to. The same port will be applied for all hosts. |8182 -|serializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from the server. Note that the serializer configured on the client should be supported by the server configuration. |_none_ -|serializer.config |A `Map` of configuration settings for the serializer. |_none_ +|responseSerializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from the server. Note that the serializer configured on the client should be supported by the server configuration. |_none_ +|responseSerializer.config |A `Map` of configuration settings for the serializer. |_none_ |username |The username to submit on requests that require authentication. |_none_ |workerPoolSize |Size of the pool for handling background work. |available processors * 2 |========================================================= @@ -1237,7 +1237,7 @@ IoRegistry registry = ...; // an IoRegistry instance exposed by a specific graph TypeSerializerRegistry typeSerializerRegistry = TypeSerializerRegistry.build().addRegistry(registry).create(); MessageSerializer serializer = new GraphBinaryMessageSerializerV4(typeSerializerRegistry); Cluster cluster = Cluster.build(). - serializer(serializer). + responseSerializer(serializer). create(); Client client = cluster.connect(); GraphTraversalSource g = traversal().with(DriverRemoteConnection.using(client, "g")); @@ -2024,7 +2024,7 @@ can be passed in the constructor of a new `Client` or `DriverRemoteConnection` : |options.interceptors |RequestInterceptor/RequestInterceptor[] |One or more functions that can modify the HTTP request before it is sent, run in order. |undefined |options.auth |RequestInterceptor |An auth interceptor that is always appended to the end of the interceptor list so it runs last. |undefined |options.preciseNumbers |Boolean |When `true`, wraps deserialized numbers in typed wrappers that preserve the server's original type. |undefined -|options.reader |GraphBinaryReader |The reader to use for deserializing responses. |GraphBinaryReader +|options.responseSerializer |GraphBinaryReader |The reader to use for deserializing responses. |GraphBinaryReader |options.enableUserAgentOnConnect |Boolean |Determines if a user agent header will be sent with requests. |true |options.pdtRegistry |PDTRegistry |A registry for hydrating and dehydrating <>. |undefined |========================================================= diff --git a/docs/src/upgrade/release-4.x.x.asciidoc b/docs/src/upgrade/release-4.x.x.asciidoc index 983c662e4cb..cf708eea66c 100644 --- a/docs/src/upgrade/release-4.x.x.asciidoc +++ b/docs/src/upgrade/release-4.x.x.asciidoc @@ -82,9 +82,10 @@ These change runtime behavior on upgrade even if you do not change your configur ===== Driver-specific notes - *Java* (`gremlin-driver`): renamed `maxConnectionPoolSize`->`maxConnections`, `connectionSetupTimeoutMillis`->`connectTimeoutMillis`, - `idleConnectionTimeoutMillis`->`idleTimeoutMillis`, `resultIterationBatchSize`->`batchSize`, and `RequestOptions` `addG`->`traversalSource`; - old names are removed. New: `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy(ProxyOptions)`, - `url(String)`, `ssl(SslContext)`. Removed `maxResponseContentLength`. `validationRequest` default reconciled to `g.inject(0)`. + `idleConnectionTimeoutMillis`->`idleTimeoutMillis`, `resultIterationBatchSize`->`batchSize`, `serializer`->`responseSerializer`, + and `RequestOptions` `addG`->`traversalSource`; old names are removed. New: `readTimeoutMillis`, `keepAliveTimeMillis`, + `maxResponseHeaderBytes`, `proxy(ProxyOptions)`, `url(String)`, `ssl(SslContext)`. Removed `maxResponseContentLength`. + `validationRequest` default reconciled to `g.inject(0)`. - *Python* (`gremlin-python`): renamed `pool_size`->`max_connections` (default 8->128) and `ssl_options`->`ssl` (old names removed). New: `connect_timeout_millis`, `read_timeout_millis`, `idle_timeout_millis`, `keep_alive_time_millis`, `compression`, `batch_size`, `proxy`, `trust_env`. `auth.sigv4` gained an optional credentials provider. Removed `headers` (use interceptors) @@ -99,10 +100,10 @@ These change runtime behavior on upgrade even if you do not change your configur (`BasicAuth`/`SigV4Auth`/`SigV4AuthWithCredentials`) into a new `auth` sub-package (`auth.Basic`/`auth.SigV4`/`auth.SigV4WithCredentials`). New: `ReadTimeout(Millis)`, `MaxResponseHeaderBytes`, `Proxy` (defaults to `http.ProxyFromEnvironment`), `BatchSize`, `BulkResults`. - *JavaScript* (`gremlin-javascript`): adopted `undici` as a pinned dependency providing the default dispatcher built from the - options above. New: `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `compression`, `batchSize`, - `bulkResults`, `logger`. Removed `headers` (use interceptors) and the `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` options - (TLS is configured through the Node/undici runtime). undici is swapped out in browser bundles, where these connection-pool - options are managed by the browser. + options above. Renamed `reader`->`responseSerializer`. New: `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, + `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`. Removed `headers` (use interceptors) and the + `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` options (TLS is configured through the Node/undici runtime). undici is swapped + out in browser bundles, where these connection-pool options are managed by the browser. See: link:https://lists.apache.org/thread/yqtr2wnb1kq2pqqq4002cz511q5o0bkg[[DISCUSS] Standardizing GLV connection options in TinkerPop 4]. diff --git a/gremlin-console/conf/remote-secure.yaml b/gremlin-console/conf/remote-secure.yaml index 9360315fd9d..36021af3705 100644 --- a/gremlin-console/conf/remote-secure.yaml +++ b/gremlin-console/conf/remote-secure.yaml @@ -32,4 +32,4 @@ auth: { connectionPool: { enableSsl: true, sslEnabledProtocols: [TLSv1.2] } -serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file +responseSerializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file diff --git a/gremlin-console/conf/remote-sigv4.yaml b/gremlin-console/conf/remote-sigv4.yaml index 0ffc3244b8b..ebde10bce0f 100644 --- a/gremlin-console/conf/remote-sigv4.yaml +++ b/gremlin-console/conf/remote-sigv4.yaml @@ -28,4 +28,4 @@ auth: { connectionPool: { enableSsl: true, sslEnabledProtocols: [TLSv1.2] } -serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file +responseSerializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file diff --git a/gremlin-console/conf/remote.yaml b/gremlin-console/conf/remote.yaml index 75659d5292c..b2255f6bb18 100644 --- a/gremlin-console/conf/remote.yaml +++ b/gremlin-console/conf/remote.yaml @@ -28,4 +28,4 @@ hosts: [localhost] port: 8182 -serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} +responseSerializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} diff --git a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/remote.yaml b/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/remote.yaml index 9a232e3dc67..88249735bac 100644 --- a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/remote.yaml +++ b/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/remote.yaml @@ -17,4 +17,4 @@ hosts: [localhost] port: 45940 -serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file +responseSerializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} \ No newline at end of file diff --git a/gremlin-driver/src/main/java/examples/Connections.java b/gremlin-driver/src/main/java/examples/Connections.java index 60f1bbe7497..3c26a378563 100644 --- a/gremlin-driver/src/main/java/examples/Connections.java +++ b/gremlin-driver/src/main/java/examples/Connections.java @@ -77,7 +77,7 @@ private static void withCluster() throws Exception { maxConnections(8). path("/gremlin"). port(SERVER_PORT). - serializer(new GraphBinaryMessageSerializerV4()). + responseSerializer(new GraphBinaryMessageSerializerV4()). create(); GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster, "g")); @@ -95,7 +95,7 @@ private static void withSerializer() throws Exception { MessageSerializer serializer = new GraphBinaryMessageSerializerV4(typeSerializerRegistry); Cluster cluster = Cluster.build(SERVER_HOST). port(SERVER_PORT). - serializer(serializer). + responseSerializer(serializer). create(); Client client = cluster.connect(); GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(client, "g")); diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java index a4b86766030..45b47993b66 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java @@ -188,7 +188,7 @@ private static Builder getBuilderFromSettings(final Settings settings) { addresses.stream().skip(1).forEach(builder::addContactPoint); try { - builder.serializer(settings.serializer.create()); + builder.responseSerializer(settings.responseSerializer.create()); } catch (Exception ex) { throw new IllegalStateException("Could not establish serializer - " + ex.getMessage()); } @@ -522,7 +522,7 @@ public final static class Builder { private final List addresses = new ArrayList<>(); private int port = 8182; private String path = "/gremlin"; - private MessageSerializer serializer = null; + private MessageSerializer responseSerializer = null; private int nioPoolSize = Runtime.getRuntime().availableProcessors(); private int workerPoolSize = Runtime.getRuntime().availableProcessors() * 2; private int maxConnections = ConnectionPool.MAX_POOL_SIZE; @@ -588,29 +588,34 @@ public Builder path(final String path) { } /** - * Set the {@link MessageSerializer} to use given the exact name of a {@link Serializers} enum. Note that - * setting this value this way will not allow specific configuration of the serializer itself. If specific - * configuration is required * please use {@link #serializer(MessageSerializer)}. + * Set the {@link MessageSerializer} used to deserialize responses from the server given the exact name of a + * {@link Serializers} enum. This selects the encoding requested via the {@code Accept} header; requests are + * always sent as JSON regardless of this setting. Note that setting this value this way will not allow specific + * configuration of the serializer itself. If specific configuration is required please use + * {@link #responseSerializer(MessageSerializer)}. */ - public Builder serializer(final String mimeType) { - serializer = Serializers.valueOf(mimeType).simpleInstance(); + public Builder responseSerializer(final String mimeType) { + responseSerializer = Serializers.valueOf(mimeType).simpleInstance(); return this; } /** - * Set the {@link MessageSerializer} to use via the {@link Serializers} enum. If specific configuration is - * required please use {@link #serializer(MessageSerializer)}. + * Set the {@link MessageSerializer} used to deserialize responses from the server via the {@link Serializers} + * enum. This selects the encoding requested via the {@code Accept} header; requests are always sent as JSON + * regardless of this setting. If specific configuration is required please use + * {@link #responseSerializer(MessageSerializer)}. */ - public Builder serializer(final Serializers mimeType) { - serializer = mimeType.simpleInstance(); + public Builder responseSerializer(final Serializers mimeType) { + responseSerializer = mimeType.simpleInstance(); return this; } /** - * Sets the {@link MessageSerializer} to use. + * Sets the {@link MessageSerializer} used to deserialize responses from the server. This selects the encoding + * requested via the {@code Accept} header; requests are always sent as JSON regardless of this setting. */ - public Builder serializer(final MessageSerializer serializer) { - this.serializer = serializer; + public Builder responseSerializer(final MessageSerializer responseSerializer) { + this.responseSerializer = responseSerializer; return this; } @@ -1014,7 +1019,7 @@ List getContactPoints() { public Cluster create() { if (addresses.isEmpty()) addContactPoint("localhost"); - if (null == serializer) serializer = Serializers.GRAPHBINARY_V4.simpleInstance(); + if (null == responseSerializer) responseSerializer = Serializers.GRAPHBINARY_V4.simpleInstance(); if (null != auth) interceptors.add(auth); return new Cluster(this); } @@ -1137,7 +1142,7 @@ private Manager(final Builder builder) { path = builder.path; this.factory = new Factory(builder.nioPoolSize); - this.serializer = builder.serializer; + this.serializer = builder.responseSerializer; this.executor = new ScheduledThreadPoolExecutor(builder.workerPoolSize, new BasicThreadFactory.Builder().namingPattern("gremlin-driver-worker-%d").build()); diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java index cf24ca194a0..9209209b978 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java @@ -60,10 +60,11 @@ public final class Settings { public List hosts = new ArrayList<>(); /** - * The serializer that will be used when communicating with the server. Note that serializer settings should match - * what is available on the server. + * The serializer used to deserialize responses from the server. This selects the encoding requested via the + * {@code Accept} header; requests are always sent as JSON regardless of this setting. Note that this serializer + * should be supported by the server configuration. */ - public SerializerSettings serializer = new SerializerSettings(); + public SerializerSettings responseSerializer = new SerializerSettings(); /** * Settings for connections and connection pool. @@ -107,7 +108,7 @@ public static Settings read(final InputStream stream) { final Constructor constructor = new Constructor(Settings.class, options); final TypeDescription settingsDescription = new TypeDescription(Settings.class); settingsDescription.addPropertyParameters("hosts", String.class); - settingsDescription.addPropertyParameters("serializer", SerializerSettings.class); + settingsDescription.addPropertyParameters("responseSerializer", SerializerSettings.class); constructor.addTypeDescription(settingsDescription); final Yaml yaml = new Yaml(constructor); @@ -138,20 +139,20 @@ public static Settings from(final Configuration conf) { if (conf.containsKey("hosts")) settings.hosts = conf.getList("hosts").stream().map(Object::toString).collect(Collectors.toList()); - if (conf.containsKey("serializer.className")) { + if (conf.containsKey("responseSerializer.className")) { final SerializerSettings serializerSettings = new SerializerSettings(); - final Configuration serializerConf = conf.subset("serializer"); + final Configuration serializerConf = conf.subset("responseSerializer"); if (serializerConf.containsKey("className")) serializerSettings.className = serializerConf.getString("className"); - final Configuration serializerConfigConf = conf.subset("serializer.config"); + final Configuration serializerConfigConf = conf.subset("responseSerializer.config"); if (IteratorUtils.count(serializerConfigConf.getKeys()) > 0) { final Map m = new HashMap<>(); serializerConfigConf.getKeys().forEachRemaining(name -> m.put(name, serializerConfigConf.getProperty(name))); serializerSettings.config = m; } - settings.serializer = serializerSettings; + settings.responseSerializer = serializerSettings; } final Configuration connectionPoolConf = conf.subset("connectionPool"); diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java index 51965633046..9a80fe22355 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/util/ProfilingApplication.java @@ -209,7 +209,7 @@ public static void main(final String[] args) { .maxConnections(maxConnectionPoolSize) .nioPoolSize(nioPoolSize) .maxWaitForConnection(maxWaitForConnection) - .serializer(Serializers.valueOf(serializer)) + .responseSerializer(Serializers.valueOf(serializer)) .workerPoolSize(workerPoolSize).create(); try { diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ClientBehaviorIntegrateTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ClientBehaviorIntegrateTest.java index bc97268977e..56f3da64e97 100644 --- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ClientBehaviorIntegrateTest.java +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ClientBehaviorIntegrateTest.java @@ -86,7 +86,7 @@ private static Cluster.Builder buildCluster() { return Cluster.build("localhost") .validationRequest(SocketServerConstants.GREMLIN_SINGLE_VERTEX) .port(PORT) - .serializer(new GraphBinaryMessageSerializerV4()); + .responseSerializer(new GraphBinaryMessageSerializerV4()); } private static void clearLogs() { diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java index 6af84c2aca7..24b5fd4534b 100644 --- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java @@ -44,8 +44,8 @@ public void shouldCreateFromConfiguration() { conf.setProperty("auth.username", "user1"); conf.setProperty("auth.password", "password1"); conf.setProperty("hosts", Arrays.asList("255.0.0.1", "255.0.0.2", "255.0.0.3")); - conf.setProperty("serializer.className", "my.serializers.MySerializer"); - conf.setProperty("serializer.config.any", "thing"); + conf.setProperty("responseSerializer.className", "my.serializers.MySerializer"); + conf.setProperty("responseSerializer.config.any", "thing"); conf.setProperty("enableUserAgentOnConnect", false); conf.setProperty("bulkResults", true); conf.setProperty("connectionPool.enableSsl", true); @@ -84,8 +84,8 @@ public void shouldCreateFromConfiguration() { assertEquals("user1", settings.auth.username); assertEquals("password1", settings.auth.password); assertEquals(Arrays.asList("255.0.0.1", "255.0.0.2", "255.0.0.3"), settings.hosts); - assertEquals("my.serializers.MySerializer", settings.serializer.className); - assertEquals("thing", settings.serializer.config.get("any")); + assertEquals("my.serializers.MySerializer", settings.responseSerializer.className); + assertEquals("thing", settings.responseSerializer.config.get("any")); assertEquals(false, settings.enableUserAgentOnConnect); assertTrue(settings.bulkResults); assertThat(settings.connectionPool.enableSsl, is(true)); diff --git a/gremlin-examples/gremlin-java/Connections.java b/gremlin-examples/gremlin-java/Connections.java index 3bbbebcbebb..310a80051b2 100644 --- a/gremlin-examples/gremlin-java/Connections.java +++ b/gremlin-examples/gremlin-java/Connections.java @@ -78,7 +78,7 @@ private static void withCluster() throws Exception { maxConnections(8). path("/gremlin"). port(SERVER_PORT). - serializer(new GraphBinaryMessageSerializerV4()). + responseSerializer(new GraphBinaryMessageSerializerV4()). create(); GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster, "g")); @@ -96,7 +96,7 @@ private static void withSerializer() throws Exception { MessageSerializer serializer = new GraphBinaryMessageSerializerV4(typeSerializerRegistry); Cluster cluster = Cluster.build(SERVER_HOST). port(SERVER_PORT). - serializer(serializer). + responseSerializer(serializer). create(); Client client = cluster.connect(); GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(client, "g")); diff --git a/gremlin-js/gremlin-javascript/lib/driver/connection.ts b/gremlin-js/gremlin-javascript/lib/driver/connection.ts index 4a673d8b890..fc4e1c0bda6 100644 --- a/gremlin-js/gremlin-javascript/lib/driver/connection.ts +++ b/gremlin-js/gremlin-javascript/lib/driver/connection.ts @@ -52,7 +52,7 @@ export type Compression = 'none' | 'deflate'; export type ConnectionOptions = { preciseNumbers?: boolean; pdtRegistry?: any; - reader?: any; + responseSerializer?: any; traversalSource?: string; enableUserAgentOnConnect?: boolean; /** Maximum number of concurrent connections per origin. Defaults to 128. */ @@ -89,7 +89,7 @@ export const DEFAULT_BATCH_SIZE = 64; * Represents a single connection to a Gremlin Server. */ export default class Connection extends EventEmitter { - private readonly _reader: any; + private readonly _responseSerializer: any; isOpen = true; traversalSource: string; @@ -113,9 +113,10 @@ export default class Connection extends EventEmitter { ) { super(); - this._reader = options.reader || (options.preciseNumbers === true ? createPreciseReader() : new GraphBinaryReader(ioc)); + this._responseSerializer = + options.responseSerializer || (options.preciseNumbers === true ? createPreciseReader() : new GraphBinaryReader(ioc)); if (options.pdtRegistry) { - this._reader.pdtRegistry = options.pdtRegistry; + this._responseSerializer.pdtRegistry = options.pdtRegistry; } this.traversalSource = options.traversalSource || 'g'; this._enableUserAgentOnConnect = options.enableUserAgentOnConnect !== false; @@ -258,7 +259,7 @@ export default class Connection extends EventEmitter { let completed = false; try { - yield* this._reader.readResponseStream(streamReader); + yield* this._responseSerializer.readResponseStream(streamReader); completed = true; } finally { if (!completed) { @@ -270,11 +271,11 @@ export default class Connection extends EventEmitter { #getReaderForContentType(contentType: string | null) { if (!contentType) { - return this._reader; + return this._responseSerializer; } - if (contentType === this._reader.mimeType) { - return this._reader; + if (contentType === this._responseSerializer.mimeType) { + return this._responseSerializer; } return null; @@ -282,7 +283,7 @@ export default class Connection extends EventEmitter { async #makeHttpRequest(request: RequestMessage, signal?: AbortSignal): Promise { const headers: Record = { - 'Accept': this._reader.mimeType, + 'Accept': this._responseSerializer.mimeType, }; if (this._compression === 'deflate') { @@ -368,7 +369,7 @@ export default class Connection extends EventEmitter { } if (!reader) { - throw new Error(`Response Content-Type '${contentType}' does not match the configured reader (expected '${this._reader.mimeType}')`); + throw new Error(`Response Content-Type '${contentType}' does not match the configured response serializer (expected '${this._responseSerializer.mimeType}')`); } const deserialized = await reader.readResponse(buffer); diff --git a/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js b/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js index 259642aa897..d02e889b996 100644 --- a/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js +++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js @@ -504,18 +504,18 @@ describe('Precise Mode Tests', () => { describe('Connection option wiring', () => { it('preciseNumbers: true uses a precise reader', () => { const conn = new Connection('http://localhost:8182', { preciseNumbers: true }); - assert.ok(conn._reader !== graphBinaryReader); + assert.ok(conn._responseSerializer !== graphBinaryReader); }); - it('explicit reader takes precedence over preciseNumbers', () => { + it('explicit responseSerializer takes precedence over preciseNumbers', () => { const customReader = { custom: true }; - const conn = new Connection('http://localhost:8182', { reader: customReader, preciseNumbers: true }); - assert.strictEqual(conn._reader, customReader); + const conn = new Connection('http://localhost:8182', { responseSerializer: customReader, preciseNumbers: true }); + assert.strictEqual(conn._responseSerializer, customReader); }); it('default uses the default reader', () => { const conn = new Connection('http://localhost:8182', {}); - assert.ok(conn._reader instanceof graphBinaryReader.constructor); + assert.ok(conn._responseSerializer instanceof graphBinaryReader.constructor); }); }); }); diff --git a/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js b/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js index eaa7f8427d5..fb8ea8f2827 100644 --- a/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js +++ b/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js @@ -150,7 +150,7 @@ describe('pdtRegistry wiring through Client/Connection', () => { }); const conn = new Connection('http://localhost:8182', { pdtRegistry: registry }); - assert.strictEqual(conn._reader.pdtRegistry, registry); + assert.strictEqual(conn._responseSerializer.pdtRegistry, registry); }); it('should set pdtRegistry on the reader when passed via Client options', () => { @@ -161,15 +161,15 @@ describe('pdtRegistry wiring through Client/Connection', () => { }); const client = new Client('http://localhost:8182', { pdtRegistry: registry }); - assert.strictEqual(client._connection._reader.pdtRegistry, registry); + assert.strictEqual(client._connection._responseSerializer.pdtRegistry, registry); }); it('should not leak pdtRegistry between connections', () => { const registry = new PDTRegistry(); const conn1 = new Connection('http://localhost:8182', { pdtRegistry: registry }); const conn2 = new Connection('http://localhost:8182'); - assert.isNull(conn2._reader.pdtRegistry); - assert.strictEqual(conn1._reader.pdtRegistry, registry); + assert.isNull(conn2._responseSerializer.pdtRegistry); + assert.strictEqual(conn1._responseSerializer.pdtRegistry, registry); }); }); diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java index 54b811d6e40..bb0dab92e69 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/RemoteWorld.java @@ -50,7 +50,7 @@ public abstract class RemoteWorld implements World { * construct a RemoteWorld. */ public static Cluster createTestCluster(final Serializers serializer) { - return TestClientFactory.build().serializer(serializer).create(); + return TestClientFactory.build().responseSerializer(serializer).create(); } public RemoteWorld(Cluster cluster) { diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java index e16b8e29038..c0b78bac540 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java @@ -293,7 +293,7 @@ public void shouldPropagateErrorWhenInterceptorSetsUnsupportedBodyType() throws @Test public void shouldWorkWithGraphSONResponse() throws Exception { final Cluster cluster = TestClientFactory.build() - .serializer(Serializers.GRAPHSON_V4.simpleInstance()).create(); + .responseSerializer(Serializers.GRAPHSON_V4.simpleInstance()).create(); try { final Client client = cluster.connect(); @@ -311,7 +311,7 @@ public void shouldWorkWithGraphSONResponse() throws Exception { @Test public void shouldWorkWithJsonRequestAndGraphBinaryResponse() throws Exception { final Cluster cluster = TestClientFactory.build() - .serializer(Serializers.GRAPHBINARY_V4.simpleInstance()).create(); + .responseSerializer(Serializers.GRAPHBINARY_V4.simpleInstance()).create(); try { final Client client = cluster.connect(); @@ -324,7 +324,7 @@ public void shouldWorkWithJsonRequestAndGraphBinaryResponse() throws Exception { @Test public void shouldWorkWithJsonRequestAndGraphSONResponse() throws Exception { final Cluster cluster = TestClientFactory.build() - .serializer(Serializers.GRAPHSON_V4.simpleInstance()).create(); + .responseSerializer(Serializers.GRAPHSON_V4.simpleInstance()).create(); try { final Client client = cluster.connect(); @@ -359,7 +359,7 @@ public void shouldInterceptRequestsWithHandshake() throws Exception { @Ignore("Reading for streaming GraphSON is not supported") @Test public void shouldReportErrorWhenRequestCantBeSerialized() throws Exception { - final Cluster cluster = TestClientFactory.build().serializer(Serializers.GRAPHSON_V4).create(); + final Cluster cluster = TestClientFactory.build().responseSerializer(Serializers.GRAPHSON_V4).create(); try { final Client client = cluster.connect().alias("g"); @@ -904,7 +904,7 @@ public void shouldFailWithBadServerSideSerialization() throws Exception { @Test public void shouldWorkWithGraphBinaryV4Serialization() throws Exception { - final Cluster cluster = TestClientFactory.build().serializer(Serializers.GRAPHBINARY_V4).create(); + final Cluster cluster = TestClientFactory.build().responseSerializer(Serializers.GRAPHBINARY_V4).create(); final Client client = cluster.connect(); try { @@ -1121,7 +1121,7 @@ public void shouldAliasGraphVariables() throws Exception { @Test public void shouldAliasTraversalSourceVariables() throws Exception { - final Cluster cluster = TestClientFactory.build().serializer(Serializers.GRAPHBINARY_V4).create(); + final Cluster cluster = TestClientFactory.build().responseSerializer(Serializers.GRAPHBINARY_V4).create(); final Client client = cluster.connect(); try { try { @@ -1346,7 +1346,7 @@ public void shouldRoundTripRegistryPdtViaTraversal() throws Exception { registry.register(new TestPointAdapter()); final Cluster cluster = TestClientFactory.build() - .serializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) + .responseSerializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) .create(); try { final DriverRemoteConnection connection = DriverRemoteConnection.using(cluster); @@ -1369,7 +1369,7 @@ public void shouldRoundTripAnnotatedPdtViaTraversal() throws Exception { registry.register(TestAnnotatedPoint.class); final Cluster cluster = TestClientFactory.build() - .serializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) + .responseSerializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) .create(); try { final DriverRemoteConnection connection = DriverRemoteConnection.using(cluster); @@ -1467,7 +1467,7 @@ public void shouldRoundTripRegisteredPrimitivePdtViaTraversal() { registry.register(new Uint32Adapter()); final Cluster cluster = TestClientFactory.build() - .serializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) + .responseSerializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) .create(); try { final DriverRemoteConnection connection = DriverRemoteConnection.using(cluster); @@ -1494,7 +1494,7 @@ public void shouldRoundTripRegistryNestedPrimitivePdtViaTraversal() { registry.register(Measurement.class); final Cluster cluster = TestClientFactory.build() - .serializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) + .responseSerializer(new GraphBinaryMessageSerializerV4(TypeSerializerRegistry.INSTANCE, registry)) .create(); try { final DriverRemoteConnection connection = DriverRemoteConnection.using(cluster); diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java index f7c61d84b5c..6fbb426f045 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java @@ -82,7 +82,7 @@ public static Iterable data() { @Before public void beforeTest() { - cluster = TestClientFactory.build().serializer(messageSerializer).create(); + cluster = TestClientFactory.build().responseSerializer(messageSerializer).create(); client = cluster.connect(); } diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java index 02a35de7010..9e41f45d799 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java @@ -87,7 +87,7 @@ public static Collection serializers() { @Before public void openConnection() { - cluster = TestClientFactory.build().serializer(serializer).create(); + cluster = TestClientFactory.build().responseSerializer(serializer).create(); client = cluster.connect(); // VertexProperty related test tun on crew graph diff --git a/gremlin-server/src/test/resources/conf/remote-objects.yaml b/gremlin-server/src/test/resources/conf/remote-objects.yaml index 37e1caaa8da..d59161a4014 100644 --- a/gremlin-server/src/test/resources/conf/remote-objects.yaml +++ b/gremlin-server/src/test/resources/conf/remote-objects.yaml @@ -28,4 +28,4 @@ hosts: [localhost] port: 45940 -serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 } \ No newline at end of file +responseSerializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 } \ No newline at end of file From fc760a2b18d67fdb58baba2eaa3d91e83d19ce41 Mon Sep 17 00:00:00 2001 From: Ken Hu <106191785+kenhuuu@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:47:46 -0700 Subject: [PATCH 2/2] Add documentation around request serialization. Assisted-by: Claude Code:claude-fable-5 --- CHANGELOG.asciidoc | 1 + docs/src/reference/gremlin-variants.asciidoc | 36 +++++++++++--------- docs/src/upgrade/release-4.x.x.asciidoc | 25 +++++++++++--- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e6d9bdbf33a..54930cc32b8 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -37,6 +37,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima ** Go: renamed `MaximumConcurrentConnections`/`IdleConnectionTimeout`/`KeepAliveInterval`/`ConnectionTimeout`/`TlsConfig`/`RequestInterceptors`/`EnableCompression`; moved auth helpers into an `auth` sub-package (`auth.Basic`/`auth.SigV4`/`auth.SigV4WithCredentials`); added `ReadTimeout`, `MaxResponseHeaderBytes`, `Proxy`, `BatchSize`, `BulkResults`. *(breaking)* ** JavaScript: adopted `undici` for the default dispatcher; renamed `reader`->`responseSerializer`; added `readTimeoutMillis`, `keepAliveTimeMillis`, `maxResponseHeaderBytes`, `proxy`, `compression`, `batchSize`, `bulkResults`, `logger`; removed `headers` (use interceptors) and `ca`/`cert`/`pfx`/`rejectUnauthorized`/`agent` (TLS via the Node/undici runtime); undici is swapped out in browser bundles. *(breaking)* ** All drivers: compression now defaults on; `connectTimeout` lowered to 5s and applied to transport establishment; `readTimeout` is a streaming-safe idle-read timeout (off by default); `idleTimeout` reaps only pooled connections. +* Changed all GLV drivers to always serialize requests as JSON (`application/json`). Request serialization is no longer configurable and the serializer options (now `responseSerializer`) control only response deserialization. * Fixed `gremlin-javascript` `Client.submit()` so that an explicit `bulkResults: false` request option is forwarded to the server instead of being silently dropped. * Fixed `gremlin-dotnet` deflate response decompression, which threw on the server's zlib-framed output because it used `DeflateStream` (raw DEFLATE, RFC 1951) instead of `ZLibStream` (zlib, RFC 1950); the bug was previously masked because compression was off by default. * Fixed `gremlin-dotnet` SSL options cloning (used on the skip-certificate-validation path) to copy `ClientCertificateContext` and `AllowTlsResume`, which were previously dropped, breaking mTLS client certificates and silently re-enabling TLS resumption. diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index a58b16ec940..b61f9a610df 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -304,6 +304,10 @@ link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_graph_driver_provide |Auth |A single RequestInterceptor for authentication (e.g. `auth.Basic`). Always appended to the end of the interceptor list so it runs last. |nil |========================================================= +Serialization is not configurable in Gremlin-Go. Requests are always serialized as JSON and responses are always +deserialized as GraphBinary. A request body encoding other than JSON can only be produced through a +<>. + Note that no driver timeout bounds the *total* duration of a request once it is under way. `ReadTimeout` only bounds the gap between response chunks, so a response that keeps producing chunks will not time out no matter how long it runs overall, and there is no client-side "overall" request timeout. If you need an absolute deadline, impose it in @@ -1113,7 +1117,7 @@ The following table describes the various configuration options for the Gremlin |password |The password to submit on requests that require authentication. |_none_ |path |The URL path to the Gremlin Server. |_/gremlin_ |port |The port of the Gremlin Server to connect to. The same port will be applied for all hosts. |8182 -|responseSerializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from the server. Note that the serializer configured on the client should be supported by the server configuration. |_none_ +|responseSerializer.className |The fully qualified class name of the `MessageSerializer` that will be used to deserialize responses from the server. Note that the serializer configured on the client should be supported by the server configuration. Requests are always serialized as JSON and can only be customized through a <>. |_none_ |responseSerializer.config |A `Map` of configuration settings for the serializer. |_none_ |username |The username to submit on requests that require authentication. |_none_ |workerPoolSize |Size of the pool for handling background work. |available processors * 2 @@ -1220,8 +1224,10 @@ tx.close(); // commits instead of rolling back === Serialization Remote systems like Gremlin Server and Remote Gremlin Providers respond to requests made in a particular serialization -format and respond by serializing results to some format to be interpreted by the client. For JVM-based languages, -there is a single option for serialization: GraphBinary. +format and respond by serializing results to some format to be interpreted by the client. Requests are always +serialized as JSON (`application/json`) and that behavior is not configurable. The `responseSerializer` option +controls only the `Accept` header sent with each request and the deserialization of the response. For JVM-based +languages, there is a single option for response serialization: GraphBinary. IMPORTANT: 4.0.0-beta.2 Release - There is temporary support for GraphSON in the Java driver which will help with testing, but it is expected that the drivers will only support GraphBinary when 4.0 is fully released. @@ -1245,9 +1251,7 @@ GraphTraversalSource g = traversal().with(DriverRemoteConnection.using(client, " The `IoRegistry` tells the serializer what classes from the graph provider to auto-register during serialization. Gremlin Server roughly uses this same approach when it configures its serializers, so using this same model will -ensure compatibility when making requests. Obviously, it is possible to switch to GraphSON or GraphBinary by using -the appropriate `MessageSerializer` (e.g. `GraphSONMessageSerializerV4` or `GraphBinaryMessageSerializerV4` respectively) -in the same way and building that into the `Cluster` object. +ensure compatibility when making requests. [[gremlin-java-gvalue]] === GValue Parameterization @@ -1433,6 +1437,7 @@ g2Client.submit("g.V()") The above code demonstrates how the `alias` method can be used such that the script need only contain a reference to "g" and "g1" and "g2" are automatically rebound into "g" on the server-side. +[[gremlin-java-interceptors]] ==== RequestInterceptor Gremlin-Java allows for modification of the underlying HTTP request through the use of `RequestInterceptors`. This is @@ -2024,7 +2029,7 @@ can be passed in the constructor of a new `Client` or `DriverRemoteConnection` : |options.interceptors |RequestInterceptor/RequestInterceptor[] |One or more functions that can modify the HTTP request before it is sent, run in order. |undefined |options.auth |RequestInterceptor |An auth interceptor that is always appended to the end of the interceptor list so it runs last. |undefined |options.preciseNumbers |Boolean |When `true`, wraps deserialized numbers in typed wrappers that preserve the server's original type. |undefined -|options.responseSerializer |GraphBinaryReader |The reader to use for deserializing responses. |GraphBinaryReader +|options.responseSerializer |GraphBinaryReader |The reader to use for deserializing responses. Requests are always serialized as JSON and can only be customized through a <>. |GraphBinaryReader |options.enableUserAgentOnConnect |Boolean |Determines if a user agent header will be sent with requests. |true |options.pdtRegistry |PDTRegistry |A registry for hydrating and dehydrating <>. |undefined |========================================================= @@ -2755,9 +2760,7 @@ The following options can be passed to the `GremlinClient` constructor: [width="100%",cols="3,10,^2",options="header"] |========================================================= |Key |Description |Default -|messageSerializer |A single `IMessageSerializer` used for both request and response. |`GraphBinary4MessageSerializer` -|requestSerializer |The serializer for outgoing requests. When `null`, interceptors must serialize the body. |`GraphBinary4MessageSerializer` -|responseSerializer |The serializer for incoming responses. Always required. |`GraphBinary4MessageSerializer` +|responseSerializer |The serializer for incoming responses. Requests are always serialized as JSON and can only be customized through a <>. |`GraphBinary4MessageSerializer` |connectionSettings |The `ConnectionSettings` for the HTTP connection. |default `ConnectionSettings` |loggerFactory |An `ILoggerFactory` for logging. |`NullLoggerFactory` |interceptors |A list of `Func` that modify HTTP requests before sending. |_none_ @@ -2822,8 +2825,9 @@ include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference [[gremlin-dotnet-serialization]] === Serialization -The Gremlin.Net driver uses GraphBinary 4.0 by default. A custom serializer can be provided when creating the -`GremlinClient`. The driver supports separate request and response serializers, or a single serializer for both: +The Gremlin.Net driver uses GraphBinary 4.0 for responses by default. A custom response serializer can be provided +when creating the `GremlinClient`. Requests are always serialized as JSON (`application/json`) and that behavior is +not configurable, however it can be modified with a `RequestInterceptor`: [source,csharp] ---- @@ -2964,9 +2968,9 @@ var client = new GremlinClient(new GremlinServer("localhost", 8182), interceptors: new[] { Auth.Basic("username", "password") }); ---- -When `requestSerializer` is set to `null`, the request body is passed as a `RequestMessage` to interceptors, and an -interceptor is responsible for serializing it to `byte[]` and setting the `Content-Type` header. This follows the -Python driver's `request_serializer=None` pattern and is useful for custom serialization workflows. +Each interceptor receives the request body as a `RequestMessage`. By default, the driver serializes the body to JSON +(`application/json`) after all interceptors have run. An interceptor that requires a different body encoding can +serialize the `RequestMessage` to `byte[]` itself and set the `Content-Type` header accordingly. anchor:gremlin-net-dsl[] [[gremlin-dotnet-dsl]] @@ -3431,7 +3435,7 @@ can be passed to the `Client` or `DriverRemoteConnection` instance as keyword ar |========================================================= |Key |Description |Default |max_workers |Maximum number of worker threads. |Same as `max_connections` (128) -|response_serializer |The response serializer implementation.|`gremlin_python.driver.serializer.GraphBinarySerializersV4` +|response_serializer |The response serializer implementation. Requests are always serialized as JSON and can only be customized through a <>.|`gremlin_python.driver.serializer.GraphBinarySerializersV4` |interceptors |The request interceptors to run before the request body is serialized.|`None` |auth |An authentication interceptor. Always appended to the end of the interceptor list so it runs last. |`None` |max_connections |The maximum number of connections used by the pool. |128 diff --git a/docs/src/upgrade/release-4.x.x.asciidoc b/docs/src/upgrade/release-4.x.x.asciidoc index cf708eea66c..eed61387c5e 100644 --- a/docs/src/upgrade/release-4.x.x.asciidoc +++ b/docs/src/upgrade/release-4.x.x.asciidoc @@ -210,6 +210,23 @@ each GLV's documentation in the link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants] reference. +==== Inconfigurable Request Serialization + +TinkerPop 3.x drivers used a single serializer configuration (for example, `serializer` in the Java driver or +`message_serializer` in Python) that controlled both how a `RequestMessage` was serialized on the way to the server +and how responses were deserialized on the way back. TinkerPop 4 splits these concerns. Requests are now always +serialized as JSON (`application/json`) and that behavior is not configurable. The remaining serializer configuration +is named `responseSerializer` (cased idiomatically per language) and controls only the `Accept` header and response +deserialization. + +Applications that require a different request body encoding, such as GraphBinary for a server that expects it, can +register a request interceptor that serializes the `RequestMessage` and replaces the body and `Content-Type` header. +See the RequestInterceptor section for each GLV in the +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants] +reference. + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3250[TINKERPOP-3250] + ==== Gremlator link:https://gremlator.com[Gremlator] has been rebuilt entirely in JavaScript as a browser-based single-page @@ -476,7 +493,7 @@ interface. The `GraphSONSerializersV4` class and the `gremlin_python.structure.io.graphsonV4` module have been removed. GraphBinary is the only supported wire format for the Python driver in TinkerPop 4. Code that constructed `GraphSONSerializersV4()` or imported from `gremlin_python.structure.io.graphsonV4` must switch to -`GraphBinarySerializersV4` (the default `request_serializer` and `response_serializer`): +`GraphBinarySerializersV4` (the default `response_serializer`): [source,python] ---- @@ -489,8 +506,8 @@ from gremlin_python.driver.connection import GremlinServerError The `protocol_factory` and `transport_factory` parameters have been removed from `Client`, `DriverRemoteConnection`, and `Connection`. Users who were passing a custom `protocol_factory` should use the -`request_serializer`, `response_serializer`, `auth`, and `interceptors` parameters directly instead — these cover -all functionality that `GremlinServerHTTPProtocol` previously provided. +`response_serializer`, `auth`, and `interceptors` parameters directly instead. These cover all functionality that +`GremlinServerHTTPProtocol` previously provided. Transport options like SSL and timeouts are now passed as keyword arguments directly: @@ -514,7 +531,7 @@ Connection(url, traversal_source, protocol, transport_factory, executor, pool) # After Connection(url, traversal_source, executor, pool, - request_serializer=None, response_serializer=None, + response_serializer=None, auth=None, interceptors=None, **transport_kwargs) ----