Hi all,

I have put together a proposal to standardize the connection options across the
five Gremlin Language Variants (Java, Python, Go, .NET, JavaScript). The goal 
is a
consistent, idiomatic set of options with the same names, defaults, and behavior
wherever the underlying HTTP stack genuinely supports it.

=====================================================================
SCOPE
=====================================================================
The proposal is split into two parts:

  Part A - HTTP / Connection options: set once when constructing the
           connection (transport, security, pooling, lifecycle, serializer).
  Part B - Request-related options that are set on the connection but applied
           to every request.

True per-request options (evaluationTimeout/timeoutMs, batchSize, bindings,
materializeProperties, requestId, transactionId, etc.) are intentionally left
to a separate task and are not covered here.

=====================================================================
PART A - PROPOSED CHANGES (HTTP / CONNECTION OPTIONS)
=====================================================================

1. Endpoint as a single url
   Add a single `url` entry point to Java/.NET (Python/Go/JS already have it).
   Multi-host stays on the existing contact-points API.

   Proposed changes (before -> after):
     Java:   host/port/path  ->  url (+ host/port/path; multi-host via 
contact-points)
     Python: url  ->  url
     Go:     url  ->  url
     .NET:   host/port/path  ->  url (+ host/port/path overload)
     JS:     url  ->  url

2. Provider-Defined Types (PDT)
   No change. PDTs already replaced the TP3 custom-type mechanism in 4.x and
   all five drivers are at parity.

3. Auth - basic
   Rename .NET to Auth.Basic (the namespace disambiguates) and move Go's
   helpers into an `auth` sub-package so they read auth.Basic(user, pass) -
   the direct analog of Java Auth.basic / .NET Auth.Basic. Breaking change to
   Go call sites (gremlingo.BasicAuth -> auth.Basic); flat functions removed,
   not aliased.

   Proposed changes (before -> after):
     Java:   Auth.basic  ->  Auth.basic
     Python: auth.basic  ->  auth.basic
     Go:     BasicAuth  ->  auth.Basic (new auth sub-package)
     .NET:   Auth.BasicAuth  ->  Auth.Basic (was BasicAuth)
     JS:     auth.basic  ->  auth.basic

4. Auth - sigv4
   Add a credentials-provider variant to Python (the only driver lacking one).
   Rename .NET to Auth.Sigv4 and Go to auth.SigV4 (auth sub-package; breaking,
   no alias). Caveat: importing Go's auth package links the AWS SDK via SigV4.

   Proposed changes (before -> after):
     Java:   Auth.sigv4 (has creds variant)  ->  Auth.sigv4 (creds variant)
     Python: auth.sigv4 (env-only)  ->  auth.sigv4 (creds added)
     Go:     SigV4Auth (has creds variant)  ->  auth.SigV4 (auth sub-package)
     .NET:   Auth.SigV4Auth (has creds variant)  ->  Auth.Sigv4 (was SigV4Auth)
     JS:     auth.sigv4 (has creds variant)  ->  auth.sigv4

5. TLS / SSL config (incl. skip-cert)
   Standardize on one canonical `ssl` option that accepts the language-native
   TLS config (Java SslContext, Python SSLContext, Go *tls.Config, .NET
   SslClientAuthenticationOptions). Adds cert support to .NET (was enableSsl +
   skip only) and folds Java's scattered keyStore/trustStore builders into one
   object. Skip-cert becomes a property of that config (an optional
   skipCertValidation shortcut must merge, not overwrite). JS: N/A as a driver
   option (native fetch has no TLS surface) - delegated to the Node/undici
   runtime; the dead TLS fields are removed (breaking surface change).

   Proposed changes (before -> after):
     Java:   enableSsl + keyStore/trustStore/ciphers  ->  ssl (SslContext)
     Python: ssl_options (SSLContext)  ->  ssl (ssl.SSLContext)
     Go:     TlsConfig (*tls.Config)  ->  Ssl (*tls.Config)
     .NET:   enableSsl + skip flag only; no cert support  ->  Ssl 
(SslClientAuthenticationOptions)
     JS:     fields declared but NOT wired up  ->  via Node/undici dispatcher 
(dead fields removed)

6. Max connections / pool size
   Unify the name and default to 128 (Python is 8 today -> 128). JS ships a
   default dispatcher capped at 128 (was uncapped).

   Proposed changes (before -> after):
     Java:   maxConnectionPoolSize (128)  ->  maxConnections (128)
     Python: pool_size (8)  ->  max_connections (128, was 8)
     Go:     MaximumConcurrentConnections (128)  ->  MaxConnections (128)
     .NET:   MaxConnectionsPerServer (128)  ->  MaxConnections (128)
     JS:     agent field ignored by fetch  ->  default dispatcher caps at 128 
(safety; was uncapped)

7. Idle connection timeout
   Unify the name; 180s default. Python gains it. JS: N/A (undici default fine).

   Proposed changes (before -> after):
     Java:   idleConnectionTimeoutMillis  ->  idleTimeout (180s)
     Python: (none)  ->  idle_timeout (180s)
     Go:     IdleConnectionTimeout (180s)  ->  IdleTimeout (180s)
     .NET:   IdleConnectionTimeout (180s)  ->  IdleTimeout (180s)
     JS:     (none)  ->  via Node/undici, not a driver option (undici default 
4s is fine)

8. Connect timeout
   Unify the name; 5s default. Java already had it (rename from
   connectionSetupTimeoutMillis); .NET 15s -> 5s; Python genuinely gains it.
   JS: N/A (runtime default). Document that connectTimeout is a TCP connection
   (transport establishment) timeout - it bounds the TCP connect plus TLS
   handshake where applicable, not HTTP request/response processing.

   Proposed changes (before -> after):
     Java:   connectionSetupTimeoutMillis  ->  connectTimeout (5s)
     Python: (none)  ->  connect_timeout (5s)
     Go:     ConnectionTimeout (5s)  ->  ConnectTimeout (5s)
     .NET:   ConnectionTimeout (15s)  ->  ConnectTimeout (5s, was 15s)
     JS:     (none)  ->  via Node/undici, not a driver option (undici default 
10s is fine)

9. Keep-alive (idle time before TCP probes)
   Promote to a single canonical keepAliveTime (30s): the idle time before TCP
   keep-alive probes begin, so the pool can detect and evict dead/half-open
   connections (matters when the idle-connection timeout is large). All five
   can express it, each via a different mechanism: Go net.Dialer (today); .NET
   a ConnectCallback socket option (rewired from the inert HTTP/2 ping); Python
   aiohttp socket_options; JS an undici dispatcher setKeepAlive (idle-delay
   only); Java Netty NioChannelOption / TCP_KEEPIDLE (JDK 11+, Linux/macOS).
   Probe interval/count stay at OS defaults.

   Proposed changes (before -> after):
     Java:   (none)  ->  keepAliveTime (NioChannelOption; Linux/macOS)
     Python: (none)  ->  keep_alive_time (TCPConnector socket_options)
     Go:     KeepAliveInterval (30s)  ->  KeepAliveTime (net.Dialer)
     .NET:   HTTP/2 ping, inert on HTTP/1.1  ->  KeepAliveTime (ConnectCallback 
socket option)
     JS:     (none)  ->  keepAliveTime (undici setKeepAlive; idle-delay)

10. Read timeout (NEW)
    Add a streaming-safe idle-read timeout in all five (Java ReadTimeoutHandler,
    Go SetReadDeadline reset, .NET per-read CancelAfter, JS undici bodyTimeout;
    Python read_timeout already works). Partial mitigation for the removed
    maxResponseContentLength (bounds idle time, not total bytes).

   Proposed changes (before -> after):
     Java:   (none)  ->  readTimeout (new; via ReadTimeoutHandler)
     Python: read_timeout/write_timeout  ->  read_timeout (via async_timeout 
wrapper)
     Go:     (none)  ->  ReadTimeout (new; via SetReadDeadline)
     .NET:   via CancellationToken  ->  ReadTimeout (new; via per-read 
CancelAfter)
     JS:     (none)  ->  readTimeout (new; via undici bodyTimeout)

11. Max response content length (REMOVED)
    Remove from all five. It was a total-bytes cap on a now-streamed body;
    default was unlimited and it fired mid-stream on large results. Trade-off:
    drops an opt-in DoS/memory guard. The read timeout is a partial mitigation;
    a true size guard could return later as an explicit incremental-count limit.

   Proposed changes (before -> after):
     Java:   maxResponseContentLength  ->  removed
     Python: accepted but discarded  ->  removed
     Go:     (none)  ->  removed
     .NET:   (none)  ->  removed
     JS:     (none)  ->  removed

12. Max response header size (NEW)
    Expose a canonical maxResponseHeaderBytes (bytes) on Java/Go/.NET/JS,
    converting internally (.NET's native unit is KB). Python is the exception:
    aiohttp has only per-field/per-line caps (max_field_size/max_line_size,
    aiohttp >= 3.9), no byte-total - keep its native options.

   Proposed changes (before -> after):
     Java:   hardcoded 8192 (Netty)  ->  maxResponseHeaderBytes (canonical; via 
Netty codec)
     Python: max_field_size/max_line_size (aiohttp >=3.9); not surfaced  ->  
max_field_size/max_line_size (>=3.9; per-field, no byte-total - canonical N/A)
     Go:     Transport default 10MB, not exposed  ->  MaxResponseHeaderBytes 
(canonical; native bytes)
     .NET:   handler default 64KB, not exposed  ->  MaxResponseHeaderBytes 
(canonical; bytes - converts to native KB)
     JS:     undici default 16KB, not exposed  ->  maxResponseHeaderBytes 
(canonical; via undici maxHeaderSize)

13. HTTP proxy (NEW)
    Expose an explicit proxy option on all five (highest demand). Fixes Go
    silently dropping the env-var proxy today.

   Proposed changes (before -> after):
     Java:   Netty: not exposed  ->  HttpProxyHandler (new; pipeline)
     Python: aiohttp proxy=, but not surfaced by driver  ->  proxy= / trust_env
     Go:     custom Transport drops env proxy (Proxy unset)  ->  
Transport.Proxy = ProxyFromEnvironment (fix)
     .NET:   inherits env-aware DefaultProxy; no explicit option  ->  
SocketsHttpHandler.Proxy / DefaultProxy
     JS:     fetch: no dispatcher passed  ->  undici ProxyAgent on default 
dispatcher (new)

14. Compression
    Standardize one optional `compression` option, default none (off, opt-in);
    set deflate to enable. Deflate is text compression, so on already-compressed
    or incompressible/tiny payloads it adds CPU/latency for no wire-size win -
    hence off by default. Make it algorithm-valued (an extensible enum / typed
    const), not a plain boolean, with boolean accepted as sugar. Deflate is the
    only algorithm the server negotiates today; gzip/br/zstd are reserved and
    added server-side first. Behavior change for Java (sends deflate
    unconditionally today). Rename for Go/.NET (EnableCompression ->
    Compression); new option for Java/Python/JS.

   Proposed changes (before -> after):
     Java:   - (on, no toggle)  ->  Compression enum (default off)
     Python: (none)  ->  compression str/enum (new)
     Go:     EnableCompression (default false)  ->  Compression const (default 
off)
     .NET:   EnableCompression (default false)  ->  Compression enum (default 
off)
     JS:     (none)  ->  compression string union (new)

15. Logging
    Canonical `logger` injection point on all five, accepting each language's
    logging abstraction: Go Logger (+ its LogVerbosity enum, the only built-in
    level), .NET loggerFactory (ILoggerFactory), Java the SLF4J binding, Python
    stdlib logging (gremlinpython logger); JS adds a new logger option
    (non-trivial - no logging foundation today). Verbosity is set via each
    stack's native levels, not a canonical driver enum.

   Proposed changes (before -> after):
     Java:   via slf4j  ->  logger = SLF4J binding
     Python: (none)  ->  logger = stdlib logging
     Go:     LogVerbosity + Logger  ->  Logger + LogVerbosity
     .NET:   loggerFactory  ->  loggerFactory (ILoggerFactory)
     JS:     (none)  ->  logger (new; non-trivial)

16. Default batch size (connection-level) (NEW)
    Add a canonical connection-level defaultBatchSize on all five (Java renames
    resultIterationBatchSize; Python/Go/.NET/JS gain it). It is the default for
    the per-request batchSize when the caller does not set one - pure
    client-side default-filling, no wire or server change. Default 64.

   Proposed changes (before -> after):
     Java:   resultIterationBatchSize (64)  ->  defaultBatchSize (rename 
resultIterationBatchSize; 64)
     Python: (none)  ->  default_batch_size (new)
     Go:     (none)  ->  DefaultBatchSize (new)
     .NET:   (none)  ->  DefaultBatchSize (new)
     JS:     (none)  ->  defaultBatchSize (new)

=====================================================================
PART A - KEPT LANGUAGE-SPECIFIC (NOT STANDARDIZED)
=====================================================================
These are real, live options but have no cross-GLV analog, so they stay
driver-specific rather than being forced onto the others:

- Validation request (Java only): a Gremlin health-check ping used when a host
  is marked unavailable; Java's self-managed pool runs it before returning a
  host to rotation. The OS-managed stacks handle connection health themselves.
  (There is also a default bug to reconcile: Builder default '' vs Settings
  default g.inject(0).)

- Java-only pool knobs: maxWaitForClose and reconnectInterval are tied to
  Java's self-managed Netty pool. (maxWaitForConnection is conceptually
  universal but is subsumed into the connect/request timeout elsewhere.)

- Thread/IO pool sizing: Java nioPoolSize/workerPoolSize and Python max_workers
  are live tuning knobs but tie to Java's Netty pool and Python's
  thread-per-connection model; no analog in the OS-managed stacks. Each knob
  needs user-facing tuning docs - what it sizes, its default, and when to raise
  vs lower it.

- Max idle connections (Go only): MaxIdleConnections is a count-based
  pool-retention policy unique to Go's transport; the others bound idle
  connections by time, not count, and its purpose is already covered by idle
  timeout + max connections.

=====================================================================
PART B - REQUEST-RELATED OPTIONS (SET ON CONNECTION, APPLIED PER REQUEST)
=====================================================================

- Configurable traversal source: already everywhere; Java addG ->
  traversalSource rename, .NET casing change.

   Proposed changes (before -> after):
     Java:   addG  ->  traversalSource
     Python: traversal_source  ->  traversal_source
     Go:     TraversalSource  ->  TraversalSource
     .NET:   traversalSource  ->  TraversalSource
     JS:     traversalSource  ->  traversalSource

- Custom headers: must be set through a RequestInterceptor (the single
  canonical mechanism, present in all five). Drop the standalone `headers`
  option (Python kwarg, JS option) as redundant - breaking for Python/JS;
  migration is a one-line header-setting interceptor. Interceptors run before
  auth is appended, so SigV4 still signs over user headers.

   Proposed changes (before -> after):
     Java:   via interceptor  ->  interceptor
     Python: headers kwarg  ->  interceptor (headers kwarg dropped)
     Go:     via interceptor  ->  interceptor
     .NET:   via interceptor  ->  interceptor
     JS:     headers option  ->  interceptor (headers option dropped)

- Request interceptors: standardize on a plain `interceptors` list; Java keeps
  its named-chain helpers.

   Proposed changes (before -> after):
     Java:   named chain  ->  interceptors (+ named-chain helpers)
     Python: list  ->  interceptors
     Go:     slice  ->  Interceptors (was RequestInterceptors)
     .NET:   list  ->  Interceptors
     JS:     single/array  ->  interceptors

- enableUserAgentOnConnect: already uniform; no change.

- bulkResults: already supported per-request in all five. Java/Python/.NET also
  have a connection-level flag; Go/JS keep it per-request only. No functional
  change - just standardizing the per-request option as canonical.

   Proposed changes (before -> after):
     Java:   bulkResults (conn + per-req)  ->  bulkResults (conn + per-req)
     Python: bulk_results (conn + per-req)  ->  bulk_results (conn + per-req)
     Go:     per-request only (no conn-level flag)  ->  BulkResults 
(per-request only)
     .NET:   BulkResults (conn + per-req)  ->  BulkResults (conn + per-req)
     JS:     per-request only (no conn-level flag)  ->  bulkResults 
(per-request only)

=====================================================================
NOTABLE IMPLEMENTATION CAVEATS
=====================================================================
- JS adopts undici as a new explicit dependency (the driver is fetch-only
  today) to enable the pool cap, bodyTimeout, maxHeaderSize, and proxy.
- Go: server compression is per-GraphBinary-chunk, not generic HTTP
  compression, so Go must keep its explicit deflate decode. Also, the custom
  Transport currently drops the env proxy (fix with ProxyFromEnvironment).
- .NET keep-alive must be rewired from the inert HTTP/2 KeepAlivePingTimeout to
  a ConnectCallback TCP socket option; skip-cert must merge into, not overwrite,
  the user's SslClientAuthenticationOptions.
- Java keep-alive (TCP_KEEPIDLE) is JDK 11+ and platform-dependent
  (Linux/macOS); the read-timeout handler must be armed per request.
- Header-size unit differs (.NET KB vs Go/JS bytes) - document to avoid a 1024x
  misconfiguration.

Thanks,
Guian

Reply via email to