From ae0cf7f1a74010a2fa60fcfb02e5006a12729ce4 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Fri, 3 Jul 2026 09:12:54 -0700 Subject: [PATCH 1/4] Added proxy tests Assisted-by: Kiro: Claude Opus 4.8 --- gremlin-dotnet/docker-compose.yml | 2 + .../Driver/ProxyBehaviorIntegrationTests.cs | 108 ++++++ .../tinkerpop/gremlin/driver/Channelizer.java | 23 +- .../driver/ProxyBehaviorIntegrateTest.java | 143 +++++++ gremlin-go/docker-compose.yml | 2 + gremlin-go/driver/client_behavior_test.go | 103 +++++ .../gremlin-javascript/docker-compose.yml | 2 + .../test/integration/proxy-behavior-tests.js | 52 +++ gremlin-python/docker-compose.yml | 2 + .../driver/test_client_behavior.py | 37 +- .../gremlin-socket-server/Dockerfile | 1 + .../socket/server/RecordingProxyServer.java | 366 ++++++++++++++++++ .../socket/server/SocketServerConstants.java | 2 + .../socket/server/SocketServerRunner.java | 6 +- 14 files changed, 845 insertions(+), 4 deletions(-) create mode 100644 gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ProxyBehaviorIntegrationTests.cs create mode 100644 gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ProxyBehaviorIntegrateTest.java create mode 100644 gremlin-js/gremlin-javascript/test/integration/proxy-behavior-tests.js create mode 100644 gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java diff --git a/gremlin-dotnet/docker-compose.yml b/gremlin-dotnet/docker-compose.yml index 244bd3f289e..36d2f55f35d 100644 --- a/gremlin-dotnet/docker-compose.yml +++ b/gremlin-dotnet/docker-compose.yml @@ -50,6 +50,7 @@ services: - SOCKET_SERVER_VERSION=${GREMLIN_SERVER} ports: - "45943:45943" + - "45944:45944" gremlin-dotnet-integration-tests: container_name: gremlin-dotnet-integration-tests @@ -65,6 +66,7 @@ services: - GREMLIN_SERVER_PORT=45940 - GREMLIN_SECURE_SERVER_PORT=45941 - GREMLIN_SOCKET_SERVER_URL=http://gremlin-socket-server-test-dotnet:45943/gremlin + - GREMLIN_SOCKET_SERVER_PROXY_URL=http://gremlin-socket-server-test-dotnet:45944 - VERTEX_LABEL=dotnet-example working_dir: /gremlin-dotnet command: > diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ProxyBehaviorIntegrationTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ProxyBehaviorIntegrationTests.cs new file mode 100644 index 00000000000..5653cabbafe --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ProxyBehaviorIntegrationTests.cs @@ -0,0 +1,108 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Gremlin.Net.Driver; +using Xunit; + +namespace Gremlin.Net.IntegrationTest.Driver +{ + public class ProxyBehaviorIntegrationTests + { + private static readonly string Host = GetHost(); + private static readonly string ProxyUrl = GetProxyUrl(); + + private static string GetHost() + { + var url = Environment.GetEnvironmentVariable("GREMLIN_SOCKET_SERVER_URL"); + if (string.IsNullOrEmpty(url)) return "localhost"; + try + { + return new Uri(url).Host; + } + catch + { + return "localhost"; + } + } + + private static string GetProxyUrl() + { + var url = Environment.GetEnvironmentVariable("GREMLIN_SOCKET_SERVER_PROXY_URL"); + return string.IsNullOrEmpty(url) ? "http://localhost:45944" : url; + } + + private static async Task ResetProxyAsync(HttpClient httpClient) + { + using var response = await httpClient.PostAsync($"{ProxyUrl}/__reset", null); + response.EnsureSuccessStatusCode(); + } + + private static async Task GetRecordedAsync(HttpClient httpClient) + { + var json = await httpClient.GetStringAsync($"{ProxyUrl}/__recorded"); + return JsonSerializer.Deserialize(json) ?? Array.Empty(); + } + + [Fact] + public async Task ShouldRouteSocketServerTrafficThroughProxy() + { + using var httpClient = new HttpClient(); + await ResetProxyAsync(httpClient); + + var server = new GremlinServer(Host, SocketServerConstants.Port); + using (var client = new GremlinClient(server, + connectionSettings: new ConnectionSettings { Proxy = new WebProxy(ProxyUrl) })) + { + var resultSet = await client.SubmitAsync(SocketServerConstants.GremlinSingleVertex); + var results = await resultSet.ToListAsync(); + Assert.Single(results); + } + + var recorded = await GetRecordedAsync(httpClient); + Assert.Contains(recorded, t => t.EndsWith(":45943")); + } + + [Fact] + public async Task ShouldNotRecordWhenNoProxy() + { + using var httpClient = new HttpClient(); + await ResetProxyAsync(httpClient); + + var server = new GremlinServer(Host, SocketServerConstants.Port); + using (var client = new GremlinClient(server)) + { + var resultSet = await client.SubmitAsync(SocketServerConstants.GremlinSingleVertex); + var results = await resultSet.ToListAsync(); + Assert.Single(results); + } + + var recorded = await GetRecordedAsync(httpClient); + Assert.DoesNotContain(recorded, t => t.EndsWith(":45943")); + } + } +} diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Channelizer.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Channelizer.java index bab50ede53c..04cea7823d1 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Channelizer.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Channelizer.java @@ -29,6 +29,9 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.proxy.HttpProxyHandler; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; import io.netty.handler.timeout.IdleStateHandler; import org.apache.tinkerpop.gremlin.driver.exception.ConnectionException; import org.apache.tinkerpop.gremlin.driver.handler.GremlinResponseHandler; @@ -147,9 +150,10 @@ protected void initChannel(final SocketChannel socketChannel) { // TLS handshake. final ProxyOptions proxy = cluster.getProxy(); if (proxy != null) { + final SocketAddress proxyAddress = resolveProxyAddress(proxy.getAddress()); final HttpProxyHandler proxyHandler = proxy.hasCredentials() - ? new HttpProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()) - : new HttpProxyHandler(proxy.getAddress()); + ? new HttpProxyHandler(proxyAddress, proxy.getUsername(), proxy.getPassword()) + : new HttpProxyHandler(proxyAddress); pipeline.addLast(PIPELINE_PROXY_HANDLER, proxyHandler); } @@ -189,6 +193,21 @@ public void connected() { } } } + + /** + * Resolves an unresolved proxy {@link SocketAddress} so that Netty's {@code HttpProxyHandler} can connect to + * it. A resolved address (or a non-{@link InetSocketAddress}) is returned unchanged. + */ + private static SocketAddress resolveProxyAddress(final SocketAddress address) { + if (address instanceof InetSocketAddress) { + final InetSocketAddress inet = (InetSocketAddress) address; + if (inet.isUnresolved()) { + return new InetSocketAddress(inet.getHostString(), inet.getPort()); + } + } + return address; + } + } /** diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ProxyBehaviorIntegrateTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ProxyBehaviorIntegrateTest.java new file mode 100644 index 00000000000..0da00b9d530 --- /dev/null +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ProxyBehaviorIntegrateTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.driver; + +import org.apache.tinkerpop.gremlin.socket.server.RecordingProxyServer; +import org.apache.tinkerpop.gremlin.socket.server.SimpleTestServer; +import org.apache.tinkerpop.gremlin.socket.server.SocketServerConstants; +import org.apache.tinkerpop.gremlin.socket.server.TestHttpServerInitializer; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Proves that {@link Cluster.Builder#proxy(ProxyOptions)} routes driver traffic through an HTTP proxy. + *

+ * A {@link SimpleTestServer} (the real Gremlin socket server) runs on {@link SocketServerConstants#PORT} + * and an in-process {@link RecordingProxyServer} runs on {@link SocketServerConstants#PROXY_PORT}. The + * proxy records the {@code host:port} target of every tunnel it establishes and exposes a small control + * API ({@code GET /__recorded}, {@code POST /__reset}) that the tests query with the JDK11 + * {@link java.net.http.HttpClient}. + */ +public class ProxyBehaviorIntegrateTest { + + private static final int PORT = SocketServerConstants.PORT; + private static final int PROXY_PORT = SocketServerConstants.PROXY_PORT; + + private static SimpleTestServer server; + private static RecordingProxyServer proxyServer; + private static HttpClient httpClient; + + @BeforeClass + public static void setUp() throws InterruptedException { + server = new SimpleTestServer(PORT); + server.start(new TestHttpServerInitializer()); + + proxyServer = new RecordingProxyServer(PROXY_PORT); + proxyServer.start(); + + httpClient = HttpClient.newHttpClient(); + } + + @AfterClass + public static void tearDown() { + if (server != null) server.stop(); + if (proxyServer != null) proxyServer.stop(); + } + + private static Cluster.Builder buildCluster() { + return Cluster.build("localhost") + .validationRequest(SocketServerConstants.GREMLIN_SINGLE_VERTEX) + .port(PORT) + .serializer(new GraphBinaryMessageSerializerV4()); + } + + private static String getRecorded() throws Exception { + final HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:" + PROXY_PORT + "/__recorded")) + .GET() + .build(); + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } + + private static void resetRecorded() throws Exception { + final HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://localhost:" + PROXY_PORT + "/__reset")) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } + + @Test + public void shouldRouteTrafficThroughProxy() throws Exception { + resetRecorded(); + + final Cluster cluster = buildCluster() + .proxy(ProxyOptions.create("localhost", PROXY_PORT)) + .create(); + try { + final Client client = cluster.connect(); + final List results = client.submit(SocketServerConstants.GREMLIN_SINGLE_VERTEX).all().get(); + assertEquals(1, results.size()); + assertThat(results.get(0).getObject(), instanceOf(Vertex.class)); + + // the driver issues an HTTP CONNECT, so the proxy should have recorded the tunnel to the + // socket server. Match on the ":45943" substring to stay tolerant of the host form. + final String recorded = getRecorded(); + assertTrue("Expected the proxy to have recorded the tunnel to :" + PORT + " but was: " + recorded, + recorded.contains(":" + PORT)); + } finally { + cluster.close(); + } + } + + @Test + public void shouldNotRecordWithoutProxy() throws Exception { + resetRecorded(); + + final Cluster cluster = buildCluster().create(); + try { + final Client client = cluster.connect(); + final List results = client.submit(SocketServerConstants.GREMLIN_SINGLE_VERTEX).all().get(); + assertEquals(1, results.size()); + assertThat(results.get(0).getObject(), instanceOf(Vertex.class)); + + // without a proxy configured the driver connects directly, so nothing should be recorded. + final String recorded = getRecorded(); + assertFalse("Expected no tunnel to :" + PORT + " to be recorded but was: " + recorded, + recorded.contains(":" + PORT)); + } finally { + cluster.close(); + } + } +} diff --git a/gremlin-go/docker-compose.yml b/gremlin-go/docker-compose.yml index 8966ed0bffa..91cf67e30cf 100644 --- a/gremlin-go/docker-compose.yml +++ b/gremlin-go/docker-compose.yml @@ -50,6 +50,7 @@ services: - SOCKET_SERVER_VERSION=${GREMLIN_SERVER} ports: - "45943:45943" + - "45944:45944" gremlin-go-integration-tests: container_name: gremlin-go-integration-tests @@ -63,6 +64,7 @@ services: - GREMLIN_SERVER_URL=http://gremlin-server-test:45940/gremlin - GREMLIN_SERVER_BASIC_AUTH_URL=https://gremlin-server-test:45941/gremlin - GREMLIN_SOCKET_SERVER_URL=http://gremlin-socket-server-test-go:45943/gremlin + - GREMLIN_SOCKET_SERVER_PROXY_URL=http://gremlin-socket-server-test-go:45944 - RUN_INTEGRATION_TESTS=true - RUN_INTEGRATION_WITH_ALIAS_TESTS=true - RUN_BASIC_AUTH_INTEGRATION_TESTS=true diff --git a/gremlin-go/driver/client_behavior_test.go b/gremlin-go/driver/client_behavior_test.go index f274c0ad2c5..b358b064041 100644 --- a/gremlin-go/driver/client_behavior_test.go +++ b/gremlin-go/driver/client_behavior_test.go @@ -21,7 +21,11 @@ package gremlingo import ( "context" + "encoding/json" "fmt" + "net/http" + "net/url" + "strings" "sync" "testing" "time" @@ -35,6 +39,105 @@ func socketServerURL() string { fmt.Sprintf("http://localhost:%d/gremlin", socketServerPort)) } +func socketServerProxyURL() string { + return getEnvOrDefaultString("GREMLIN_SOCKET_SERVER_PROXY_URL", + "http://localhost:45944") +} + +// resetProxyRecording clears the proxy's recorded targets via POST /__reset. +func resetProxyRecording(t *testing.T) { + t.Helper() + proxyURL := socketServerProxyURL() + resp, err := http.Post(proxyURL+"/__reset", "application/json", nil) + if err != nil { + t.Skipf("Recording proxy not available: %v", err) + } + resp.Body.Close() +} + +// recordedProxyTargets fetches the recorded "host:port" targets via GET /__recorded. +func recordedProxyTargets(t *testing.T) []string { + t.Helper() + proxyURL := socketServerProxyURL() + resp, err := http.Get(proxyURL + "/__recorded") + if err != nil { + t.Skipf("Recording proxy not available: %v", err) + } + defer resp.Body.Close() + + var recorded []string + if err := json.NewDecoder(resp.Body).Decode(&recorded); err != nil { + t.Fatalf("failed to decode recorded proxy targets: %v", err) + } + return recorded +} + +// anyTargetHasPortSuffix reports whether any recorded target ends with ":", +// matching on port suffix only so the host portion is ignored. +func anyTargetHasPortSuffix(targets []string, port int) bool { + suffix := fmt.Sprintf(":%d", port) + for _, target := range targets { + if strings.HasSuffix(target, suffix) { + return true + } + } + return false +} + +// TestProxyRoutesSocketServerTraffic verifies that traffic submitted through a +// client configured with a Proxy is routed to the socket server via the shared +// recording proxy, and that the proxy records the socket server target. +func TestProxyRoutesSocketServerTraffic(t *testing.T) { + resetProxyRecording(t) + + parsedProxyURL, err := url.Parse(socketServerProxyURL()) + require.NoError(t, err) + + client, err := NewClient(socketServerURL(), func(settings *ClientSettings) { + settings.Proxy = func(*http.Request) (*url.URL, error) { + return parsedProxyURL, nil + } + }) + require.NoError(t, err) + defer client.Close() + + rs, err := client.Submit(gremlinSingleVertex) + require.NoError(t, err) + results, err := rs.All() + require.NoError(t, err) + assert.Equal(t, 1, len(results)) + + targets := recordedProxyTargets(t) + assert.True(t, anyTargetHasPortSuffix(targets, socketServerPort), + "expected at least one recorded target ending with :%d, got %v", socketServerPort, targets) +} + +// TestProxyNegativeControl verifies that a client connecting directly to the +// socket server (no Proxy configured) does not route through the recording +// proxy, so the proxy records no socket server target. +func TestProxyNegativeControl(t *testing.T) { + resetProxyRecording(t) + + client, err := NewClient(socketServerURL(), func(settings *ClientSettings) { + // Force a direct connection (no proxy) regardless of ambient proxy env vars. + settings.Proxy = func(*http.Request) (*url.URL, error) { + return nil, nil + } + }) + require.NoError(t, err) + defer client.Close() + + rs, err := client.Submit(gremlinSingleVertex) + require.NoError(t, err) + results, err := rs.All() + require.NoError(t, err) + assert.Equal(t, 1, len(results)) + + targets := recordedProxyTargets(t) + assert.False(t, anyTargetHasPortSuffix(targets, socketServerPort), + "expected no recorded target ending with :%d, got %v", socketServerPort, targets) +} + func newSocketServerClient(t *testing.T, configurations ...func(*ClientSettings)) *Client { t.Helper() url := socketServerURL() diff --git a/gremlin-js/gremlin-javascript/docker-compose.yml b/gremlin-js/gremlin-javascript/docker-compose.yml index 1c00082634c..2505109bbf0 100644 --- a/gremlin-js/gremlin-javascript/docker-compose.yml +++ b/gremlin-js/gremlin-javascript/docker-compose.yml @@ -50,6 +50,7 @@ services: - SOCKET_SERVER_VERSION=${GREMLIN_SERVER} ports: - "45943:45943" + - "45944:45944" gremlin-js-integration-tests: container_name: gremlin-js-integration-tests @@ -66,6 +67,7 @@ services: - DOCKER_ENVIRONMENT=true - GREMLIN_SERVER_URL=http://gremlin-server-test-js:45940/gremlin - GREMLIN_SOCKET_SERVER_URL=http://gremlin-socket-server-test-js:45943/gremlin + - GREMLIN_SOCKET_SERVER_PROXY_URL=http://gremlin-socket-server-test-js:45944 - VERTEX_LABEL=javascript-example - IO_TEST_DIRECTORY=/workspace/gremlin-js/gremlin-javascript/gremlin-test/graphbinary/ - NPM_CONFIG_CACHE=/tmp/npm-cache diff --git a/gremlin-js/gremlin-javascript/test/integration/proxy-behavior-tests.js b/gremlin-js/gremlin-javascript/test/integration/proxy-behavior-tests.js new file mode 100644 index 00000000000..7d410c41fe5 --- /dev/null +++ b/gremlin-js/gremlin-javascript/test/integration/proxy-behavior-tests.js @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'assert'; + +import Client from '../../lib/driver/client.js'; + +import { GREMLIN_SINGLE_VERTEX } from './socket-server-constants.js'; + +const url = process.env.GREMLIN_SOCKET_SERVER_URL || 'http://localhost:45943/gremlin'; +const proxyUrl = process.env.GREMLIN_SOCKET_SERVER_PROXY_URL || 'http://localhost:45944'; + +describe('proxy behavior', function () { + this.timeout(30000); + + it('routes socket server traffic through the configured proxy', async function () { + await fetch(proxyUrl + '/__reset', { method: 'POST' }); + const client = new Client(url, { traversalSource: 'g', proxy: proxyUrl }); + await client.open?.(); + const result = await client.submit(GREMLIN_SINGLE_VERTEX); + assert.strictEqual(result.length, 1); + const recorded = await (await fetch(proxyUrl + '/__recorded')).json(); + assert.ok(recorded.some((t) => t.endsWith(':45943')), JSON.stringify(recorded)); + await client.close(); + }); + + it('does not record when no proxy is configured', async function () { + await fetch(proxyUrl + '/__reset', { method: 'POST' }); + const client = new Client(url, { traversalSource: 'g' }); + const result = await client.submit(GREMLIN_SINGLE_VERTEX); + assert.strictEqual(result.length, 1); + const recorded = await (await fetch(proxyUrl + '/__recorded')).json(); + assert.ok(!recorded.some((t) => t.endsWith(':45943'))); + await client.close(); + }); +}); diff --git a/gremlin-python/docker-compose.yml b/gremlin-python/docker-compose.yml index cdb4cbfdcc5..0eee3f2a5dd 100644 --- a/gremlin-python/docker-compose.yml +++ b/gremlin-python/docker-compose.yml @@ -51,6 +51,7 @@ services: - SOCKET_SERVER_VERSION=${GREMLIN_SERVER} ports: - "45943:45943" + - "45944:45944" gremlin-python-integration-tests: container_name: gremlin-python-integration-tests @@ -65,6 +66,7 @@ services: - GREMLIN_SERVER_URL=http://gremlin-server-test-python:{}/gremlin - GREMLIN_SERVER_BASIC_AUTH_URL=https://gremlin-server-test-python:{}/gremlin - GREMLIN_SOCKET_SERVER_URL=http://gremlin-socket-server-test-python:45943/gremlin + - GREMLIN_SOCKET_SERVER_PROXY_URL=http://gremlin-socket-server-test-python:45944 - IO_TEST_DIRECTORY=/python_app/gremlin-test/graphbinary/ - PYTEST_ARGS=${PYTEST_ARGS:-} - RADISH_ARGS=${RADISH_ARGS:-} diff --git a/gremlin-python/src/main/python/tests/integration/driver/test_client_behavior.py b/gremlin-python/src/main/python/tests/integration/driver/test_client_behavior.py index 013280e39fc..18eef5edf8e 100644 --- a/gremlin-python/src/main/python/tests/integration/driver/test_client_behavior.py +++ b/gremlin-python/src/main/python/tests/integration/driver/test_client_behavior.py @@ -18,8 +18,10 @@ # import asyncio +import json import os import time +import urllib.request from concurrent.futures import ThreadPoolExecutor, as_completed import pytest @@ -43,6 +45,7 @@ ) url = os.environ.get('GREMLIN_SOCKET_SERVER_URL', 'http://localhost:{}/gremlin'.format(PORT)) +proxy_url = os.environ.get('GREMLIN_SOCKET_SERVER_PROXY_URL', 'http://localhost:45944') @pytest.fixture(scope="module") @@ -50,7 +53,8 @@ def socket_server_client(): try: client = Client(url, 'g') # Verify connectivity - client.submit(GREMLIN_SINGLE_VERTEX).all().result() + result = client.submit(GREMLIN_SINGLE_VERTEX).all().result() + assert len(result) == 1 except Exception: pytest.skip("Socket server is not available at {}".format(url)) yield client @@ -195,3 +199,34 @@ def submit_close(): assert len(vertex_results) == 5 assert len(close_errors) == 5 + + +def test_proxy_routes_socket_server_traffic(): + # Reset the recording proxy, route a client through it, and confirm the + # proxy observed traffic to the socket server on port 45943. + urllib.request.urlopen(urllib.request.Request(proxy_url + '/__reset', method='POST')) + + client = Client(url, 'g', proxy=proxy_url) + try: + result = client.submit(GREMLIN_SINGLE_VERTEX).all().result() + assert len(result) == 1 + + recorded = json.loads(urllib.request.urlopen(proxy_url + '/__recorded').read().decode()) + assert any(target.endswith(':45943') for target in recorded) + finally: + client.close() + + +def test_proxy_negative_control(): + # Negative control: without the proxy kwarg the client connects directly, + # so the recording proxy should observe no traffic to the socket server. + urllib.request.urlopen(urllib.request.Request(proxy_url + '/__reset', method='POST')) + + client = Client(url, 'g') + try: + client.submit(GREMLIN_SINGLE_VERTEX).all().result() + + recorded = json.loads(urllib.request.urlopen(proxy_url + '/__recorded').read().decode()) + assert not any(target.endswith(':45943') for target in recorded) + finally: + client.close() diff --git a/gremlin-tools/gremlin-socket-server/Dockerfile b/gremlin-tools/gremlin-socket-server/Dockerfile index f40c91a66d8..370281c5a6d 100644 --- a/gremlin-tools/gremlin-socket-server/Dockerfile +++ b/gremlin-tools/gremlin-socket-server/Dockerfile @@ -32,5 +32,6 @@ COPY ${SOCKET_SERVER_DIR}/libs/ /opt/gremlin-socket-server/libs/ WORKDIR /opt/gremlin-socket-server EXPOSE 45943 +EXPOSE 45944 ENTRYPOINT ["java","-jar","gremlin-socket-server.jar"] diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java new file mode 100644 index 00000000000..04f33429b42 --- /dev/null +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.socket.server; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.util.ReferenceCountUtil; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * A self-contained Netty HTTP proxy used for cross-GLV proxy testing. It records the + * {@code host:port} target of every request it proxies and exposes a small control API so tests can + * prove that traffic actually transited the proxy. + *

+ * Three kinds of requests are supported: + *

    + *
  • HTTP CONNECT tunneling (used by Java/Netty and JS/undici clients): the target is + * recorded, a 200 "Connection Established" is returned and the pipeline is reconfigured to relay + * raw bytes bidirectionally between the client and a freshly opened upstream connection.
  • + *
  • Absolute-URI forward requests (used by Go/Python/.NET for http targets): the target + * authority is recorded and the request is forwarded to that target over a new upstream HTTP + * connection with the response relayed back to the client.
  • + *
  • Origin-form control requests: {@code GET /__recorded} returns a JSON array of the + * recorded targets, {@code POST /__reset} clears the recorded list. Any other control path + * returns 404.
  • + *
+ */ +public class RecordingProxyServer { + + private final int port; + private final List recordedTargets = new CopyOnWriteArrayList<>(); + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + + public RecordingProxyServer(final int port) { + this.port = port; + } + + public Channel start() throws InterruptedException { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + final ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(final SocketChannel ch) { + ch.pipeline().addLast("codec", new HttpServerCodec()); + ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); + ch.pipeline().addLast("handler", new ProxyFrontendHandler(recordedTargets)); + } + }); + return b.bind(port).sync().channel(); + } + + public void stop() { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + /** + * Handles the client-facing side of the proxy: CONNECT tunneling, absolute-URI forwarding and + * the origin-form control API. + */ + private static class ProxyFrontendHandler extends SimpleChannelInboundHandler { + + private final List recordedTargets; + + ProxyFrontendHandler(final List recordedTargets) { + this.recordedTargets = recordedTargets; + } + + @Override + protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) { + final String uri = request.uri(); + if (HttpMethod.CONNECT.equals(request.method())) { + handleConnect(ctx, uri); + } else if (uri.startsWith("/")) { + // origin-form -> control API + handleControl(ctx, request); + } else { + // absolute-form -> forward to the target origin + handleForward(ctx, request); + } + } + + // (a) CONNECT tunneling: record host:port, reply 200, then relay raw bytes. + private void handleConnect(final ChannelHandlerContext ctx, final String target) { + recordedTargets.add(target); + + final int colon = target.lastIndexOf(':'); + final String host = target.substring(0, colon); + final int targetPort = Integer.parseInt(target.substring(colon + 1)); + + final Channel clientChannel = ctx.channel(); + // Pause reads on the client until the tunnel is fully wired up. + clientChannel.config().setAutoRead(false); + + final Bootstrap b = new Bootstrap(); + b.group(clientChannel.eventLoop()) + .channel(NioSocketChannel.class) + .option(ChannelOption.AUTO_READ, false) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(final Channel ch) { + ch.pipeline().addLast(new RelayHandler(clientChannel)); + } + }); + + final ChannelFuture connectFuture = b.connect(host, targetPort); + final Channel upstreamChannel = connectFuture.channel(); + connectFuture.addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + sendControlResponse(clientChannel, HttpResponseStatus.BAD_GATEWAY, "", "text/plain"); + closeOnFlush(clientChannel); + return; + } + final FullHttpResponse established = new DefaultFullHttpResponse( + HTTP_1_1, new HttpResponseStatus(200, "Connection Established")); + established.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); + clientChannel.writeAndFlush(established).addListener((ChannelFutureListener) writeFuture -> { + if (!writeFuture.isSuccess()) { + closeOnFlush(upstreamChannel); + closeOnFlush(clientChannel); + return; + } + // Switch to a raw byte relay. Add the relay before removing the HTTP handlers + // (codec last) so any bytes buffered by the codec flow into the relay. + clientChannel.pipeline().addLast(new RelayHandler(upstreamChannel)); + clientChannel.pipeline().remove("handler"); + clientChannel.pipeline().remove("aggregator"); + clientChannel.pipeline().remove("codec"); + clientChannel.config().setAutoRead(true); + upstreamChannel.config().setAutoRead(true); + clientChannel.read(); + upstreamChannel.read(); + }); + }); + } + + // (b) Absolute-URI forward: record host:port, forward request, relay response back. + private void handleForward(final ChannelHandlerContext ctx, final FullHttpRequest request) { + final URI parsed; + try { + parsed = new URI(request.uri()); + } catch (Exception e) { + sendControlResponse(ctx.channel(), HttpResponseStatus.BAD_REQUEST, "", "text/plain"); + return; + } + + final String host = parsed.getHost(); + final int targetPort = parsed.getPort(); + + recordedTargets.add(host + ":" + targetPort); + + // Build an origin-form request for the upstream. Copy synchronously since the inbound + // request is released once channelRead0 returns. + String pathAndQuery = parsed.getRawPath(); + if (pathAndQuery == null || pathAndQuery.isEmpty()) { + pathAndQuery = "/"; + } + if (parsed.getRawQuery() != null) { + pathAndQuery = pathAndQuery + "?" + parsed.getRawQuery(); + } + + final FullHttpRequest forwardRequest = request.copy(); + forwardRequest.setUri(pathAndQuery); + forwardRequest.headers().set(HttpHeaderNames.HOST, host + ":" + targetPort); + + final Channel clientChannel = ctx.channel(); + final String upstreamHost = host; + final int upstreamPort = targetPort; + + final Bootstrap b = new Bootstrap(); + b.group(clientChannel.eventLoop()) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(final Channel ch) { + ch.pipeline().addLast(new HttpClientCodec()); + ch.pipeline().addLast(new HttpObjectAggregator(65536)); + ch.pipeline().addLast(new UpstreamResponseHandler(clientChannel)); + } + }); + + b.connect(upstreamHost, upstreamPort).addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + forwardRequest.release(); + sendControlResponse(clientChannel, HttpResponseStatus.BAD_GATEWAY, "", "text/plain"); + return; + } + future.channel().writeAndFlush(forwardRequest); + }); + } + + // (c) Origin-form control API. + private void handleControl(final ChannelHandlerContext ctx, final FullHttpRequest request) { + final String uri = request.uri(); + if (HttpMethod.GET.equals(request.method()) && "/__recorded".equals(uri)) { + sendControlResponse(ctx.channel(), HttpResponseStatus.OK, toJsonArray(recordedTargets), + "application/json"); + } else if (HttpMethod.POST.equals(request.method()) && "/__reset".equals(uri)) { + recordedTargets.clear(); + sendControlResponse(ctx.channel(), HttpResponseStatus.OK, "", "text/plain"); + } else { + sendControlResponse(ctx.channel(), HttpResponseStatus.NOT_FOUND, "", "text/plain"); + } + } + + @Override + public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { + closeOnFlush(ctx.channel()); + } + + /** + * Builds a JSON array of strings by hand to avoid pulling in a JSON dependency. + */ + private static String toJsonArray(final List targets) { + final StringBuilder sb = new StringBuilder("["); + final List snapshot = new ArrayList<>(targets); + for (int i = 0; i < snapshot.size(); i++) { + if (i > 0) { + sb.append(","); + } + sb.append("\"").append(escapeJson(snapshot.get(i))).append("\""); + } + sb.append("]"); + return sb.toString(); + } + + private static String escapeJson(final String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } + } + + /** + * Writes a simple full HTTP response with the given body and content type. + */ + private static void sendControlResponse(final Channel channel, final HttpResponseStatus status, + final String body, final String contentType) { + final ByteBuf content = Unpooled.copiedBuffer(body, StandardCharsets.UTF_8); + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, content); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8"); + HttpUtil.setContentLength(response, content.readableBytes()); + channel.writeAndFlush(response); + } + + private static void closeOnFlush(final Channel channel) { + if (channel != null && channel.isActive()) { + channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } + } + + /** + * Relays raw bytes to a peer channel and mirrors close/error events so both ends of a tunnel are + * torn down together. + */ + private static class RelayHandler extends ChannelInboundHandlerAdapter { + + private final Channel relayChannel; + + RelayHandler(final Channel relayChannel) { + this.relayChannel = relayChannel; + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, final Object msg) { + if (relayChannel.isActive()) { + relayChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { + if (future.isSuccess()) { + ctx.channel().read(); + } else { + future.channel().close(); + } + }); + } else { + ReferenceCountUtil.release(msg); + } + } + + @Override + public void channelInactive(final ChannelHandlerContext ctx) { + closeOnFlush(relayChannel); + } + + @Override + public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { + closeOnFlush(ctx.channel()); + } + } + + /** + * Relays a forwarded HTTP response from the upstream origin back to the client, then closes the + * upstream connection. + */ + private static class UpstreamResponseHandler extends SimpleChannelInboundHandler { + + private final Channel clientChannel; + + UpstreamResponseHandler(final Channel clientChannel) { + this.clientChannel = clientChannel; + } + + @Override + protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpResponse response) { + if (clientChannel.isActive()) { + clientChannel.writeAndFlush(response.retain()); + } + ctx.close(); + } + + @Override + public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { + ctx.close(); + closeOnFlush(clientChannel); + } + } +} diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerConstants.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerConstants.java index 74289110208..a276a75704e 100644 --- a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerConstants.java +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerConstants.java @@ -26,6 +26,8 @@ public final class SocketServerConstants { public static final int PORT = 45943; + public static final int PROXY_PORT = 45944; + public static final String GREMLIN_SINGLE_VERTEX = "server_single_vertex"; public static final String GREMLIN_CLOSE_CONNECTION = "server_close_connection"; public static final String GREMLIN_VERTEX_THEN_CLOSE = "server_vertex_then_close"; diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java index 5f6d958cc0c..becdc3d6381 100644 --- a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java @@ -28,7 +28,11 @@ public class SocketServerRunner { public static void main(final String[] args) throws InterruptedException { final SimpleTestServer server = new SimpleTestServer(SocketServerConstants.PORT); final Channel channel = server.start(new TestHttpServerInitializer()); - while (channel.isOpen()) { + + final RecordingProxyServer proxyServer = new RecordingProxyServer(SocketServerConstants.PROXY_PORT); + final Channel proxyChannel = proxyServer.start(); + + while (channel.isOpen() || proxyChannel.isOpen()) { Thread.sleep(1000); } } From 1c26b1081324cf8d7c71c0f74710ffc730854888 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Tue, 7 Jul 2026 15:14:44 -0700 Subject: [PATCH 2/4] Use LittleProxy for testing --- gremlin-tools/gremlin-socket-server/pom.xml | 21 ++ .../socket/server/RecordingProxyServer.java | 336 +++++------------- .../socket/server/SocketServerRunner.java | 4 +- 3 files changed, 103 insertions(+), 258 deletions(-) diff --git a/gremlin-tools/gremlin-socket-server/pom.xml b/gremlin-tools/gremlin-socket-server/pom.xml index 5d25aae750d..996eef40a75 100644 --- a/gremlin-tools/gremlin-socket-server/pom.xml +++ b/gremlin-tools/gremlin-socket-server/pom.xml @@ -27,6 +27,27 @@ netty-all ${netty.version} + + xyz.rogfam + littleproxy + 2.0.22 + + + + io.netty + netty-all + + + + com.barchart.udt + barchart-udt-bundle + + + org.slf4j diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java index 04f33429b42..5c64b524ca4 100644 --- a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java @@ -18,34 +18,18 @@ */ package org.apache.tinkerpop.gremlin.socket.server; -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.util.ReferenceCountUtil; +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.littleshoot.proxy.HttpFiltersSourceAdapter; +import org.littleshoot.proxy.HttpProxyServer; +import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -56,208 +40,131 @@ import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /** - * A self-contained Netty HTTP proxy used for cross-GLV proxy testing. It records the + * A proxy used for cross-GLV proxy testing, backed by the LittleProxy library. It records the * {@code host:port} target of every request it proxies and exposes a small control API so tests can * prove that traffic actually transited the proxy. *

- * Three kinds of requests are supported: + * Two categories of requests are handled by the installed {@code HttpFilters}: *

    - *
  • HTTP CONNECT tunneling (used by Java/Netty and JS/undici clients): the target is - * recorded, a 200 "Connection Established" is returned and the pipeline is reconfigured to relay - * raw bytes bidirectionally between the client and a freshly opened upstream connection.
  • - *
  • Absolute-URI forward requests (used by Go/Python/.NET for http targets): the target - * authority is recorded and the request is forwarded to that target over a new upstream HTTP - * connection with the response relayed back to the client.
  • - *
  • Origin-form control requests: {@code GET /__recorded} returns a JSON array of the - * recorded targets, {@code POST /__reset} clears the recorded list. Any other control path - * returns 404.
  • + *
  • Proxied requests (HTTP CONNECT tunnels used by Java/Netty and JS/undici clients, plus + * absolute-URI forward requests used by Go/Python/.NET for http targets): the target authority is + * recorded and LittleProxy is allowed to proxy the request normally.
  • + *
  • Origin-form control requests served on the same proxy port: {@code GET /__recorded} + * returns a JSON array of the recorded targets, {@code POST /__reset} clears the recorded list. Any + * other control path returns 404. These are short-circuited by returning a response directly from + * the filter so they never leave the proxy.
  • *
*/ public class RecordingProxyServer { private final int port; private final List recordedTargets = new CopyOnWriteArrayList<>(); - private EventLoopGroup bossGroup; - private EventLoopGroup workerGroup; + private HttpProxyServer proxyServer; public RecordingProxyServer(final int port) { this.port = port; } - public Channel start() throws InterruptedException { - bossGroup = new NioEventLoopGroup(1); - workerGroup = new NioEventLoopGroup(); - final ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .childHandler(new ChannelInitializer() { + public void start() { + proxyServer = DefaultHttpProxyServer.bootstrap() + .withPort(port) + .withFiltersSource(new HttpFiltersSourceAdapter() { @Override - protected void initChannel(final SocketChannel ch) { - ch.pipeline().addLast("codec", new HttpServerCodec()); - ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); - ch.pipeline().addLast("handler", new ProxyFrontendHandler(recordedTargets)); + public org.littleshoot.proxy.HttpFilters filterRequest(final HttpRequest originalRequest) { + return new RecordingFilters(originalRequest, recordedTargets); } - }); - return b.bind(port).sync().channel(); + }) + .start(); } public void stop() { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); + if (proxyServer != null) { + proxyServer.stop(); + } } /** - * Handles the client-facing side of the proxy: CONNECT tunneling, absolute-URI forwarding and - * the origin-form control API. + * Records proxied targets and short-circuits the origin-form control API. */ - private static class ProxyFrontendHandler extends SimpleChannelInboundHandler { + private static class RecordingFilters extends HttpFiltersAdapter { private final List recordedTargets; - ProxyFrontendHandler(final List recordedTargets) { + RecordingFilters(final HttpRequest originalRequest, final List recordedTargets) { + super(originalRequest); this.recordedTargets = recordedTargets; } @Override - protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) { - final String uri = request.uri(); - if (HttpMethod.CONNECT.equals(request.method())) { - handleConnect(ctx, uri); - } else if (uri.startsWith("/")) { - // origin-form -> control API - handleControl(ctx, request); - } else { - // absolute-form -> forward to the target origin - handleForward(ctx, request); + public io.netty.handler.codec.http.HttpResponse clientToProxyRequest(final HttpObject httpObject) { + if (!(httpObject instanceof HttpRequest)) { + return null; } - } - // (a) CONNECT tunneling: record host:port, reply 200, then relay raw bytes. - private void handleConnect(final ChannelHandlerContext ctx, final String target) { - recordedTargets.add(target); - - final int colon = target.lastIndexOf(':'); - final String host = target.substring(0, colon); - final int targetPort = Integer.parseInt(target.substring(colon + 1)); - - final Channel clientChannel = ctx.channel(); - // Pause reads on the client until the tunnel is fully wired up. - clientChannel.config().setAutoRead(false); + final HttpRequest request = (HttpRequest) httpObject; + final String uri = request.uri(); - final Bootstrap b = new Bootstrap(); - b.group(clientChannel.eventLoop()) - .channel(NioSocketChannel.class) - .option(ChannelOption.AUTO_READ, false) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(final Channel ch) { - ch.pipeline().addLast(new RelayHandler(clientChannel)); - } - }); + if (uri.startsWith("/")) { + // origin-form -> control API, short-circuit with a direct response. + return handleControl(request); + } - final ChannelFuture connectFuture = b.connect(host, targetPort); - final Channel upstreamChannel = connectFuture.channel(); - connectFuture.addListener((ChannelFutureListener) future -> { - if (!future.isSuccess()) { - sendControlResponse(clientChannel, HttpResponseStatus.BAD_GATEWAY, "", "text/plain"); - closeOnFlush(clientChannel); - return; - } - final FullHttpResponse established = new DefaultFullHttpResponse( - HTTP_1_1, new HttpResponseStatus(200, "Connection Established")); - established.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); - clientChannel.writeAndFlush(established).addListener((ChannelFutureListener) writeFuture -> { - if (!writeFuture.isSuccess()) { - closeOnFlush(upstreamChannel); - closeOnFlush(clientChannel); - return; - } - // Switch to a raw byte relay. Add the relay before removing the HTTP handlers - // (codec last) so any bytes buffered by the codec flow into the relay. - clientChannel.pipeline().addLast(new RelayHandler(upstreamChannel)); - clientChannel.pipeline().remove("handler"); - clientChannel.pipeline().remove("aggregator"); - clientChannel.pipeline().remove("codec"); - clientChannel.config().setAutoRead(true); - upstreamChannel.config().setAutoRead(true); - clientChannel.read(); - upstreamChannel.read(); - }); - }); + // otherwise it is a proxied request: record the authority and let LittleProxy proxy it. + final String authority = extractAuthority(request); + if (authority != null) { + recordedTargets.add(authority); + } + return null; } - // (b) Absolute-URI forward: record host:port, forward request, relay response back. - private void handleForward(final ChannelHandlerContext ctx, final FullHttpRequest request) { - final URI parsed; - try { - parsed = new URI(request.uri()); - } catch (Exception e) { - sendControlResponse(ctx.channel(), HttpResponseStatus.BAD_REQUEST, "", "text/plain"); - return; + /** + * Extracts the {@code host:port} authority from a proxied request. For CONNECT the uri is + * already {@code host:port}; for an absolute-URI request the uri is parsed with + * {@link URI}, falling back to the Host header when the uri cannot be parsed. + */ + private String extractAuthority(final HttpRequest request) { + final String uri = request.uri(); + if (HttpMethod.CONNECT.equals(request.method())) { + return uri; } - final String host = parsed.getHost(); - final int targetPort = parsed.getPort(); - - recordedTargets.add(host + ":" + targetPort); - - // Build an origin-form request for the upstream. Copy synchronously since the inbound - // request is released once channelRead0 returns. - String pathAndQuery = parsed.getRawPath(); - if (pathAndQuery == null || pathAndQuery.isEmpty()) { - pathAndQuery = "/"; - } - if (parsed.getRawQuery() != null) { - pathAndQuery = pathAndQuery + "?" + parsed.getRawQuery(); + try { + final URI parsed = new URI(uri); + final String host = parsed.getHost(); + if (host != null) { + final int parsedPort = parsed.getPort(); + return host + ":" + (parsedPort == -1 ? 80 : parsedPort); + } + } catch (Exception ignored) { + // fall through to the Host header fallback below } - final FullHttpRequest forwardRequest = request.copy(); - forwardRequest.setUri(pathAndQuery); - forwardRequest.headers().set(HttpHeaderNames.HOST, host + ":" + targetPort); - - final Channel clientChannel = ctx.channel(); - final String upstreamHost = host; - final int upstreamPort = targetPort; - - final Bootstrap b = new Bootstrap(); - b.group(clientChannel.eventLoop()) - .channel(NioSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(final Channel ch) { - ch.pipeline().addLast(new HttpClientCodec()); - ch.pipeline().addLast(new HttpObjectAggregator(65536)); - ch.pipeline().addLast(new UpstreamResponseHandler(clientChannel)); - } - }); - - b.connect(upstreamHost, upstreamPort).addListener((ChannelFutureListener) future -> { - if (!future.isSuccess()) { - forwardRequest.release(); - sendControlResponse(clientChannel, HttpResponseStatus.BAD_GATEWAY, "", "text/plain"); - return; - } - future.channel().writeAndFlush(forwardRequest); - }); + final String hostHeader = request.headers().get(HttpHeaderNames.HOST); + if (hostHeader != null && !hostHeader.isEmpty()) { + return hostHeader.contains(":") ? hostHeader : hostHeader + ":80"; + } + return null; } - // (c) Origin-form control API. - private void handleControl(final ChannelHandlerContext ctx, final FullHttpRequest request) { + private FullHttpResponse handleControl(final HttpRequest request) { final String uri = request.uri(); if (HttpMethod.GET.equals(request.method()) && "/__recorded".equals(uri)) { - sendControlResponse(ctx.channel(), HttpResponseStatus.OK, toJsonArray(recordedTargets), - "application/json"); + return buildResponse(HttpResponseStatus.OK, toJsonArray(recordedTargets), "application/json"); } else if (HttpMethod.POST.equals(request.method()) && "/__reset".equals(uri)) { recordedTargets.clear(); - sendControlResponse(ctx.channel(), HttpResponseStatus.OK, "", "text/plain"); + return buildResponse(HttpResponseStatus.OK, "", "text/plain"); } else { - sendControlResponse(ctx.channel(), HttpResponseStatus.NOT_FOUND, "", "text/plain"); + return buildResponse(HttpResponseStatus.NOT_FOUND, "", "text/plain"); } } - @Override - public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { - closeOnFlush(ctx.channel()); + private static FullHttpResponse buildResponse(final HttpResponseStatus status, final String body, + final String contentType) { + final FullHttpResponse response = new DefaultFullHttpResponse( + HTTP_1_1, status, Unpooled.copiedBuffer(body, StandardCharsets.UTF_8)); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8"); + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); + return response; } /** @@ -280,87 +187,4 @@ private static String escapeJson(final String value) { return value.replace("\\", "\\\\").replace("\"", "\\\""); } } - - /** - * Writes a simple full HTTP response with the given body and content type. - */ - private static void sendControlResponse(final Channel channel, final HttpResponseStatus status, - final String body, final String contentType) { - final ByteBuf content = Unpooled.copiedBuffer(body, StandardCharsets.UTF_8); - final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, content); - response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8"); - HttpUtil.setContentLength(response, content.readableBytes()); - channel.writeAndFlush(response); - } - - private static void closeOnFlush(final Channel channel) { - if (channel != null && channel.isActive()) { - channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); - } - } - - /** - * Relays raw bytes to a peer channel and mirrors close/error events so both ends of a tunnel are - * torn down together. - */ - private static class RelayHandler extends ChannelInboundHandlerAdapter { - - private final Channel relayChannel; - - RelayHandler(final Channel relayChannel) { - this.relayChannel = relayChannel; - } - - @Override - public void channelRead(final ChannelHandlerContext ctx, final Object msg) { - if (relayChannel.isActive()) { - relayChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { - if (future.isSuccess()) { - ctx.channel().read(); - } else { - future.channel().close(); - } - }); - } else { - ReferenceCountUtil.release(msg); - } - } - - @Override - public void channelInactive(final ChannelHandlerContext ctx) { - closeOnFlush(relayChannel); - } - - @Override - public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { - closeOnFlush(ctx.channel()); - } - } - - /** - * Relays a forwarded HTTP response from the upstream origin back to the client, then closes the - * upstream connection. - */ - private static class UpstreamResponseHandler extends SimpleChannelInboundHandler { - - private final Channel clientChannel; - - UpstreamResponseHandler(final Channel clientChannel) { - this.clientChannel = clientChannel; - } - - @Override - protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpResponse response) { - if (clientChannel.isActive()) { - clientChannel.writeAndFlush(response.retain()); - } - ctx.close(); - } - - @Override - public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { - ctx.close(); - closeOnFlush(clientChannel); - } - } } diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java index becdc3d6381..15609808c4c 100644 --- a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/SocketServerRunner.java @@ -30,9 +30,9 @@ public static void main(final String[] args) throws InterruptedException { final Channel channel = server.start(new TestHttpServerInitializer()); final RecordingProxyServer proxyServer = new RecordingProxyServer(SocketServerConstants.PROXY_PORT); - final Channel proxyChannel = proxyServer.start(); + proxyServer.start(); - while (channel.isOpen() || proxyChannel.isOpen()) { + while (channel.isOpen()) { Thread.sleep(1000); } } From 480a52a4a25b7dbde317245d2a08000f574847f0 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Wed, 8 Jul 2026 13:33:02 -0700 Subject: [PATCH 3/4] Fix proxy server --- .../tinkerpop/gremlin/socket/server/RecordingProxyServer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java index 5c64b524ca4..78f1899ab62 100644 --- a/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java +++ b/gremlin-tools/gremlin-socket-server/src/main/java/org/apache/tinkerpop/gremlin/socket/server/RecordingProxyServer.java @@ -31,6 +31,7 @@ import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; +import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -67,7 +68,7 @@ public RecordingProxyServer(final int port) { public void start() { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(port) + .withAddress(new InetSocketAddress(port)) .withFiltersSource(new HttpFiltersSourceAdapter() { @Override public org.littleshoot.proxy.HttpFilters filterRequest(final HttpRequest originalRequest) { From d3eeee090e26e3226f5b32aed2cbc772d26bca92 Mon Sep 17 00:00:00 2001 From: Guian Gumpac Date: Fri, 10 Jul 2026 13:00:34 -0700 Subject: [PATCH 4/4] Replaced dead dependency with an active one --- .../Gherkin/Gremlin.cs | 104 +++++++++++++++++- gremlin-tools/gremlin-socket-server/pom.xml | 18 ++- 2 files changed, 109 insertions(+), 13 deletions(-) diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 7120c36fb8a..facc134fda9 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -2749,6 +2749,37 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V().Has(T.Label, "person")}}, {"g_V_hasXlabel_eqXpersonXX", new List, ITraversal>> {(g,p) =>g.V().Has(T.Label, P.Eq("person"))}}, {"g_V_hasXname_nullX", new List, ITraversal>> {(g,p) =>g.V().Has("name", (object) null)}}, + {"g_V_hasXname_constantXmarkoXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.Constant("marko"))}}, + {"g_V_hasXname_VXvid1X_outXknowsX_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.V(p["vid1"]).Out("knows").Values("name").Order())}}, + {"g_V_hasXname_valuesXnonexistentXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.Values("nonexistent"))}}, + {"g_V_hasXname_notXidentityXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.Not(__.Identity()))}}, + {"g_V_hasXage_gtXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Gt(__.Constant(29)))}}, + {"g_V_hasXage_lteXconstantX27XXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Lte(__.Constant(27)))}}, + {"g_V_hasXage_neqXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Neq(__.Constant(32)))}}, + {"g_V_hasXname_eqXconstantXmarkoXXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Eq(__.Constant("marko")))}}, + {"g_V_hasXage_ltXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Lt(__.Constant(29)))}}, + {"g_V_hasXage_gteXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Gte(__.Constant(32)))}}, + {"g_V_hasXage_eqXvaluesXnonexistentXXX", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Eq(__.Values("nonexistent")))}}, + {"g_V_hasXlabel_constantXpersonXX", new List, ITraversal>> {(g,p) =>g.V().Has(T.Label, __.Constant("person"))}}, + {"g_V_hasXperson_name_constantXmarkoXX_age", new List, ITraversal>> {(g,p) =>g.V().Has("person", "name", __.Constant("marko")).Values("age")}}, + {"g_V_hasXname_withinXunionXVXvid1X_outXknowsX_valuesXnameX_constantXpeterXX_foldXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.Union(__.V(p["vid1"]).Out("knows").Values("name"), __.Constant("peter")).Fold()))}}, + {"g_V_hasXname_withinXvaluesXnonexistentX_constantXmarkoXXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.Values("nonexistent"), __.Constant("marko")))}}, + {"g_V_hasXname_withinXvaluesXnonexistentX_valuesXnonexistent2XXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.Values("nonexistent"), __.Values("nonexistent2")))}}, + {"g_V_hasXname_withoutXconstantXmarkoX_constantXvadasX_constantXpeterXXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Without(__.Constant("marko"), __.Constant("vadas"), __.Constant("peter")))}}, + {"g_V_hasXname_withinXunionXVXvid1X_outXknowsX_valuesXnameX_VXvid3X_outXcreatedX_valuesXnameXX_foldXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.Union(__.V(p["vid1"]).Out("knows").Values("name"), __.V(p["vid3"]).Out("created").Values("name")).Fold()))}}, + {"g_V_hasLabelXsoftwareX_hasXname_withoutXunionXVXvid1X_outXcreatedX_valuesXnameX_VXvid4X_outXcreatedX_valuesXnameXX_foldXX", new List, ITraversal>> {(g,p) =>g.V().HasLabel("software").Has("name", P.Without(__.Union(__.V(p["vid1"]).Out("created").Values("name"), __.V(p["vid4"]).Out("created").Values("name")).Fold()))}}, + {"g_V_hasLabelXpersonX_valuesXageX_isXwithinXunionXVXvid1X_valuesXageX_V_hasXname_lopX_inXcreatedX_valuesXageXX_foldXX", new List, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values("age").Is(P.Within(__.Union(__.V(p["vid1"]).Values("age"), __.V().Has("name", "lop").In("created").Values("age")).Fold()))}}, + {"g_VXvid1X_outEXknowsX_filterXinV_hasXname_withinXunionXV_hasXname_lopX_inXcreatedX_valuesXnameX_V_hasXname_rippleX_inXcreatedX_valuesXnameXX_foldXXXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("knows").Filter(__.InV().Has("name", P.Within(__.Union(__.V().Has("name", "lop").In("created").Values("name"), __.V().Has("name", "ripple").In("created").Values("name")).Fold())))}}, + {"g_V_hasLabelXconstantXpersonXX", new List, ITraversal>> {(g,p) =>g.V().HasLabel(__.Constant("person"))}}, + {"g_V_hasIdXeqXVXvid1X_idXX", new List, ITraversal>> {(g,p) =>g.V().HasId(P.Eq(__.V(p["vid1"]).Id()))}}, + {"g_V_hasIdXVXvid1X_idXX", new List, ITraversal>> {(g,p) =>g.V().HasId(__.V(p["vid1"]).Id())}}, + {"g_V_propertiesXageX_hasKeyXconstantXageXX", new List, ITraversal>> {(g,p) =>g.V().Properties("age").HasKey(__.Constant("age"))}}, + {"g_V_propertiesXageX_hasKeyXeqXconstantXageXXX", new List, ITraversal>> {(g,p) =>g.V().Properties("age").HasKey(P.Eq(__.Constant("age")))}}, + {"g_V_propertiesXageX_hasValueXconstantX29XX", new List, ITraversal>> {(g,p) =>g.V().Properties("age").HasValue(__.Constant(29))}}, + {"g_V_propertiesXageX_hasValueXeqXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Properties("age").HasValue(P.Eq(__.Constant(29)))}}, + {"g_VXvid1X_asXaX_V_hasXage_gtXselectXaX_valuesXageXXX_valuesXnameX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").V().Has("age", P.Gt(__.Select("a").Values("age"))).Values("name")}}, + {"g_withSackX29X_V_hasXage_gtXsackXX_valuesXnameX", new List, ITraversal>> {(g,p) =>g.WithSack(29).V().Has("age", P.Gt(__.Sack())).Values("name")}}, + {"g_V_hasXname_markoX_outEXknowsX_hasXweight_gteXconstantX05XXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", "marko").OutE("knows").Has("weight", P.Gte(__.Constant(0.5)))}}, {"g_V_hasIdXemptyX_count", new List, ITraversal>> {(g,p) =>g.V().HasId(p["xx1"]).Count()}}, {"g_V_hasIdXwithinXemptyXX_count", new List, ITraversal>> {(g,p) =>g.V().HasId(P.Within(new List { })).Count()}}, {"g_V_hasIdXwithoutXemptyXX_count", new List, ITraversal>> {(g,p) =>g.V().HasId(P.Without(new List { })).Count()}}, @@ -2808,6 +2839,28 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Gte(p["xx1"])).Is(P.Lt(p["xx2"]))}}, {"g_V_whereXinXcreatedX_count_isX1XX_valuesXnameX", new List, ITraversal>> {(g,p) =>g.V().Where(__.In("created").Count().Is(1)).Values("name")}}, {"g_V_whereXinXcreatedX_count_isXgte_2XX_valuesXnameX", new List, ITraversal>> {(g,p) =>g.V().Where(__.In("created").Count().Is(P.Gte(2))).Values("name")}}, + {"g_V_valuesXageX_isXconstantX29XX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(__.Constant(29))}}, + {"g_V_valuesXnameX_isXconstantXmarkoXX", new List, ITraversal>> {(g,p) =>g.V().Values("name").Is(__.Constant("marko"))}}, + {"g_V_valuesXageX_isXgtXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Gt(__.Constant(29)))}}, + {"g_V_valuesXageX_isXltXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Lt(__.Constant(32)))}}, + {"g_V_valuesXageX_isXneqXconstantX35XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Neq(__.Constant(35)))}}, + {"g_V_valuesXageX_isXwithinXVXvid1X_outXknowsX_valuesXageXXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Within(__.V(p["vid1"]).Out("knows").Values("age").Fold()))}}, + {"g_V_valuesXageX_isXconstantX99999XX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(__.Constant(99999))}}, + {"g_V_hasLabelXpersonX_valuesXageX_chooseXgtXconstantX29XX_olderX", new List, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values("age").Choose(__.Is(P.Gt(__.Constant(29))), __.Constant("older than marko"), __.Constant("not older"))}}, + {"g_V_hasLabelXpersonX_valuesXageX_chooseXgteXmeanAgeX_aboveX", new List, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values("age").Choose(__.Is(P.Gte(__.V().HasLabel("person").Values("age").Mean())), __.Constant("above average"), __.Constant("below average"))}}, + {"g_VXvid1X_outXknowsX_valuesXageX_fold_allXgteXconstantX27XXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out("knows").Values("age").Fold().All(P.Gte(__.Constant(27)))}}, + {"g_VXvid1X_outXknowsX_valuesXageX_fold_allXgtXconstantX27XXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out("knows").Values("age").Fold().All(P.Gt(__.Constant(27)))}}, + {"g_VXvid1X_outXknowsX_valuesXageX_fold_anyXeqXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out("knows").Values("age").Fold().Any(P.Eq(__.Constant(32)))}}, + {"g_VXvid1X_outXknowsX_valuesXageX_fold_noneXeqXconstantX35XXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out("knows").Values("age").Fold().None(P.Eq(__.Constant(35)))}}, + {"g_VXvid1X_outXknowsX_valuesXageX_fold_noneXeqXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out("knows").Values("age").Fold().None(P.Eq(__.Constant(32)))}}, + {"g_injectXnullX_isXeqXV9999_valuesXnameXXX", new List, ITraversal>> {(g,p) =>g.Inject(null).Is(P.Eq(__.V(9999).Values("name")))}}, + {"g_injectXmarkoX_isXV9999_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.Inject("marko").Is(__.V(9999).Values("name"))}}, + {"g_injectXmarkoX_chooseXeqXV9999_valuesXnameXX_matched_unmatchedX", new List, ITraversal>> {(g,p) =>g.Inject("marko").Choose(__.Is(P.Eq(__.V(9999).Values("name"))), __.Constant("matched"), __.Constant("unmatched"))}}, + {"g_injectXlistX_noneXeqXV9999_valuesXnameXXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { "marko", "josh" }).None(P.Eq(__.V(9999).Values("name")))}}, + {"g_V_hasXname_eqXV9999_valuesXnameXXX", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Eq(__.V(9999).Values("name")))}}, + {"g_V_valuesXageX_isXwithoutXconstantX29X_constantX32XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Without(__.Constant(29), __.Constant(32)))}}, + {"g_VXvid1X_asXaX_V_valuesXageX_isXgtXselectXaX_valuesXageXXX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").V().Values("age").Is(P.Gt(__.Select("a").Values("age")))}}, + {"g_withSackX30X_V_valuesXageX_isXgtXsackXX", new List, ITraversal>> {(g,p) =>g.WithSack(30).V().Values("age").Is(P.Gt(__.Sack()))}}, {"g_V_valuesXageX_noneXgtX32XX", new List, ITraversal>> {(g,p) =>g.V().Values("age").None(P.Gt(32))}}, {"g_V_valuesXageX_whereXisXP_gtX33XXX_fold_noneXlteX33XX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(__.Is(P.Gt(33))).Fold().None(P.Lte(33))}}, {"g_V_valuesXageX_order_byXdescX_fold_noneXltX10XX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Order().By(Order.Desc).Fold().None(P.Lt(10))}}, @@ -2980,6 +3033,16 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V().Where(__.Values("age").Is(P.Gt(30)))}}, {"g_V_whereXlabel_isXsoftwareXX", new List, ITraversal>> {(g,p) =>g.V().Where(__.Label().Is("software"))}}, {"g_V_whereXlabel_isXpersonXX", new List, ITraversal>> {(g,p) =>g.V().Where(__.Label().Is("person"))}}, + {"g_V_valuesXageX_whereXgtXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(P.Gt(__.Constant(29)))}}, + {"g_V_valuesXageX_whereXltXconstantX32XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(P.Lt(__.Constant(32)))}}, + {"g_V_valuesXageX_whereXeqXconstantX29XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(P.Eq(__.Constant(29)))}}, + {"g_V_valuesXageX_whereXwithinXVXvid1X_outXknowsX_valuesXageXXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(P.Within(__.V(p["vid1"]).Out("knows").Values("age").Fold()))}}, + {"g_V_valuesXnameX_whereXneqXconstantXmarkoXXX", new List, ITraversal>> {(g,p) =>g.V().Values("name").Where(P.Neq(__.Constant("marko")))}}, + {"g_V_valuesXageX_whereXeqXconstantX99999XXX", new List, ITraversal>> {(g,p) =>g.V().Values("age").Where(P.Eq(__.Constant(99999)))}}, + {"g_V_whereXgtXVXvid1X_valuesXageXXX_byXageX_valuesXnameX", new List, ITraversal>> {(g,p) =>g.V().Where(P.Gt(__.V(p["vid1"]).Values("age"))).By("age").Values("name")}}, + {"g_VX1X_asXaX_V_valuesXageX_whereXgteXconstantX0XX_andXneqXaXXX_byX_byXageX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").V().HasLabel("person").Values("age").Where(P.Gte(__.Constant(0)).And(P.Neq("a"))).By().By("age")}}, + {"g_VX1X_asXaX_V_valuesXageX_whereXneqXaX_andXgtXVXvid1X_valuesXageXXXX_byX_byXageX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").V().HasLabel("person").Values("age").Where(P.Neq("a").And(P.Gt(__.V(p["vid1"]).Values("age")))).By().By("age")}}, + {"g_VX1X_asXaX_V_valuesXageX_whereXeqXVXvid1X_valuesXageXX_orXeqXaXXX_byX_byXageX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").V().HasLabel("person").Values("age").Where(P.Eq(__.V(p["vid1"]).Values("age")).Or(P.Eq("a"))).By().By("age")}}, {"g_withStrategiesXAdjacentToIncidentStrategyX_V", new List, ITraversal>> {(g,p) =>g.WithStrategies(new AdjacentToIncidentStrategy()).V()}}, {"g_withoutStrategiesXAdjacentToIncidentStrategyX_V", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(AdjacentToIncidentStrategy)).V()}}, {"g_withStrategiesXAdjacentToIncidentStrategyX_V_out_count", new List, ITraversal>> {(g,p) =>g.WithStrategies(new AdjacentToIncidentStrategy()).V().Out().Count()}}, @@ -3101,6 +3164,23 @@ private static IDictionary, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(SeedStrategy)).V()}}, {"g_withStrategiesXStandardVerificationStrategyX_V", new List, ITraversal>> {(g,p) =>g.WithStrategies(new StandardVerificationStrategy()).V()}}, {"g_withoutStrategiesXStandardVerificationStrategyX_V", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(StandardVerificationStrategy)).V()}}, + {"g_V_hasXname_addVXxX_valuesXnameXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.AddV((string) "x").Values("name"))}}, + {"g_V_hasXname_V_drop_constantXxXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.V().Drop().Constant("x"))}}, + {"g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.V().Map(__.AddV((string) "x")).Values("name"))}}, + {"g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Eq(__.AddV((string) "x").Values("name")))}}, + {"g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.AddV((string) "x").Values("name")))}}, + {"g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Gt(__.AddV((string) "x").Values("age")))}}, + {"g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(__.AddV((string) "x").Values("age"))}}, + {"g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Values("age").Is(P.Gt(__.AddV((string) "x").Values("age")))}}, + {"g_V_VXaddVXxX_idX_rejected", new List, ITraversal>> {(g,p) =>g.V().V(__.AddV((string) "x").Id())}}, + {"g_V_EXaddVXxX_idX_rejected", new List, ITraversal>> {(g,p) =>g.V().E(__.AddV((string) "x").Id())}}, + {"g_VXaddVXxX_idX_rejected", new List, ITraversal>> {(g,p) =>g.V(__.AddV((string) "x").Id())}}, + {"g_EXaddVXxX_idX_rejected", new List, ITraversal>> {(g,p) =>g.E(__.AddV((string) "x").Id())}}, + {"g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Property((ITraversal) __.V().Map(__.Drop()).Project("x").By("name"))}}, + {"g_V_propertyXaddVXtempX_projectXkX_byXnameXX_rejected", new List, ITraversal>> {(g,p) =>g.V().Property((ITraversal) __.AddV((string) "temp").Project("k").By("name"))}}, + {"g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification", new List, ITraversal>> {(g,p) =>g.V().Has("name", __.V(p["vid1"]).Values("name"))}}, + {"g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification", new List, ITraversal>> {(g,p) =>g.V().Has("age", P.Gt(__.V(p["vid1"]).Values("age")))}}, + {"g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).V(__.Out("knows").Id()).Values("name")}}, {"g_withStrategiesXSubgraphStrategyXsubgraphAXX_V", new List, ITraversal>> {(g,p) =>g.WithStrategies(new SubgraphStrategy(vertices: __.Has("name", P.Within("josh", "lop", "ripple")))).V()}}, {"g_withStrategiesXSubgraphStrategyXsubgraphAXX_E", new List, ITraversal>> {(g,p) =>g.WithStrategies(new SubgraphStrategy(vertices: __.Has("name", P.Within("josh", "lop", "ripple")))).E()}}, {"g_withStrategiesXSubgraphStrategyXsubgraphAXX_VX4X_outE", new List, ITraversal>> {(g,p) =>g.WithStrategies(new SubgraphStrategy(vertices: __.Has("name", P.Within("josh", "lop", "ripple")))).V(p["vid4"]).OutE()}}, @@ -3211,9 +3291,9 @@ private static IDictionary, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single, new Dictionary {{ "name", "foo" }, { "age", 42 }}), (g,p) =>g.V().Has("name", "foo")}}, {"g_V_hasXname_fooX_propertyXname_setXbarX_age_43X", new List, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single, "name", "foo").Property("age", 42), (g,p) =>g.V().Has("name", "foo").Property(new Dictionary {{ "name", CardinalityValue.Set("bar") }, { "age", 43 }}), (g,p) =>g.V().Has("name", "foo"), (g,p) =>g.V().Has("name", "bar"), (g,p) =>g.V().Has("age", 43), (g,p) =>g.V().Has("age", 42)}}, {"g_V_hasXname_fooX_propertyXset_name_bar_age_singleX43XX", new List, ITraversal>> {(g,p) =>g.AddV().Property(Cardinality.Single, "name", "foo").Property("age", 42), (g,p) =>g.V().Has("name", "foo").Property(Cardinality.Set, new Dictionary {{ "name", "bar" }, { "age", CardinalityValue.Single(43) }}), (g,p) =>g.V().Has("name", "foo"), (g,p) =>g.V().Has("name", "bar"), (g,p) =>g.V().Has("age", 43), (g,p) =>g.V().Has("age", 42)}}, - {"g_addV_propertyXnullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property(null), (g,p) =>g.V().HasLabel("person").Values()}}, + {"g_addV_propertyXnullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property((IDictionary) null), (g,p) =>g.V().HasLabel("person").Values()}}, {"g_addV_propertyXemptyX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property(new Dictionary {}), (g,p) =>g.V().HasLabel("person").Values()}}, - {"g_addV_propertyXset_nullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "foo").Property(Cardinality.Set, null), (g,p) =>g.V().HasLabel("foo").Values()}}, + {"g_addV_propertyXset_nullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "foo").Property(Cardinality.Set, (IDictionary) null), (g,p) =>g.V().HasLabel("foo").Values()}}, {"g_addV_propertyXset_emptyX", new List, ITraversal>> {(g,p) =>g.AddV((string) "foo").Property(Cardinality.Set, new Dictionary {}), (g,p) =>g.V().HasLabel("person").Values()}}, {"g_addVXpersonX_propertyXname_joshX_propertyXage_nullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "josh").Property("age", null), (g,p) =>g.V().Has("person", "age", (object) null)}}, {"g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("friendWeight", null, "acl", null), (g,p) =>g.V().Has("person", "name", "marko").Has("friendWeight", (object) null), (g,p) =>g.V().Has("person", "name", "marko").Properties("friendWeight").Has("acl", (object) null), (g,p) =>g.V().Has("person", "name", "marko").Properties("friendWeight").Count()}}, @@ -3454,6 +3534,10 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("knows").BothV()}}, {"g_VX1X_outEXknowsX_bothV_name", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("knows").BothV().Values("name")}}, {"g_V_toEXout_knowsvarX_valuesXweightX", new List, ITraversal>> {(g,p) =>g.V().ToE(Direction.Out, new GValue("xx1", (string) p["xx1"])).Values("weight")}}, + {"g_VXvid1X_EXoutE_idX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).E(__.OutE().Id())}}, + {"g_VXvid1X_outEXknowsX_hasXinV_name_vadasX_id_EXidentityX", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("knows").Filter(__.InV().Has("name", "vadas")).Id().E(__.Identity())}}, + {"g_injectX9999X_EXidentityX", new List, ITraversal>> {(g,p) =>g.Inject(9999).E(__.Identity())}}, + {"g_EXVXvid1X_outE_idX", new List, ITraversal>> {(g,p) =>g.E(__.V(p["vid1"]).OutE().Id())}}, {"g_VX1X_properties_element", new List, ITraversal>> {(g,p) =>g.V(p["vid2"]).Properties().Element().Limit(1)}}, {"g_V_properties_element", new List, ITraversal>> {(g,p) =>g.V().Properties().Element()}}, {"g_V_propertiesXageX_element", new List, ITraversal>> {(g,p) =>g.V().Properties("age").Element()}}, @@ -4118,6 +4202,12 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V().Out("knows").V().Values("name")}}, {"g_V_hasXname_GarciaX_inXsungByX_asXsongX_V_hasXname_Willie_DixonX_inXwrittenByX_whereXeqXsongXX_name", new List, ITraversal>> {(g,p) =>g.V().Has("artist", "name", "Garcia").In("sungBy").As("song").V().Has("artist", "name", "Willie_Dixon").In("writtenBy").Where(P.Eq("song")).Values("name")}}, {"g_V_hasLabelXpersonX_asXpX_VXsoftwareX_addInEXuses_pX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddE((string) "knows").From("marko").To("vadas").Property("weight", 0.5d).AddE((string) "knows").From("marko").To("josh").Property("weight", 1.0d).AddE((string) "created").From("marko").To("lop").Property("weight", 0.4d).AddE((string) "created").From("josh").To("ripple").Property("weight", 1.0d).AddE((string) "created").From("josh").To("lop").Property("weight", 0.4d).AddE((string) "created").From("peter").To("lop").Property("weight", 0.2d), (g,p) =>g.V().HasLabel("person").As("p").V(p["xx1"]).AddE((string) "uses").From("p"), (g,p) =>g.E().HasLabel("uses"), (g,p) =>g.V(p["vid1"]).OutE("uses"), (g,p) =>g.V(p["vid2"]).OutE("uses"), (g,p) =>g.V(p["vid3"]).InE("uses"), (g,p) =>g.V(p["vid4"]).OutE("uses"), (g,p) =>g.V(p["vid5"]).InE("uses"), (g,p) =>g.V(p["vid6"]).OutE("uses")}}, + {"g_VXvid1X_id_VXidentityX_name", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Id().V(__.Identity()).Values("name")}}, + {"g_VXvid1_vid2X_id_VXidentityX_name", new List, ITraversal>> {(g,p) =>g.V(p["vid1"], p["vid2"]).Id().V(__.Identity()).Values("name")}}, + {"g_VXvid1X_id_asXbookmarkX_V_hasXname_joshX_VXselectXbookmarkXX_name", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).Id().As("bookmark").V().Has("name", "josh").V(__.Select("bookmark")).Values("name")}}, + {"g_VXvid1X_VXoutXknowsX_idX_name", new List, ITraversal>> {(g,p) =>g.V(p["vid1"]).V(__.Out("knows").Id()).Values("name")}}, + {"g_injectX9999X_VXidentityX", new List, ITraversal>> {(g,p) =>g.Inject(9999).V(__.Identity())}}, + {"g_VXVXvid1X_idX_name", new List, ITraversal>> {(g,p) =>g.V(__.V(p["vid1"]).Id()).Values("name")}}, {"InjectXnullX_eqXnullX", new List, ITraversal>> {(g,p) =>g.Inject(null).Is(P.Eq(null))}}, {"InjectXnullX_neqXnullX", new List, ITraversal>> {(g,p) =>g.Inject(null).Is(P.Neq(null))}}, {"InjectXnullX_ltXnullX", new List, ITraversal>> {(g,p) =>g.Inject(null).Is(P.Lt(null))}}, @@ -4390,6 +4480,16 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(new List { 1, 3, 100, 300 })}}, {"g_injectX1_3_100_300X_set", new List, ITraversal>> {(g,p) =>g.Inject(new HashSet { 1, 3, 100, 300 })}}, {"g_injectX1_1X_set", new List, ITraversal>> {(g,p) =>g.Inject(new HashSet { 1, 1 })}}, + {"g_VXvid2X_propertyXalias_VXvid1X_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).AddV((string) "person").Property("name", "vadas").Property("age", 27).AddV((string) "software").Property("name", "lop").Property("lang", "java").AddV((string) "person").Property("name", "josh").Property("age", 32).AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java"), (g,p) =>g.V(p["vid2"]).Property("alias", __.V(p["vid1"]).Values("name")), (g,p) =>g.V().Has("alias", "marko")}}, + {"g_VXvid1X_propertyXcreatorCount_VXvid1X_inXcreatedX_countX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddE((string) "knows").From("marko").To(__.V().Has("name", "vadas")).AddE((string) "knows").From("marko").To("josh").AddE((string) "created").From("marko").To("lop").AddE((string) "created").From("josh").To("lop").AddE((string) "created").From("josh").To("ripple").AddE((string) "created").From("peter").To("lop"), (g,p) =>g.V(p["vid1"]).Property("creatorCount", __.V(p["vid1"]).In("created").Count()), (g,p) =>g.V().Has("creatorCount", 3l)}}, + {"g_VXvid1X_propertyXknownCount_outXknowsX_countX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java").AddE((string) "knows").From("marko").To("vadas").AddE((string) "knows").From("marko").To("josh"), (g,p) =>g.V(p["vid1"]).Property("knownCount", __.Out("knows").Count()), (g,p) =>g.V().Has("knownCount", 2l)}}, + {"g_VXvid1X_propertyXcreator_inXcreatedX_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddE((string) "knows").From("marko").To(__.V().Has("name", "vadas")).AddE((string) "knows").From("marko").To("josh").AddE((string) "created").From("marko").To("lop").AddE((string) "created").From("josh").To("lop").AddE((string) "created").From("josh").To("ripple").AddE((string) "created").From("peter").To("lop"), (g,p) =>g.V(p["vid1"]).Property("creator", __.In("created").Values("name")), (g,p) =>g.V().Has("creator")}}, + {"g_VXvid2X_propertyXVXvid1X_projectXfriendCount_softwareCreatedXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddE((string) "knows").From("marko").To("vadas").AddE((string) "knows").From("marko").To("josh").AddE((string) "created").From("marko").To("lop").AddE((string) "created").From("josh").To("lop").AddE((string) "created").From("josh").To("ripple").AddE((string) "created").From("peter").To("lop"), (g,p) =>g.V(p["vid2"]).Property((ITraversal) __.V(p["vid1"]).Project("friendCount", "softwareCreated").By(__.Out("knows").Count()).By(__.Out("created").Values("name"))), (g,p) =>g.V().Has("friendCount", 2l), (g,p) =>g.V().Has("softwareCreated", "lop")}}, + {"g_VXvid2X_propertyXVXvid1X_projectXoriginalName_originalLabelX_byXnameX_byXlabelXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java"), (g,p) =>g.V(p["vid2"]).Property((ITraversal) __.V(p["vid1"]).Project("originalName", "originalLabel").By(__.Values("name")).By(__.Label())), (g,p) =>g.V(p["vid2"]).Has("originalName", "marko"), (g,p) =>g.V(p["vid2"]).Has("originalLabel", "person")}}, + {"g_VXvid1X_propertyXlist_friends_outXknowsX_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java").AddE((string) "knows").From("marko").To("vadas").AddE((string) "knows").From("marko").To("josh"), (g,p) =>g.V(p["vid1"]).Property(Cardinality.List, "friends", __.Out("knows").Values("name")), (g,p) =>g.V(p["vid1"]).Properties("friends")}}, + {"g_VXvid1X_propertyXset_langs_outXcreatedX_valuesXlangXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).AddV((string) "person").Property("name", "vadas").Property("age", 27).AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddE((string) "created").From("josh").To("lop").AddE((string) "created").From("josh").To("ripple"), (g,p) =>g.V(p["vid1"]).Property(Cardinality.Set, "langs", __.Out("created").Values("lang")), (g,p) =>g.V(p["vid1"]).Properties("langs")}}, + {"g_VXvid1X_propertyXsingle_friend_outXknowsX_valuesXnameXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "person").Property("name", "peter").Property("age", 35).AddV((string) "software").Property("name", "ripple").Property("lang", "java").AddE((string) "knows").From("marko").To("vadas").AddE((string) "knows").From("marko").To("josh"), (g,p) =>g.V(p["vid1"]).Property(Cardinality.Single, "friend", __.Out("knows").Values("name"))}}, + {"g_VXvid1X_propertyXVXvid1X_groupX_byXidX_byXvaluesXnameXXX", new List, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29), (g,p) =>g.V(p["vid1"]).Property((ITraversal) __.V(p["vid1"]).Group().By(__.Id()).By(__.Values("name")))}}, {"g_io_readXkryoX", new List, ITraversal>> {(g,p) =>g.Io("data/tinkerpop-modern.kryo").Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, {"g_io_read_withXreader_gryoX", new List, ITraversal>> {(g,p) =>g.Io("data/tinkerpop-modern.kryo").With(IO.Reader, IO.Gryo).Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, {"g_io_readXgraphsonX", new List, ITraversal>> {(g,p) =>g.Io("data/tinkerpop-modern.json").Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, diff --git a/gremlin-tools/gremlin-socket-server/pom.xml b/gremlin-tools/gremlin-socket-server/pom.xml index 996eef40a75..42bb46df1fc 100644 --- a/gremlin-tools/gremlin-socket-server/pom.xml +++ b/gremlin-tools/gremlin-socket-server/pom.xml @@ -28,23 +28,19 @@ ${netty.version} - xyz.rogfam + io.github.littleproxy littleproxy - 2.0.22 + 2.8.0 io.netty - netty-all - - - - com.barchart.udt - barchart-udt-bundle + *