Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
[[release-4-0-0]]
=== TinkerPop 4.0.0 (NOT OFFICIALLY RELEASED YET)

* Added `HTTPTransport` setting to `gremlin-go` to allow users to provide a custom `http.RoundTripper` for transport-level instrumentation.
* Added grammar-based `Translator` for `gremlin-javascript` supporting translation to JavaScript, Python, Go, .NET, Java, Groovy, canonical, and anonymized output.
* Added `translate_gremlin_query` tool to `gremlin-mcp` that translates Gremlin queries to a target language variant, with optional LLM-assisted normalization via MCP sampling for non-canonical input.
* Modified `gremlin-mcp` to support offline mode where utility tools (translate, format) remain available without a configured `GREMLIN_MCP_ENDPOINT`.
Expand Down
14 changes: 14 additions & 0 deletions gremlin-go/driver/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package gremlingo

import (
"crypto/tls"
"net/http"
"reflect"
"time"

Expand Down Expand Up @@ -61,6 +62,18 @@ type ClientSettings struct {

EnableUserAgentOnConnect bool

// HTTPTransport is the http.RoundTripper used for Gremlin HTTP requests.
//
// When set, the driver uses this transport directly. The following settings
// are ignored as they configure the driver's default transport:
// MaximumConcurrentConnections, MaxIdleConnections, IdleConnectionTimeout,
// KeepAliveInterval, TlsConfig, and EnableCompression. Configure these on
// your transport directly.
//
// When nil (the default), the driver creates an http.Transport configured
// with the above settings.
HTTPTransport http.RoundTripper

// RequestInterceptors are functions that modify HTTP requests before sending.
RequestInterceptors []RequestInterceptor
}
Expand Down Expand Up @@ -105,6 +118,7 @@ func NewClient(url string, configurations ...func(settings *ClientSettings)) (*C
keepAliveInterval: settings.KeepAliveInterval,
enableCompression: settings.EnableCompression,
enableUserAgentOnConnect: settings.EnableUserAgentOnConnect,
httpTransport: settings.HTTPTransport,
}

logHandler := newLogHandler(settings.Logger, settings.LogVerbosity, settings.Language)
Expand Down
30 changes: 20 additions & 10 deletions gremlin-go/driver/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type connectionSettings struct {
keepAliveInterval time.Duration
enableCompression bool
enableUserAgentOnConnect bool
httpTransport http.RoundTripper
}

// connection handles HTTP request/response for Gremlin queries.
Expand All @@ -118,7 +119,24 @@ const (
)

func newConnection(handler *logHandler, url string, connSettings *connectionSettings) *connection {
// Apply defaults for zero values
var rt http.RoundTripper
if connSettings.httpTransport != nil {
// user supplied their own transport, so use it
rt = connSettings.httpTransport
} else {
rt = newDefaultTransport(connSettings)
}

return &connection{
url: url,
httpClient: &http.Client{Transport: rt}, // No Timeout - allows streaming
connSettings: connSettings,
logHandler: handler,
serializer: newGraphBinarySerializer(handler),
}
}

func newDefaultTransport(connSettings *connectionSettings) *http.Transport {
connectionTimeout := connSettings.connectionTimeout
if connectionTimeout == 0 {
connectionTimeout = defaultConnectionTimeout
Expand All @@ -144,7 +162,7 @@ func newConnection(handler *logHandler, url string, connSettings *connectionSett
keepAliveInterval = defaultKeepAliveInterval
}

transport := &http.Transport{
return &http.Transport{
DialContext: (&net.Dialer{
Timeout: connectionTimeout,
KeepAlive: keepAliveInterval,
Expand All @@ -155,14 +173,6 @@ func newConnection(handler *logHandler, url string, connSettings *connectionSett
IdleConnTimeout: idleConnTimeout,
DisableCompression: !connSettings.enableCompression,
}

return &connection{
url: url,
httpClient: &http.Client{Transport: transport}, // No Timeout - allows streaming
connSettings: connSettings,
logHandler: handler,
serializer: newGraphBinarySerializer(handler),
}
}

// AddInterceptor adds a request interceptor to the chain.
Expand Down
74 changes: 74 additions & 0 deletions gremlin-go/driver/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1134,3 +1134,77 @@ func TestDriverRemoteConnectionSettingsWiring(t *testing.T) {
assert.Equal(t, 180*time.Second, transport.IdleConnTimeout)
})
}

// countingRoundTripper is a test RoundTripper that counts requests.
type countingRoundTripper struct {
base http.RoundTripper
count int
}

func (c *countingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
c.count++
return c.base.RoundTrip(req)
}

func TestHTTPTransport(t *testing.T) {
t.Run("custom transport is used for requests", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

counting := &countingRoundTripper{base: http.DefaultTransport}
conn := newConnection(newTestLogHandler(), server.URL, &connectionSettings{
httpTransport: counting,
})

rs, err := conn.submit(&request{gremlin: "g.V()", fields: map[string]interface{}{}})
require.NoError(t, err)
_, _ = rs.All() // drain

assert.Equal(t, 1, counting.count, "custom transport should have been called")
})

t.Run("nil transport uses default http.Transport", func(t *testing.T) {
conn := newConnection(newTestLogHandler(), "http://localhost:8182/gremlin", &connectionSettings{})

// When no custom transport is set, the driver creates an *http.Transport
_, ok := conn.httpClient.Transport.(*http.Transport)
assert.True(t, ok, "default transport should be *http.Transport")
})

t.Run("custom transport ignores pool settings", func(t *testing.T) {
counting := &countingRoundTripper{base: http.DefaultTransport}
conn := newConnection(newTestLogHandler(), "http://localhost:8182/gremlin", &connectionSettings{
httpTransport: counting,
maxConnsPerHost: 256, // should be ignored
})

// The transport should be our custom one, not an http.Transport with pool settings
assert.Equal(t, counting, conn.httpClient.Transport, "custom transport should be used directly")
})

t.Run("ClientSettings wires HTTPTransport", func(t *testing.T) {
counting := &countingRoundTripper{base: http.DefaultTransport}
client, err := NewClient("http://localhost:8182/gremlin",
func(settings *ClientSettings) {
settings.HTTPTransport = counting
})
require.NoError(t, err)
defer client.Close()

assert.Equal(t, counting, client.conn.httpClient.Transport, "custom transport should be wired through")
})

t.Run("DriverRemoteConnectionSettings wires HTTPTransport", func(t *testing.T) {
counting := &countingRoundTripper{base: http.DefaultTransport}
drc, err := NewDriverRemoteConnection("http://localhost:8182/gremlin",
func(settings *DriverRemoteConnectionSettings) {
settings.HTTPTransport = counting
})
require.NoError(t, err)
defer drc.Close()

assert.Equal(t, counting, drc.client.conn.httpClient.Transport, "custom transport should be wired through")
})
}
14 changes: 14 additions & 0 deletions gremlin-go/driver/driverRemoteConnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package gremlingo

import (
"crypto/tls"
"net/http"
"time"

"golang.org/x/text/language"
Expand All @@ -37,6 +38,18 @@ type DriverRemoteConnectionSettings struct {
EnableCompression bool
EnableUserAgentOnConnect bool

// HTTPTransport is the http.RoundTripper used for Gremlin HTTP requests.
//
// When set, the driver uses this transport directly. The following settings
// are ignored as they configure the driver's default transport:
// MaximumConcurrentConnections, MaxIdleConnections, IdleConnectionTimeout,
// KeepAliveInterval, TlsConfig, and EnableCompression. Configure these on
// your transport directly.
//
// When nil (the default), the driver creates an http.Transport configured
// with the above settings.
HTTPTransport http.RoundTripper

// MaximumConcurrentConnections is the maximum number of concurrent TCP connections
// to the Gremlin server. This limits how many requests can be in-flight simultaneously.
// Default: 128. Set to 0 to use the default.
Expand Down Expand Up @@ -103,6 +116,7 @@ func NewDriverRemoteConnection(
keepAliveInterval: settings.KeepAliveInterval,
enableCompression: settings.EnableCompression,
enableUserAgentOnConnect: settings.EnableUserAgentOnConnect,
httpTransport: settings.HTTPTransport,
}

logHandler := newLogHandler(settings.Logger, settings.LogVerbosity, settings.Language)
Expand Down
Loading