You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by ke...@apache.org on 2024/04/08 17:10:39 UTC
(tinkerpop) branch migrate-integrationtests-http created (now f47084c432)
This is an automated email from the ASF dual-hosted git repository.
kenhuuu pushed a change to branch migrate-integrationtests-http
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
at f47084c432 Split HTTP endpoint into several handlers and update to use RequestMessageV4
This branch includes the following new commits:
new f47084c432 Split HTTP endpoint into several handlers and update to use RequestMessageV4
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(tinkerpop) 01/01: Split HTTP endpoint into several handlers and update to use RequestMessageV4
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kenhuuu pushed a commit to branch migrate-integrationtests-http
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit f47084c432b59a5bbf217921b36ab3ea4b69efdb
Author: Ken Hu <10...@users.noreply.github.com>
AuthorDate: Sun Apr 7 16:19:44 2024 -0700
Split HTTP endpoint into several handlers and update to use RequestMessageV4
---
.../driver/handler/HttpGremlinRequestEncoder.java | 2 +-
.../driver/handler/HttpGremlinResponseDecoder.java | 9 +-
.../gremlin/driver/simple/SimpleHttpClient.java | 6 -
.../apache/tinkerpop/gremlin/server/Context.java | 25 +-
.../tinkerpop/gremlin/server/authz/Authorizer.java | 6 +-
.../gremlin/server/channel/HttpChannelizer.java | 14 +-
.../handler/HttpBasicAuthorizationHandler.java | 27 +-
.../server/handler/HttpGremlinEndpointHandler.java | 554 ++++++++-------------
.../gremlin/server/handler/HttpHandlerUtil.java | 197 +-------
.../server/handler/HttpRequestCheckingHandler.java | 11 +-
...lerUtil.java => HttpRequestMessageDecoder.java} | 215 ++++----
.../tinkerpop/gremlin/server/handler/StateKey.java | 7 +-
.../tinkerpop/gremlin/server/ContextTest.java | 15 +-
.../server/GremlinServerHttpIntegrateTest.java | 518 ++++++++++---------
.../gremlin/server/GremlinServerIntegrateTest.java | 425 ++++++++--------
.../gremlin/server/authz/AllowListAuthorizer.java | 6 +-
.../gremlin/server/authz/AuthorizerTest.java | 6 +-
.../server/handler/HttpHandlerUtilTest.java | 248 ---------
.../handler/HttpRequestMessageDecoderTest.java | 377 ++++++++++++++
.../gremlin/util/message/RequestMessageV4.java | 37 +-
.../gremlin/util/message/ResponseMessage.java | 8 +
.../AbstractGraphBinaryMessageSerializerV1.java | 2 -
.../ser/AbstractGraphSONMessageSerializerV4.java | 2 +-
.../util/ser/GraphBinaryMessageSerializerV4.java | 2 +-
.../gremlin/util/ser/MessageChunkSerializer.java | 48 --
.../gremlin/util/ser/MessageTextSerializerV4.java | 22 +
.../ser/binary/RequestMessageSerializerV4.java | 33 +-
.../gremlin/util/message/RequestMessageV4Test.java | 16 +-
28 files changed, 1303 insertions(+), 1535 deletions(-)
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinRequestEncoder.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinRequestEncoder.java
index 06377e31be..97e8d951e2 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinRequestEncoder.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinRequestEncoder.java
@@ -65,7 +65,7 @@ public final class HttpGremlinRequestEncoder extends MessageToMessageEncoder<Req
protected void encode(final ChannelHandlerContext channelHandlerContext, final RequestMessageV4 requestMessage, final List<Object> objects) throws Exception {
final String mimeType = serializer.mimeTypesSupported()[0];
// only GraphSON3 and GraphBinary recommended for serialization of Bytecode requests
- if (requestMessage.getArg("gremlin") instanceof Bytecode &&
+ if (requestMessage.getField("gremlin") instanceof Bytecode &&
!mimeType.equals(SerTokens.MIME_GRAPHSON_V3) &&
!mimeType.equals(SerTokens.MIME_GRAPHBINARY_V1)) {
throw new ResponseException(ResponseStatusCode.REQUEST_ERROR_SERIALIZATION, String.format(
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinResponseDecoder.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinResponseDecoder.java
index 9b46db27ee..8144ddbbe2 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinResponseDecoder.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/handler/HttpGremlinResponseDecoder.java
@@ -23,7 +23,9 @@ import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
import org.apache.tinkerpop.gremlin.util.MessageSerializer;
import org.apache.tinkerpop.gremlin.util.Tokens;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
@@ -32,6 +34,7 @@ import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
@@ -49,10 +52,12 @@ public final class HttpGremlinResponseDecoder extends MessageToMessageDecoder<Fu
@Override
protected void decode(final ChannelHandlerContext channelHandlerContext, final FullHttpResponse httpResponse, final List<Object> objects) throws Exception {
- if (httpResponse.status() == HttpResponseStatus.OK) {
+ String type = httpResponse.headers().get(HttpHeaderNames.CONTENT_TYPE);
+ if (type != null && type != "application/json") {
objects.add(serializer.deserializeResponse(httpResponse.content()));
} else {
- final JsonNode root = mapper.readTree(new ByteBufInputStream(httpResponse.content()));
+ String response = httpResponse.content().toString(StandardCharsets.UTF_8);
+ final JsonNode root = mapper.readTree(response);
objects.add(ResponseMessage.build(UUID.fromString(root.get(Tokens.REQUEST_ID).asText()))
.code(ResponseStatusCode.SERVER_ERROR)
.statusMessage(root.get(SerTokens.TOKEN_MESSAGE).asText())
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/SimpleHttpClient.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/SimpleHttpClient.java
index fd97fa3d6e..f36b9d51a7 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/SimpleHttpClient.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/SimpleHttpClient.java
@@ -96,8 +96,6 @@ public class SimpleHttpClient extends AbstractClient {
sslCtx = null;
}
-// final WebSocketClientHandler wsHandler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(
-// uri, WebSocketVersion.V13, null, true, EmptyHttpHeaders.INSTANCE, 65536), 10000, false);
final MessageSerializer<GraphBinaryMapper> serializer = new GraphBinaryMessageSerializerV4();
b.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@@ -110,9 +108,6 @@ public class SimpleHttpClient extends AbstractClient {
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(65536),
-// wsHandler,
-// new WebSocketGremlinRequestEncoder(true, serializer),
-// new WebSocketGremlinResponseDecoder(serializer),
new HttpGremlinRequestEncoder(serializer, HandshakeInterceptor.NO_OP, false),
new HttpGremlinResponseDecoder(serializer),
callbackResponseHandler);
@@ -120,7 +115,6 @@ public class SimpleHttpClient extends AbstractClient {
});
channel = b.connect(uri.getHost(), uri.getPort()).sync().channel();
-// wsHandler.handshakeFuture().get(30, TimeUnit.SECONDS);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
index 0cca811a83..26513f2646 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
@@ -28,7 +28,7 @@ import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceFactory;
import org.apache.tinkerpop.gremlin.util.Tokens;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import java.util.List;
import java.util.Map;
@@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class Context {
- private final RequestMessage requestMessage;
+ private final RequestMessageV4 requestMessage;
private final ChannelHandlerContext channelHandlerContext;
private final Settings settings;
private final GraphManager graphManager;
@@ -79,14 +79,14 @@ public class Context {
UNKNOWN
}
- public Context(final RequestMessage requestMessage, final ChannelHandlerContext ctx,
+ public Context(final RequestMessageV4 requestMessage, final ChannelHandlerContext ctx,
final Settings settings, final GraphManager graphManager,
final GremlinExecutor gremlinExecutor, final ScheduledExecutorService scheduledExecutorService) {
this(requestMessage, ctx, settings, graphManager, gremlinExecutor, scheduledExecutorService,
HttpGremlinEndpointHandler.RequestState.CHUNKING_NOT_SUPPORTED);
}
- public Context(final RequestMessage requestMessage, final ChannelHandlerContext ctx,
+ public Context(final RequestMessageV4 requestMessage, final ChannelHandlerContext ctx,
final Settings settings, final GraphManager graphManager,
final GremlinExecutor gremlinExecutor, final ScheduledExecutorService scheduledExecutorService,
final HttpGremlinEndpointHandler.RequestState requestState) {
@@ -98,7 +98,7 @@ public class Context {
this.scheduledExecutorService = scheduledExecutorService;
// order of calls matter as one depends on the next
- this.gremlinArgument = requestMessage.getArgs().get(Tokens.ARGS_GREMLIN);
+ this.gremlinArgument = requestMessage.getGremlin();
this.requestState = requestState;
this.requestContentType = determineRequestContents();
this.requestTimeout = determineTimeout();
@@ -145,7 +145,7 @@ public class Context {
/**
* Gets the current request to Gremlin Server.
*/
- public RequestMessage getRequestMessage() {
+ public RequestMessageV4 getRequestMessage() {
return requestMessage;
}
@@ -200,16 +200,13 @@ public class Context {
private long determineTimeout() {
// timeout override - handle both deprecated and newly named configuration. earlier logic should prevent
// both configurations from being submitted at the same time
- final Map<String, Object> args = requestMessage.getArgs();
- final long seto = args.containsKey(Tokens.ARGS_EVAL_TIMEOUT) ?
- ((Number) args.get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() : settings.getEvaluationTimeout();
-
+ // evaluationTimeout RequestMessage argument was removed starting in 4.0.
// override the timeout if the lifecycle has a value assigned. if the script contains with(timeout)
// options then allow that value to override what's provided on the lifecycle
final Optional<Long> timeoutDefinedInScript = requestContentType == RequestContentType.SCRIPT ?
GremlinScriptChecker.parse(gremlinArgument.toString()).getTimeout() : Optional.empty();
- return timeoutDefinedInScript.orElse(seto);
+ return timeoutDefinedInScript.orElse(settings.getEvaluationTimeout());
}
private String determineMaterializeProperties() {
@@ -222,12 +219,8 @@ public class Context {
: Tokens.MATERIALIZE_PROPERTIES_ALL;
}
- final Map<String, Object> args = requestMessage.getArgs();
// all options except MATERIALIZE_PROPERTIES_TOKENS treated as MATERIALIZE_PROPERTIES_ALL
- return args.containsKey(Tokens.ARGS_MATERIALIZE_PROPERTIES)
- && args.get(Tokens.ARGS_MATERIALIZE_PROPERTIES).equals(Tokens.MATERIALIZE_PROPERTIES_TOKENS)
- ? Tokens.MATERIALIZE_PROPERTIES_TOKENS
- : Tokens.MATERIALIZE_PROPERTIES_ALL;
+ return Tokens.MATERIALIZE_PROPERTIES_ALL;
}
public void handleDetachment(final List<Object> aggregate) {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/authz/Authorizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/authz/Authorizer.java
index 8028bf1f13..2cad3bda0b 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/authz/Authorizer.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/authz/Authorizer.java
@@ -18,7 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.server.authz;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
@@ -55,8 +55,8 @@ public interface Authorizer {
* {@link AuthorizationException} if this is not the case.
*
* @param user {@link AuthenticatedUser} that needs authorization.
- * @param msg {@link RequestMessage} in which the {@link org.apache.tinkerpop.gremlin.util.Tokens}.ARGS_GREMLIN argument can contain an arbitrary succession of script statements.
+ * @param msg {@link RequestMessageV4} in which the {@link org.apache.tinkerpop.gremlin.util.Tokens}.ARGS_GREMLIN argument can contain an arbitrary succession of script statements.
*/
- public void authorize(final AuthenticatedUser user, final RequestMessage msg) throws AuthorizationException;
+ public void authorize(final AuthenticatedUser user, final RequestMessageV4 msg) throws AuthorizationException;
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
index cfcac8b48e..4d631bfdd0 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
@@ -19,6 +19,9 @@
package org.apache.tinkerpop.gremlin.server.channel;
import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.HttpServerKeepAliveHandler;
+import io.netty.handler.codec.http.cors.CorsConfigBuilder;
+import io.netty.handler.codec.http.cors.CorsHandler;
import org.apache.tinkerpop.gremlin.server.AbstractChannelizer;
import org.apache.tinkerpop.gremlin.server.Channelizer;
import org.apache.tinkerpop.gremlin.server.Settings;
@@ -27,6 +30,7 @@ import org.apache.tinkerpop.gremlin.server.handler.AbstractAuthenticationHandler
import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthenticationHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthorizationHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpRequestCheckingHandler;
+import org.apache.tinkerpop.gremlin.server.handler.HttpRequestMessageDecoder;
import org.apache.tinkerpop.gremlin.server.handler.HttpUserAgentHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpGremlinEndpointHandler;
import io.netty.channel.ChannelPipeline;
@@ -49,6 +53,7 @@ public class HttpChannelizer extends AbstractChannelizer {
private HttpGremlinEndpointHandler httpGremlinEndpointHandler;
private HttpRequestCheckingHandler httpRequestCheckingHandler = new HttpRequestCheckingHandler();
+ private HttpRequestMessageDecoder httpRequestMessageDecoder = new HttpRequestMessageDecoder(serializers);
@Override
public void init(final ServerGremlinExecutor serverGremlinExecutor) {
@@ -66,11 +71,14 @@ public class HttpChannelizer extends AbstractChannelizer {
if (logger.isDebugEnabled())
pipeline.addLast(new LoggingHandler("http-io", LogLevel.DEBUG));
+ pipeline.addLast("http-keepalive-handler", new HttpServerKeepAliveHandler());
+ pipeline.addLast("http-cors-handler", new CorsHandler(CorsConfigBuilder.forAnyOrigin().build()));
+
final HttpObjectAggregator aggregator = new HttpObjectAggregator(settings.maxContentLength);
aggregator.setMaxCumulationBufferComponents(settings.maxAccumulationBufferComponents);
pipeline.addLast(PIPELINE_HTTP_AGGREGATOR, aggregator);
-
pipeline.addLast("http-request-checker", httpRequestCheckingHandler);
+ pipeline.addLast("http-user-agent-handler", new HttpUserAgentHandler());
if (authenticator != null) {
// Cannot add the same handler instance multiple times unless
@@ -83,12 +91,14 @@ public class HttpChannelizer extends AbstractChannelizer {
pipeline.addLast(PIPELINE_AUTHENTICATOR, authenticationHandler);
}
+ // The authorizer needs access to the RequestMessage but the authenticator doesn't.
+ pipeline.addLast("http-requestmessage-decoder", httpRequestMessageDecoder);
+
if (authorizer != null) {
final ChannelInboundHandlerAdapter authorizationHandler = new HttpBasicAuthorizationHandler(authorizer);
pipeline.addLast(PIPELINE_AUTHORIZER, authorizationHandler);
}
- pipeline.addLast("http-user-agent-handler", new HttpUserAgentHandler());
pipeline.addLast("http-gremlin-handler", httpGremlinEndpointHandler);
// Note that channelRead()'s do not propagate down the pipeline past HttpGremlinEndpointHandler
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthorizationHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthorizationHandler.java
index 94774597af..ee60461cb7 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthorizationHandler.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpBasicAuthorizationHandler.java
@@ -31,6 +31,7 @@ import org.apache.tinkerpop.gremlin.server.GremlinServer;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
import org.apache.tinkerpop.gremlin.server.authz.AuthorizationException;
import org.apache.tinkerpop.gremlin.server.authz.Authorizer;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,16 +59,8 @@ public class HttpBasicAuthorizationHandler extends ChannelInboundHandlerAdapter
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
- if (msg instanceof FullHttpMessage){
- final FullHttpMessage request = (FullHttpMessage) msg;
- final boolean keepAlive = HttpUtil.isKeepAlive(request);
- final RequestMessage requestMessage;
- try {
- requestMessage = HttpHandlerUtil.getRequestMessageFromHttpRequest((FullHttpRequest) request);
- } catch (IllegalArgumentException iae) {
- HttpHandlerUtil.sendError(ctx, BAD_REQUEST, iae.getMessage(), keepAlive);
- return;
- }
+ if (msg instanceof RequestMessageV4) {
+ final RequestMessageV4 requestMessage = (RequestMessageV4) msg;
try {
user = ctx.channel().attr(StateKey.AUTHENTICATED_USER).get();
@@ -76,26 +69,20 @@ public class HttpBasicAuthorizationHandler extends ChannelInboundHandlerAdapter
}
authorizer.authorize(user, requestMessage);
- ctx.fireChannelRead(request);
+ ctx.fireChannelRead(msg);
} catch (AuthorizationException ex) { // Expected: users can alternate between allowed and disallowed requests
String address = ctx.channel().remoteAddress().toString();
if (address.startsWith("/") && address.length() > 1) address = address.substring(1);
- final String script;
- try {
- script = HttpHandlerUtil.getRequestMessageFromHttpRequest((FullHttpRequest) request).getArgOrDefault(Tokens.ARGS_GREMLIN, "");
- } catch (IllegalArgumentException iae) {
- HttpHandlerUtil.sendError(ctx, BAD_REQUEST, requestMessage.getRequestId(), iae.getMessage(), keepAlive);
- return;
- }
+ final String script = requestMessage.getGremlin().toString();
auditLogger.info("User {} with address {} attempted an unauthorized http request: {}",
user.getName(), address, script);
final String message = String.format("No authorization for script [%s] - check permissions.", script);
- HttpHandlerUtil.sendError(ctx, UNAUTHORIZED, requestMessage.getRequestId(), message, keepAlive);
+ HttpHandlerUtil.sendError(ctx, UNAUTHORIZED, message);
ReferenceCountUtil.release(msg);
} catch (Exception ex) {
final String message = String.format(
"%s is not ready to handle requests - unknown error", authorizer.getClass().getSimpleName());
- HttpHandlerUtil.sendError(ctx, INTERNAL_SERVER_ERROR, requestMessage.getRequestId(), message, keepAlive);
+ HttpHandlerUtil.sendError(ctx, INTERNAL_SERVER_ERROR, message);
ReferenceCountUtil.release(msg);
}
} else {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
index ca58c9253c..2fc63afcc2 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
@@ -22,16 +22,12 @@ import com.codahale.metrics.Timer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.TooLongFrameException;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpUtil;
-import io.netty.util.ReferenceCountUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor;
import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException;
@@ -54,7 +50,6 @@ import org.apache.tinkerpop.gremlin.server.Settings;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
import org.apache.tinkerpop.gremlin.server.ProcessingException;
import org.apache.tinkerpop.gremlin.server.util.MetricManager;
-import org.apache.tinkerpop.gremlin.server.util.TextPlainMessageSerializer;
import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
import org.apache.tinkerpop.gremlin.structure.Column;
import org.apache.tinkerpop.gremlin.structure.Graph;
@@ -64,11 +59,10 @@ import org.apache.tinkerpop.gremlin.util.ExceptionHelper;
import org.apache.tinkerpop.gremlin.util.MessageSerializer;
import org.apache.tinkerpop.gremlin.util.Tokens;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
-import org.apache.tinkerpop.gremlin.util.ser.MessageChunkSerializer;
-import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializerV4;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.javatuples.Pair;
import org.slf4j.Logger;
@@ -87,26 +81,18 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.codahale.metrics.MetricRegistry.name;
import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING;
import static io.netty.handler.codec.http.HttpHeaderValues.CHUNKED;
-import static io.netty.handler.codec.http.HttpMethod.POST;
-import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
-import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
-import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.apache.tinkerpop.gremlin.server.handler.HttpGremlinEndpointHandler.RequestState.CHUNKING_NOT_SUPPORTED;
@@ -123,7 +109,7 @@ import static org.apache.tinkerpop.gremlin.server.handler.HttpHandlerUtil.writeE
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
@ChannelHandler.Sharable
-public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
+public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<RequestMessageV4> {
private static final Logger logger = LoggerFactory.getLogger(HttpGremlinEndpointHandler.class);
private static final Logger auditLogger = LoggerFactory.getLogger(GremlinServer.AUDIT_LOGGER_NAME);
@@ -168,17 +154,10 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
*/
private final Map<String, MessageSerializer<?>> serializers;
- /**
- * Serializer for {@code text/plain} which is a serializer exclusive to HTTP.
- */
- private static final TextPlainMessageSerializer textPlainSerializer = new TextPlainMessageSerializer();
-
private final GremlinExecutor gremlinExecutor;
private final GraphManager graphManager;
private final Settings settings;
- private static final Pattern pattern = Pattern.compile("(.*);q=(.*)");
-
public HttpGremlinEndpointHandler(final Map<String, MessageSerializer<?>> serializers,
final GremlinExecutor gremlinExecutor,
final GraphManager graphManager,
@@ -190,241 +169,184 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
- if (msg instanceof FullHttpRequest) {
- final FullHttpRequest req = (FullHttpRequest) msg;
- final boolean keepAlive = HttpUtil.isKeepAlive(req);
+ public void channelRead0(final ChannelHandlerContext ctx, final RequestMessageV4 requestMessage) {
+ Pair<String, MessageTextSerializerV4<?>> serializer = ctx.attr(StateKey.SERIALIZER).get();
+ final RequestState requestState = NOT_STARTED;
- if (HttpUtil.is100ContinueExpected(req)) {
- ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
- }
+ final Context requestCtx = new Context(requestMessage, ctx, settings, graphManager, gremlinExecutor,
+ gremlinExecutor.getScheduledExecutorService(), requestState);
- if (req.method() != POST) {
- HttpHandlerUtil.sendError(ctx, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED.toString(), keepAlive);
- ReferenceCountUtil.release(msg);
- return;
- }
+ final Timer.Context timerContext = evalOpTimer.time();
+ // timeout override - handle both deprecated and newly named configuration. earlier logic should prevent
+ // both configurations from being submitted at the same time
+ final long seto = requestCtx.getSettings().getEvaluationTimeout();
+
+ final FutureTask<Void> evalFuture = new FutureTask<>(() -> {
+ requestCtx.setStartedResponse();
- final RequestMessage requestMessage;
try {
- requestMessage = HttpHandlerUtil.getRequestMessageFromHttpRequest(req, serializers);
- } catch (IllegalArgumentException | SerializationException | NullPointerException ex) {
- HttpHandlerUtil.sendError(ctx, BAD_REQUEST, ex.getMessage(), keepAlive);
- ReferenceCountUtil.release(msg);
- return;
- }
+ logger.debug("Processing request containing script [{}] and bindings of [{}] on {}",
+ requestMessage.getFieldOrDefault(Tokens.ARGS_GREMLIN, ""),
+ requestMessage.getFieldOrDefault(Tokens.ARGS_BINDINGS, Collections.emptyMap()),
+ Thread.currentThread().getName());
+ if (settings.enableAuditLog) {
+ AuthenticatedUser user = ctx.channel().attr(StateKey.AUTHENTICATED_USER).get();
+ if (null == user) { // This is expected when using the AllowAllAuthenticator
+ user = AuthenticatedUser.ANONYMOUS_USER;
+ }
+ String address = ctx.channel().remoteAddress().toString();
+ if (address.startsWith("/") && address.length() > 1) address = address.substring(1);
+ auditLogger.info("User {} with address {} requested: {}", user.getName(), address,
+ requestMessage.getFieldOrDefault(Tokens.ARGS_GREMLIN, ""));
+ }
- final UUID requestId = requestMessage.getRequestId();
- final String acceptMime = Optional.ofNullable(req.headers().get(HttpHeaderNames.ACCEPT)).orElse("application/json");
- final Pair<String, MessageSerializer<?>> serializer = chooseSerializer(acceptMime);
- if (null == serializer) {
- HttpHandlerUtil.sendError(ctx, BAD_REQUEST, requestId, String.format("no serializer for requested Accept header: %s", acceptMime),
- keepAlive);
- ReferenceCountUtil.release(msg);
- return;
+ // Send back the 200 OK response header here since the response is always chunk transfer encoded. Any
+ // failures that follow this will show up in the response body instead.
+ final HttpResponse responseHeader = new DefaultHttpResponse(HTTP_1_1, OK);
+ responseHeader.headers().set(TRANSFER_ENCODING, CHUNKED);
+ responseHeader.headers().set(HttpHeaderNames.CONTENT_TYPE, serializer.getValue0());
+ ctx.writeAndFlush(responseHeader);
+
+ switch (requestMessage.getGremlinType()) {
+ case "":
+ case Tokens.OPS_EVAL:
+ iterateScriptEvalResult(requestCtx, serializer.getValue1(), requestMessage);
+ break;
+ case Tokens.OPS_BYTECODE:
+ iterateTraversal(requestCtx, serializer.getValue1(), translateBytecodeToTraversal(requestCtx));
+ break;
+ case Tokens.OPS_INVALID:
+ final String msgInvalid =
+ String.format("Message could not be parsed. Check the format of the request. [%s]", requestMessage);
+ throw new ProcessingException(msgInvalid,
+ ResponseMessage.build(requestMessage)
+ .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
+ .statusMessage(msgInvalid)
+ .create());
+ default:
+ final String msgDefault =
+ String.format("Message with gremlin of type [%s] is not recognized.", requestMessage.getGremlinType());
+ throw new ProcessingException(msgDefault,
+ ResponseMessage.build(requestMessage)
+ .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
+ .statusMessage(msgDefault)
+ .create());
+ }
+ } catch (Throwable t) {
+ writeError(requestCtx, formErrorResponseMessage(t, requestMessage), serializer.getValue1());
+ } finally {
+ timerContext.stop();
+
+ // There is a race condition that this query may have finished before the timeoutFuture was created,
+ // though this is very unlikely. This is handled in the settor, if this has already been grabbed.
+ // If we passed this point and the setter hasn't been called, it will cancel the timeoutFuture inside
+ // the setter to compensate.
+ final ScheduledFuture<?> timeoutFuture = requestCtx.getTimeoutExecutor();
+ if (null != timeoutFuture)
+ timeoutFuture.cancel(true);
}
- final String origin = req.headers().get(HttpHeaderNames.ORIGIN);
-
- // not using the req anywhere below here - assume it is safe to release at this point.
- ReferenceCountUtil.release(msg);
-
- final RequestState requestState = serializer.getValue1() instanceof MessageChunkSerializer
- ? NOT_STARTED
- : CHUNKING_NOT_SUPPORTED;
-
- final Context requestCtx = new Context(requestMessage, ctx, settings, graphManager, gremlinExecutor,
- gremlinExecutor.getScheduledExecutorService(), requestState);
-
- final Timer.Context timerContext = evalOpTimer.time();
- // timeout override - handle both deprecated and newly named configuration. earlier logic should prevent
- // both configurations from being submitted at the same time
- final Map<String, Object> args = requestMessage.getArgs();
- final long seto = args.containsKey(Tokens.ARGS_EVAL_TIMEOUT) ?
- ((Number) args.get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() : requestCtx.getSettings().getEvaluationTimeout();
-
- final FutureTask<Void> evalFuture = new FutureTask<>(() -> {
- requestCtx.setStartedResponse();
-
- try {
- logger.debug("Processing request containing script [{}] and bindings of [{}] on {}",
- requestMessage.getArgOrDefault(Tokens.ARGS_GREMLIN, ""),
- requestMessage.getArgOrDefault(Tokens.ARGS_BINDINGS, Collections.emptyMap()),
- Thread.currentThread().getName());
- if (settings.enableAuditLog) {
- AuthenticatedUser user = ctx.channel().attr(StateKey.AUTHENTICATED_USER).get();
- if (null == user) { // This is expected when using the AllowAllAuthenticator
- user = AuthenticatedUser.ANONYMOUS_USER;
- }
- String address = ctx.channel().remoteAddress().toString();
- if (address.startsWith("/") && address.length() > 1) address = address.substring(1);
- auditLogger.info("User {} with address {} requested: {}", user.getName(), address,
- requestMessage.getArgOrDefault(Tokens.ARGS_GREMLIN, ""));
- }
+ return null;
+ });
- // Send back the 200 OK response header here since the response is always chunk transfer encoded. Any
- // failures that follow this will show up in the response body instead.
- final HttpResponse responseHeader = new DefaultHttpResponse(HTTP_1_1, OK);
- responseHeader.headers().set(TRANSFER_ENCODING, CHUNKED);
- responseHeader.headers().set(HttpHeaderNames.CONTENT_TYPE, serializer.getValue0());
- HttpUtil.setKeepAlive(responseHeader, keepAlive);
- if (origin != null) {
- responseHeader.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
- }
- ctx.writeAndFlush(responseHeader);
-
- switch (requestMessage.getOp()) {
- case "":
- case Tokens.OPS_EVAL:
- iterateScriptEvalResult(requestCtx, serializer.getValue1(), requestMessage);
- break;
- case Tokens.OPS_BYTECODE:
- iterateTraversal(requestCtx, serializer.getValue1(), translateBytecodeToTraversal(requestCtx));
- break;
- case Tokens.OPS_INVALID:
- final String msgInvalid =
- String.format("Message could not be parsed. Check the format of the request. [%s]", requestMessage);
- throw new ProcessingException(msgInvalid,
- ResponseMessage.build(requestMessage)
- .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
- .statusMessage(msgInvalid)
- .create());
- default:
- final String msgDefault =
- String.format("Message with op code [%s] is not recognized.", requestMessage.getOp());
- throw new ProcessingException(msgDefault,
- ResponseMessage.build(requestMessage)
- .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
- .statusMessage(msgDefault)
- .create());
- }
- } catch (Exception ex) {
- Throwable t = ex;
- if (ex instanceof UndeclaredThrowableException)
- t = t.getCause();
-
- // if any exception in the chain is TemporaryException or Failure then we should respond with the
- // right error code so that the client knows to retry
- final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
- if (possibleSpecialException.isPresent()) {
- final Throwable special = possibleSpecialException.get();
- final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(requestMessage).
- statusMessage(special.getMessage()).
- statusAttributeException(special);
- if (special instanceof TemporaryException) {
- specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
- } else if (special instanceof Failure) {
- final Failure failure = (Failure) special;
- specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
- statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
- }
- writeError(requestCtx, specialResponseMsg.create(), serializer.getValue1());
- } else if (t instanceof ProcessingException) {
- final ProcessingException pe = (ProcessingException) t;
- logger.warn(pe.getMessage(), pe);
- writeError(requestCtx, pe.getResponseMessage(), serializer.getValue1());
- } else if (ExceptionHelper.getRootCause(ex) instanceof TooLongFrameException) {
- writeError(requestCtx,
- ResponseMessage.build(requestId)
- .code(ResponseStatusCode.SERVER_ERROR)
- .statusMessage(t.getMessage() + " - increase the maxContentLength")
- .create(),
- serializer.getValue1());
- } else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
- final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
- logger.warn(errorMessage);
+ try {
+ final Future<?> executionFuture = requestCtx.getGremlinExecutor().getExecutorService().submit(evalFuture);
+ if (seto > 0) {
+ // Schedule a timeout in the thread pool for future execution
+ requestCtx.setTimeoutExecutor(requestCtx.getScheduledExecutorService().schedule(() -> {
+ executionFuture.cancel(true);
+ if (!requestCtx.getStartedResponse()) {
+ final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", requestMessage);
writeError(requestCtx,
ResponseMessage.build(requestMessage)
.code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
.statusMessage(errorMessage)
- .statusAttributeException(ex)
- .create(),
- serializer.getValue1());
- } else if (t instanceof TimedInterruptTimeoutException) {
- // occurs when the TimedInterruptCustomizerProvider is in play
- final String errorMessage = String.format("A timeout occurred within the script during evaluation of [%s] - consider increasing the limit given to TimedInterruptCustomizerProvider", msg);
- logger.warn(errorMessage);
- writeError(requestCtx,
- ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
- .statusMessage("Timeout during script evaluation triggered by TimedInterruptCustomizerProvider")
- .statusAttributeException(t).create(),
- serializer.getValue1());
- } else if (t instanceof TimeoutException) {
- final String errorMessage = String.format("Script evaluation exceeded the configured threshold for request [%s]", msg);
- logger.warn(errorMessage, t);
- writeError(requestCtx,
- ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
- .statusMessage(t.getMessage())
- .statusAttributeException(t).create(),
- serializer.getValue1());
- } else if (t instanceof MultipleCompilationErrorsException && t.getMessage().contains("Method too large") &&
- ((MultipleCompilationErrorsException) t).getErrorCollector().getErrorCount() == 1) {
- final String errorMessage = String.format("The Gremlin statement that was submitted exceeds the maximum compilation size allowed by the JVM, please split it into multiple smaller statements - %s", trimMessage(requestMessage));
- logger.warn(errorMessage);
- writeError(requestCtx,
- ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
- .statusMessage(errorMessage)
- .statusAttributeException(t).create(),
- serializer.getValue1());
- } else {
- logger.warn(String.format("Exception processing a Traversal on iteration for request [%s].", requestId), ex);
- writeError(requestCtx,
- ResponseMessage.build(requestMessage)
- .code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
- .statusMessage((t != null) ? t.getMessage() : ex.getMessage())
- .statusAttributeException(ex)
.create(),
serializer.getValue1());
}
- } finally {
- timerContext.stop();
-
- // There is a race condition that this query may have finished before the timeoutFuture was created,
- // though this is very unlikely. This is handled in the settor, if this has already been grabbed.
- // If we passed this point and the setter hasn't been called, it will cancel the timeoutFuture inside
- // the setter to compensate.
- final ScheduledFuture<?> timeoutFuture = requestCtx.getTimeoutExecutor();
- if (null != timeoutFuture)
- timeoutFuture.cancel(true);
- }
-
- return null;
- });
-
- try {
- final Future<?> executionFuture = requestCtx.getGremlinExecutor().getExecutorService().submit(evalFuture);
- if (seto > 0) {
- // Schedule a timeout in the thread pool for future execution
- requestCtx.setTimeoutExecutor(requestCtx.getScheduledExecutorService().schedule(() -> {
- executionFuture.cancel(true);
- if (!requestCtx.getStartedResponse()) {
- final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", requestMessage);
- writeError(requestCtx,
- ResponseMessage.build(requestMessage)
- .code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
- .statusMessage(errorMessage)
- .create(),
- serializer.getValue1());
- }
- }, seto, TimeUnit.MILLISECONDS));
- }
- } catch (RejectedExecutionException ree) {
- writeError(requestCtx,
- ResponseMessage.build(requestMessage).code(ResponseStatusCode.TOO_MANY_REQUESTS).statusMessage("Rate limiting").create(),
- serializer.getValue1());
+ }, seto, TimeUnit.MILLISECONDS));
}
+ } catch (RejectedExecutionException ree) {
+ writeError(requestCtx,
+ ResponseMessage.build(requestMessage).code(ResponseStatusCode.TOO_MANY_REQUESTS).statusMessage("Rate limiting").create(),
+ serializer.getValue1());
}
}
- private void iterateScriptEvalResult(final Context context, MessageSerializer<?> serializer, final RequestMessage message)
- throws ProcessingException, InterruptedException, ScriptException {
- if (!message.optionalArgs(Tokens.ARGS_GREMLIN).isPresent()) {
- final String msg = String.format("A message with an [%s] op code requires a [%s] argument.", Tokens.OPS_EVAL, Tokens.ARGS_GREMLIN);
- throw new ProcessingException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+ private ResponseMessage formErrorResponseMessage(Throwable t, RequestMessageV4 requestMessage) {
+ final ResponseMessage errorResponseMessage;
+ String logMessage = String.format("Exception processing request [%s].", requestMessage);
+
+ if (t instanceof UndeclaredThrowableException) t = t.getCause();
+
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(requestMessage).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ errorResponseMessage = specialResponseMsg.create();
+ } else if (t instanceof ProcessingException) {
+ errorResponseMessage = ((ProcessingException) t).getResponseMessage();
+ } else {
+ t = ExceptionHelper.getRootCause(t);
+
+ if (t instanceof TooLongFrameException) {
+ return ResponseMessage.build(requestMessage)
+ .code(ResponseStatusCode.SERVER_ERROR)
+ .statusMessage(t.getMessage() + " - increase the maxContentLength")
+ .create();
+ } else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
+ final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", requestMessage);
+ errorResponseMessage = ResponseMessage.build(requestMessage)
+ .code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
+ .statusMessage(errorMessage)
+ .statusAttributeException(t)
+ .create();
+ } else if (t instanceof TimedInterruptTimeoutException) {
+ // occurs when the TimedInterruptCustomizerProvider is in play
+ logMessage = String.format("A timeout occurred within the script during evaluation of [%s] - consider increasing the limit given to TimedInterruptCustomizerProvider", requestMessage);
+ errorResponseMessage = ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
+ .statusMessage("Timeout during script evaluation triggered by TimedInterruptCustomizerProvider")
+ .statusAttributeException(t).create();
+ } else if (t instanceof TimeoutException) {
+ logMessage = String.format("Script evaluation exceeded the configured threshold for request [%s]", requestMessage);
+ errorResponseMessage = ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
+ .statusMessage(t.getMessage())
+ .statusAttributeException(t).create();
+ } else if (t instanceof MultipleCompilationErrorsException && t.getMessage().contains("Method too large") &&
+ ((MultipleCompilationErrorsException) t).getErrorCollector().getErrorCount() == 1) {
+ final String errorMessage = String.format("The Gremlin statement that was submitted exceeds the maximum compilation size allowed by the JVM, please split it into multiple smaller statements - %s", requestMessage.trimMessage(1021));
+ logMessage = errorMessage;
+ errorResponseMessage = ResponseMessage.build(requestMessage).code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
+ .statusMessage(errorMessage)
+ .statusAttributeException(t).create();
+ } else {
+ errorResponseMessage = ResponseMessage.build(requestMessage)
+ .code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
+ .statusMessage((t.getMessage() == null) ? t.toString() : t.getMessage())
+ .statusAttributeException(t)
+ .create();
+ }
}
- if (message.optionalArgs(Tokens.ARGS_BINDINGS).isPresent()) {
- final Map bindings = (Map) message.getArgs().get(Tokens.ARGS_BINDINGS);
+ logger.warn(logMessage, t);
+ return errorResponseMessage;
+ }
+
+ private void iterateScriptEvalResult(final Context context, MessageTextSerializerV4<?> serializer, final RequestMessageV4 message)
+ throws ProcessingException, InterruptedException, ScriptException {
+ if (message.optionalField(Tokens.ARGS_BINDINGS).isPresent()) {
+ final Map bindings = (Map) message.getFields().get(Tokens.ARGS_BINDINGS);
if (IteratorUtils.anyMatch(bindings.keySet().iterator(), k -> null == k || !(k instanceof String))) {
final String msg = String.format("The [%s] message is using one or more invalid binding keys - they must be of type String and cannot be null", Tokens.OPS_EVAL);
throw new ProcessingException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
@@ -444,51 +366,38 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
}
}
- final Map<String, Object> args = message.getArgs();
+ final Map<String, Object> args = message.getFields();
final String language = args.containsKey(Tokens.ARGS_LANGUAGE) ? (String) args.get(Tokens.ARGS_LANGUAGE) : "gremlin-groovy";
final GremlinScriptEngine scriptEngine = gremlinExecutor.getScriptEngineManager().getEngineByName(language);
- final Bindings bindings = mergeBindingsFromRequest(context, graphManager.getAsBindings());
- final Object result = scriptEngine.eval((String) message.getArg(Tokens.ARGS_GREMLIN), bindings);
+ final Bindings mergedBindings = mergeBindingsFromRequest(context, new SimpleBindings(graphManager.getAsBindings()));
+ final Object result = scriptEngine.eval((String) message.getGremlin(), mergedBindings);
handleIterator(context, IteratorUtils.asIterator(result), serializer);
}
private static Traversal.Admin<?,?> translateBytecodeToTraversal(Context ctx) throws ProcessingException {
- final RequestMessage requestMsg = ctx.getRequestMessage();
-
- if (!requestMsg.optionalArgs(Tokens.ARGS_GREMLIN).isPresent()) {
- final String msg = String.format("A message with [%s] op code requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN);
- throw new ProcessingException(msg, ResponseMessage.build(requestMsg).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
- }
-
- if (!(requestMsg.optionalArgs(Tokens.ARGS_GREMLIN).get() instanceof Bytecode)) {
- final String msg = String.format("A message with [%s] op code requires a [%s] argument that is of type %s.",
+ final RequestMessageV4 requestMsg = ctx.getRequestMessage();
+ if (!(requestMsg.getGremlin() instanceof Bytecode)) {
+ final String msg = String.format("A [%s] message requires a gremlin argument that is of type %s.",
Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN, Bytecode.class.getSimpleName());
throw new ProcessingException(msg, ResponseMessage.build(requestMsg).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
}
- final Optional<Map<String, String>> aliases = requestMsg.optionalArgs(Tokens.ARGS_ALIASES);
- if (!aliases.isPresent()) {
- final String msg = String.format("A message with [%s] op code requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_ALIASES);
- throw new ProcessingException(msg, ResponseMessage.build(requestMsg).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
- }
-
- if (aliases.get().size() != 1 || !aliases.get().containsKey(Tokens.VAL_TRAVERSAL_SOURCE_ALIAS)) {
- final String msg = String.format("A message with [%s] op code requires the [%s] argument to be a Map containing one alias assignment named '%s'.",
- Tokens.OPS_BYTECODE, Tokens.ARGS_ALIASES, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS);
+ final Optional<String> alias = requestMsg.optionalField(Tokens.ARGS_G);
+ if (!alias.isPresent()) {
+ final String msg = String.format("A [%s] message requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_G);
throw new ProcessingException(msg, ResponseMessage.build(requestMsg).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
}
- final String traversalSourceBindingForAlias = aliases.get().values().iterator().next();
- if (!ctx.getGraphManager().getTraversalSourceNames().contains(traversalSourceBindingForAlias)) {
- final String msg = String.format("The traversal source [%s] for alias [%s] is not configured on the server.", traversalSourceBindingForAlias, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS);
+ final String traversalSourceName = alias.get();
+ if (!ctx.getGraphManager().getTraversalSourceNames().contains(traversalSourceName)) {
+ final String msg = String.format("The traversal source [%s] for alias [%s] is not configured on the server.", traversalSourceName, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS);
throw new ProcessingException(msg, ResponseMessage.build(requestMsg).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
}
- final String traversalSourceName = aliases.get().entrySet().iterator().next().getValue();
final TraversalSource g = ctx.getGraphManager().getTraversalSource(traversalSourceName);
- final Bytecode bytecode = (Bytecode) requestMsg.getArgs().get(Tokens.ARGS_GREMLIN); // type checked at start of method.
+ final Bytecode bytecode = (Bytecode) requestMsg.getGremlin(); // type checked at start of method.
try {
final Optional<String> lambdaLanguage = BytecodeHelper.getLambdaLanguage(bytecode);
if (!lambdaLanguage.isPresent())
@@ -515,77 +424,52 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
logger.error("Error processing HTTP Request", cause);
if (ctx.channel().isActive()) {
- HttpHandlerUtil.sendError(ctx, INTERNAL_SERVER_ERROR, cause.getMessage(), false);
+ HttpHandlerUtil.sendError(ctx, INTERNAL_SERVER_ERROR, cause.getMessage());
}
}
private Bindings mergeBindingsFromRequest(final Context ctx, final Bindings bindings) throws ProcessingException {
// alias any global bindings to a different variable.
- final RequestMessage msg = ctx.getRequestMessage();
- if (msg.getArgs().containsKey(Tokens.ARGS_ALIASES)) {
- final Map<String, String> aliases = (Map<String, String>) msg.getArgs().get(Tokens.ARGS_ALIASES);
- for (Map.Entry<String, String> aliasKv : aliases.entrySet()) {
- boolean found = false;
-
- // first check if the alias refers to a Graph instance
- final Graph graph = ctx.getGraphManager().getGraph(aliasKv.getValue());
- if (null != graph) {
- bindings.put(aliasKv.getKey(), graph);
- found = true;
- }
+ final RequestMessageV4 msg = ctx.getRequestMessage();
+ if (msg.getFields().containsKey(Tokens.ARGS_G)) {
+ final String aliased = msg.getField(Tokens.ARGS_G);
+ boolean found = false;
+
+ // first check if the alias refers to a Graph instance
+ final Graph graph = ctx.getGraphManager().getGraph(aliased);
+ if (null != graph) {
+ bindings.put(Tokens.ARGS_G, graph);
+ found = true;
+ }
- // if the alias wasn't found as a Graph then perhaps it is a TraversalSource - it needs to be
- // something
- if (!found) {
- final TraversalSource ts = ctx.getGraphManager().getTraversalSource(aliasKv.getValue());
- if (null != ts) {
- bindings.put(aliasKv.getKey(), ts);
- found = true;
- }
+ // if the alias wasn't found as a Graph then perhaps it is a TraversalSource - it needs to be
+ // something
+ if (!found) {
+ final TraversalSource ts = ctx.getGraphManager().getTraversalSource(aliased);
+ if (null != ts) {
+ bindings.put(Tokens.ARGS_G, ts);
+ found = true;
}
+ }
- // this validation is important to calls to GraphManager.commit() and rollback() as they both
- // expect that the aliases supplied are valid
- if (!found) {
- final String error = String.format("Could not alias [%s] to [%s] as [%s] not in the Graph or TraversalSource global bindings",
- aliasKv.getKey(), aliasKv.getValue(), aliasKv.getValue());
- throw new ProcessingException(error, ResponseMessage.build(msg)
- .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(error).create());
- }
+ // this validation is important to calls to GraphManager.commit() and rollback() as they both
+ // expect that the aliases supplied are valid
+ if (!found) {
+ final String error = String.format("Could not alias [%s] to [%s] as [%s] not in the Graph or TraversalSource global bindings",
+ Tokens.ARGS_G, aliased, aliased);
+ throw new ProcessingException(error, ResponseMessage.build(msg)
+ .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(error).create());
}
}
// add any bindings to override any other supplied
- Optional.ofNullable((Map<String, Object>) msg.getArgs().get(Tokens.ARGS_BINDINGS)).ifPresent(bindings::putAll);
+ Optional.ofNullable((Map<String, Object>) msg.getFields().get(Tokens.ARGS_BINDINGS)).ifPresent(bindings::putAll);
return bindings;
}
- private Pair<String, MessageSerializer<?>> chooseSerializer(final String mimeType) {
- final List<Pair<String, Double>> ordered = Stream.of(mimeType.split(",")).map(mediaType -> {
- // parse out each mediaType with its params - keeping it simple and just looking for "quality". if
- // that value isn't there, default it to 1.0. not really validating here so users better get their
- // accept headers straight
- final Matcher matcher = pattern.matcher(mediaType);
- return (matcher.matches()) ? Pair.with(matcher.group(1), Double.parseDouble(matcher.group(2))) : Pair.with(mediaType, 1.0);
- }).sorted((o1, o2) -> o2.getValue0().compareTo(o1.getValue0())).collect(Collectors.toList());
-
- for (Pair<String, Double> p : ordered) {
- // this isn't perfect as it doesn't really account for wildcards. that level of complexity doesn't seem
- // super useful for gremlin server really.
- final String accept = p.getValue0().equals("*/*") ? "application/json" : p.getValue0();
- if (serializers.containsKey(accept))
- return Pair.with(accept, serializers.get(accept));
- else if (accept.equals("text/plain")) {
- return Pair.with(accept, textPlainSerializer);
- }
- }
-
- return null;
- }
-
- private void iterateTraversal(final Context context, MessageSerializer<?> serializer, Traversal.Admin<?, ?> traversal)
+ private void iterateTraversal(final Context context, MessageTextSerializerV4<?> serializer, Traversal.Admin<?, ?> traversal)
throws InterruptedException {
- final RequestMessage msg = context.getRequestMessage();
+ final RequestMessageV4 msg = context.getRequestMessage();
logger.debug("Traversal request {} for in thread {}", msg.getRequestId(), Thread.currentThread().getName());
// compile the traversal - without it getEndStep() has nothing in it
@@ -593,9 +477,9 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
handleIterator(context, new TraverserIterator(traversal), serializer);
}
- private void handleIterator(final Context context, final Iterator itty, final MessageSerializer<?> serializer) throws InterruptedException {
+ private void handleIterator(final Context context, final Iterator itty, final MessageTextSerializerV4<?> serializer) throws InterruptedException {
final ChannelHandlerContext nettyContext = context.getChannelHandlerContext();
- final RequestMessage msg = context.getRequestMessage();
+ final RequestMessageV4 msg = context.getRequestMessage();
final Settings settings = context.getSettings();
boolean warnOnce = false;
@@ -615,7 +499,7 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
}
// the batch size can be overridden by the request
- final int resultIterationBatchSize = (Integer) msg.optionalArgs(Tokens.ARGS_BATCH_SIZE)
+ final int resultIterationBatchSize = (Integer) msg.optionalField(Tokens.ARGS_BATCH_SIZE)
.orElse(settings.resultIterationBatchSize);
List<Object> aggregate = new ArrayList<>(resultIterationBatchSize);
@@ -717,8 +601,8 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
filter(i -> i instanceof TemporaryException || i instanceof Failure).findFirst();
}
- private static ByteBuf makeChunk(final Context ctx, final RequestMessage msg,
- final MessageSerializer<?> serializer, final List<Object> aggregate,
+ private static ByteBuf makeChunk(final Context ctx, final RequestMessageV4 msg,
+ final MessageTextSerializerV4<?> serializer, final List<Object> aggregate,
final boolean hasMore) throws Exception {
try {
final ChannelHandlerContext nettyContext = ctx.getChannelHandlerContext();
@@ -747,13 +631,11 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
return serializer.serializeResponseAsBinary(responseMessage, nettyContext.alloc());
}
- final MessageChunkSerializer<?> chunkSerializer = (MessageChunkSerializer) serializer;
-
switch (ctx.getRequestState()) {
case NOT_STARTED:
if (hasMore) {
ctx.setRequestState(STREAMING);
- return chunkSerializer.writeHeader(responseMessage, nettyContext.alloc());
+ return serializer.writeHeader(responseMessage, nettyContext.alloc());
}
ctx.setRequestState(FINISHED);
@@ -764,10 +646,10 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
.create(), nettyContext.alloc());
case STREAMING:
- return chunkSerializer.writeChunk(aggregate, nettyContext.alloc());
+ return serializer.writeChunk(aggregate, nettyContext.alloc());
case FINISHING:
ctx.setRequestState(FINISHED);
- return chunkSerializer.writeFooter(responseMessage, nettyContext.alloc());
+ return serializer.writeFooter(responseMessage, nettyContext.alloc());
}
// todo: just throw?
@@ -786,18 +668,6 @@ public class HttpGremlinEndpointHandler extends ChannelInboundHandlerAdapter {
}
}
- /**
- * Used to decrease the size of a Gremlin script that triggered a "method too large" exception so that it
- * doesn't log a massive text string nor return a large error message.
- */
- private RequestMessage trimMessage(final RequestMessage msg) {
- final RequestMessage trimmedMsg = RequestMessage.from(msg).create();
- if (trimmedMsg.getArgs().containsKey(Tokens.ARGS_GREMLIN))
- trimmedMsg.getArgs().put(Tokens.ARGS_GREMLIN, trimmedMsg.getArgs().get(Tokens.ARGS_GREMLIN).toString().substring(0, 1021) + "...");
-
- return trimmedMsg;
- }
-
public enum RequestState {
CHUNKING_NOT_SUPPORTED,
NOT_STARTED,
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
index 466f0195fc..209503a0d3 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
@@ -44,7 +44,6 @@ import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
-import org.apache.tinkerpop.gremlin.util.ser.MessageChunkSerializer;
import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializerV4;
import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
@@ -56,6 +55,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -82,199 +83,37 @@ public class HttpHandlerUtil {
private static final String ARGS_BINDINGS_DOT = Tokens.ARGS_BINDINGS + ".";
private static final String ARGS_ALIASES_DOT = Tokens.ARGS_ALIASES + ".";
/**
- * This is just a generic mapper to interpret the JSON of a POSTed request. It is not used for the serialization
- * of the response.
+ * A generic mapper to return JSON errors in specific cases.
*/
private static final ObjectMapper mapper = new ObjectMapper();
/**
- * Convert a http request into a {@link RequestMessage}.
- * There are 2 payload types options here.
- * 1.
- * existing https://tinkerpop.apache.org/docs/current/reference/#connecting-via-http
- * intended to use with curl, postman, etc. by users
- * both GET and POST
- * Content-Type header can be empty or application/json
- * Accept header can be any, most useful can be application/json, text/plain, application/vnd.gremlin-v3.0+json and application/vnd.gremlin-v3.0+json;types=false
- * Request body example: { "gremlin": "g.V()" }
- * 2.
- * experimental payload with serialized RequestMessage
- * intended for drivers/GLV's. Support both gremlin and bytecode queries.
- * only POST
- * Content-Type is defined by used serializer, expected type GraphSON application/vnd.gremlin-v3.0+json or GraphBinary application/vnd.graphbinary-v1.0. Untyped GraphSON is not supported, it can't deserialize bytecode
- * Accept header can be any.
- * Request body contains serialized RequestMessage
+ * Helper method to send errors back as JSON. Only to be used when the RequestMessage couldn't be parsed, because
+ * a proper serialized ResponseMessage should be sent in that case.
+ *
+ * @param ctx The netty channel context.
+ * @param status The HTTP error status code.
+ * @param message The error message to contain the body.
*/
- public static RequestMessage getRequestMessageFromHttpRequest(final FullHttpRequest request,
- Map<String, MessageSerializer<?>> serializers) throws SerializationException {
- final String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
-
- if (request.method() == POST && contentType != null && !contentType.equals("application/json") && serializers.containsKey(contentType)) {
- final MessageSerializer<?> serializer = serializers.get(contentType);
- if (!(serializer instanceof MessageTextSerializerV4)) {
- throw new SerializationException("Server only supports V4 or later serializers.");
- }
-
- final MessageTextSerializerV4<?> serializerV4 = (MessageTextSerializerV4) serializer;
-
- final ByteBuf buffer = request.content();
-
- // additional validation for header
- final int first = buffer.readByte();
- // payload can be plain json or can start with additional header with content type.
- // if first character is not "{" (0x7b) then need to verify is correct serializer selected.
- if (first != 0x7b) {
- final byte[] bytes = new byte[first];
- buffer.readBytes(bytes);
- final String mimeType = new String(bytes, StandardCharsets.UTF_8);
-
- if (Arrays.stream(serializer.mimeTypesSupported()).noneMatch(t -> t.equals(mimeType)))
- throw new IllegalArgumentException("Mime type mismatch. Value in content-type header is not equal payload header.");
- } else {
- buffer.resetReaderIndex();
- }
-
- try {
- return serializerV4.deserializeRequestMessageV4(buffer).convertToV1();
- } catch (Exception e) {
- throw new SerializationException("Unable to deserialize request using: " + serializerV4.getClass().getSimpleName(), e);
- }
- }
- return getRequestMessageFromHttpRequest(request);
- }
-
- /**
- * Convert a http request into a {@link RequestMessage}.
- */
- public static RequestMessage getRequestMessageFromHttpRequest(final FullHttpRequest request) {
- return getRequestMessageV4FromHttpRequest(request).convertToV1();
- }
-
- public static RequestMessageV4 getRequestMessageV4FromHttpRequest(final FullHttpRequest request) {
- final JsonNode body;
- try {
- body = mapper.readTree(request.content().toString(CharsetUtil.UTF_8));
- } catch (IOException ioe) {
- throw new IllegalArgumentException("body could not be parsed", ioe);
- }
-
- final JsonNode scriptNode = body.get(Tokens.ARGS_GREMLIN);
- if (null == scriptNode) throw new IllegalArgumentException("no gremlin script supplied");
-
- final JsonNode bindingsNode = body.get(Tokens.ARGS_BINDINGS);
- if (bindingsNode != null && !bindingsNode.isObject())
- throw new IllegalArgumentException("bindings must be a Map");
-
- final Map<String, Object> bindings = new HashMap<>();
- if (bindingsNode != null)
- bindingsNode.fields().forEachRemaining(kv -> bindings.put(kv.getKey(), fromJsonNode(kv.getValue())));
-
- final JsonNode gNode = body.get(Tokens.ARGS_G);
- final String g = (null == gNode) ? null : gNode.asText();
-
- final JsonNode languageNode = body.get(Tokens.ARGS_LANGUAGE);
- final String language = null == languageNode ? "gremlin-groovy" : languageNode.asText();
-
- final JsonNode requestIdNode = body.get(Tokens.REQUEST_ID);
- final UUID requestId = null == requestIdNode ? UUID.randomUUID() : UUID.fromString(requestIdNode.asText());
-
- final JsonNode chunkSizeNode = body.get(Tokens.ARGS_BATCH_SIZE);
- final Integer chunkSize = null == chunkSizeNode ? null : chunkSizeNode.asInt();
-
- final RequestMessageV4.Builder builder = RequestMessageV4.build(scriptNode.asText()).overrideRequestId(requestId)
- .addBindings(bindings).addLanguage(language);
- if (null != g) builder.addG(g);
- if (null != chunkSize) builder.addChunkSize(chunkSize);
- return builder.create();
- }
-
- private static Object fromJsonNode(final JsonNode node) {
- if (node.isNull())
- return null;
- else if (node.isObject()) {
- final Map<String, Object> map = new HashMap<>();
- final ObjectNode objectNode = (ObjectNode) node;
- final Iterator<String> iterator = objectNode.fieldNames();
- while (iterator.hasNext()) {
- String key = iterator.next();
- map.put(key, fromJsonNode(objectNode.get(key)));
- }
- return map;
- } else if (node.isArray()) {
- final ArrayNode arrayNode = (ArrayNode) node;
- final ArrayList<Object> array = new ArrayList<>();
- for (int i = 0; i < arrayNode.size(); i++) {
- array.add(fromJsonNode(arrayNode.get(i)));
- }
- return array;
- } else if (node.isFloatingPointNumber())
- return node.asDouble();
- else if (node.isIntegralNumber())
- return node.asLong();
- else if (node.isBoolean())
- return node.asBoolean();
- else
- return node.asText();
- }
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status,
- final String message, final boolean keepAlive) {
- sendError(ctx, status, null, message, Optional.empty(), keepAlive);
- }
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status, final UUID requestId,
- final String message, final boolean keepAlive) {
- sendError(ctx, status, requestId, message, Optional.empty(), keepAlive);
- }
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status, final UUID requestId,
- final String message, final Optional<Throwable> t, final boolean keepAlive) {
- if (t.isPresent())
- logger.warn(String.format("Invalid request - responding with %s and %s", status, message), t.get());
- else
- logger.warn(String.format("Invalid request - responding with %s and %s", status, message));
-
+ public static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status, final String message) {
+ logger.warn(String.format("Invalid request - responding with %s and %s", status, message));
errorMeter.mark();
+
final ObjectNode node = mapper.createObjectNode();
node.put("message", message);
- if (t.isPresent()) {
- // "Exception-Class" needs to go away - didn't realize it was named that way during review for some reason.
- // replaced with the same method for exception reporting as is used with websocket/nio protocol
- node.put("Exception-Class", t.get().getClass().getName());
- final ArrayNode exceptionList = node.putArray(Tokens.STATUS_ATTRIBUTE_EXCEPTIONS);
- ExceptionUtils.getThrowableList(t.get()).forEach(throwable -> exceptionList.add(throwable.getClass().getName()));
- node.put(Tokens.STATUS_ATTRIBUTE_STACK_TRACE, ExceptionUtils.getStackTrace(t.get()));
- }
- if (requestId != null) {
- node.put("requestId", requestId.toString());
- }
final FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, status, Unpooled.copiedBuffer(node.toString(), CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "application/json");
-
- sendAndCleanupConnection(ctx, keepAlive, response);
- }
-
- static void sendAndCleanupConnection(final ChannelHandlerContext ctx,
- final boolean keepAlive,
- final FullHttpResponse response) {
- HttpUtil.setKeepAlive(response, keepAlive);
HttpUtil.setContentLength(response, response.content().readableBytes());
-
- final ChannelFuture flushPromise = ctx.writeAndFlush(response);
-
- if (!keepAlive) {
- // Close the connection as soon as the response is sent.
- flushPromise.addListener(ChannelFutureListener.CLOSE);
- }
+ ctx.writeAndFlush(response);
}
static void writeError(final Context context, ResponseMessage responseMessage, final MessageSerializer<?> serializer) {
try {
final ChannelHandlerContext ctx = context.getChannelHandlerContext();
final ByteBuf ByteBuf = context.getRequestState() == HttpGremlinEndpointHandler.RequestState.STREAMING
- ? ((MessageChunkSerializer) serializer).writeErrorFooter(responseMessage, ctx.alloc())
+ ? ((MessageTextSerializerV4) serializer).writeErrorFooter(responseMessage, ctx.alloc())
: serializer.serializeResponseAsBinary(responseMessage, ctx.alloc());
context.setRequestState(HttpGremlinEndpointHandler.RequestState.ERROR);
@@ -289,7 +128,13 @@ public class HttpHandlerUtil {
static void sendTrailingHeaders(final ChannelHandlerContext ctx, final ResponseStatusCode statusCode, final String message) {
final DefaultLastHttpContent defaultLastHttpContent = new DefaultLastHttpContent();
defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_CODE, statusCode.getValue());
- defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_MESSAGE, message);
+ try {
+ defaultLastHttpContent.trailingHeaders().add(
+ SerTokens.TOKEN_MESSAGE, URLEncoder.encode(message, StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException uee) {
+ // This should never occur since we use UTF-8 so just log rather than handle.
+ logger.info(StandardCharsets.UTF_8.name() + " encoding not supported", uee);
+ }
ctx.writeAndFlush(defaultLastHttpContent);
}
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestCheckingHandler.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestCheckingHandler.java
index c118dddf56..bdb2d6f0f9 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestCheckingHandler.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestCheckingHandler.java
@@ -22,7 +22,6 @@ import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.ReferenceCountUtil;
import static io.netty.handler.codec.http.HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED;
@@ -30,28 +29,26 @@ import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
/**
- * A handler that checks for errors in the HTTP request.
+ * A handler that checks for errors in the HTTP request for all routes.
*/
@ChannelHandler.Sharable
public class HttpRequestCheckingHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
public HttpRequestCheckingHandler() {
- // This handler never consumes the actual FullHttpRequest so don't release it.
+ // This handler only consumes the FullHttpRequest in certain situations so don't release it.
super(false);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
- final boolean keepAlive = HttpUtil.isKeepAlive(msg);
-
// Chunked transfer encoding is used to stream back results so only accept HTTP/1.1 requests.
if (msg.protocolVersion() != HTTP_1_1) {
- HttpHandlerUtil.sendError(ctx, HTTP_VERSION_NOT_SUPPORTED, "Only HTTP/1.1 is supported.", keepAlive);
+ HttpHandlerUtil.sendError(ctx, HTTP_VERSION_NOT_SUPPORTED, "Only HTTP/1.1 is supported.");
ReferenceCountUtil.release(msg);
return;
}
if ("/favicon.ico".equals(msg.uri())) {
- HttpHandlerUtil.sendError(ctx, NOT_FOUND, "Gremlin Server doesn't have a favicon.ico", keepAlive);
+ HttpHandlerUtil.sendError(ctx, NOT_FOUND, "Gremlin Server doesn't have a favicon.ico");
ReferenceCountUtil.release(msg);
return;
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoder.java
similarity index 54%
copy from gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
copy to gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoder.java
index 466f0195fc..29595107c7 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoder.java
@@ -18,40 +18,25 @@
*/
package org.apache.tinkerpop.gremlin.server.handler;
-import com.codahale.metrics.Meter;
import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelFutureListener;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.DefaultHttpContent;
-import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.CharsetUtil;
-import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.apache.tinkerpop.gremlin.server.Context;
-import org.apache.tinkerpop.gremlin.server.GremlinServer;
-import org.apache.tinkerpop.gremlin.server.Settings;
-import org.apache.tinkerpop.gremlin.server.util.MetricManager;
+import org.apache.tinkerpop.gremlin.server.util.TextPlainMessageSerializer;
import org.apache.tinkerpop.gremlin.util.MessageSerializer;
import org.apache.tinkerpop.gremlin.util.Tokens;
import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
-import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
-import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
-import org.apache.tinkerpop.gremlin.util.ser.MessageChunkSerializer;
import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializerV4;
-import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToMessageDecoder;
import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
import org.apache.tinkerpop.shaded.jackson.databind.node.ArrayNode;
import org.apache.tinkerpop.shaded.jackson.databind.node.ObjectNode;
+import org.javatuples.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,31 +46,96 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import static com.codahale.metrics.MetricRegistry.name;
-import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpMethod.POST;
-import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
-import static io.netty.handler.codec.http.LastHttpContent.EMPTY_LAST_CONTENT;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
+import static org.apache.tinkerpop.gremlin.server.handler.HttpHandlerUtil.sendError;
/**
- * Provides methods shared by the HTTP handlers.
- *
- * @author Stephen Mallette (http://stephen.genoprime.com)
+ * Decodes the contents of a {@code FullHttpRequest}. This will extract the {@code RequestMessageV4} from the
+ * {@code FullHttpRequest} or, if unsuccessful, will flush an error back.
*/
-public class HttpHandlerUtil {
- private static final Logger logger = LoggerFactory.getLogger(HttpHandlerUtil.class);
- static final Meter errorMeter = MetricManager.INSTANCE.getMeter(name(GremlinServer.class, "errors"));
- private static final String ARGS_BINDINGS_DOT = Tokens.ARGS_BINDINGS + ".";
- private static final String ARGS_ALIASES_DOT = Tokens.ARGS_ALIASES + ".";
+@ChannelHandler.Sharable
+public class HttpRequestMessageDecoder extends MessageToMessageDecoder<FullHttpRequest> {
+ private static final Logger logger = LoggerFactory.getLogger(HttpRequestMessageDecoder.class);
+ private static final Pattern pattern = Pattern.compile("(.*);q=(.*)");
+
+ /**
+ * Serializer for {@code text/plain} which is a serializer exclusive to HTTP.
+ */
+ private final TextPlainMessageSerializer textPlainSerializer = new TextPlainMessageSerializer();
+
+ private final Map<String, MessageSerializer<?>> serializers;
+
/**
- * This is just a generic mapper to interpret the JSON of a POSTed request. It is not used for the serialization
- * of the response.
+ * A generic mapper to decode an application/json request.
*/
- private static final ObjectMapper mapper = new ObjectMapper();
+ private final ObjectMapper mapper = new ObjectMapper();
+
+
+ public HttpRequestMessageDecoder(final Map<String, MessageSerializer<?>> serializers) {
+ this.serializers = serializers;
+ }
+
+ @Override
+ protected void decode(final ChannelHandlerContext ctx, final FullHttpRequest req, final List<Object> objects) throws Exception {
+ final String acceptMime = Optional.ofNullable(req.headers().get(HttpHeaderNames.ACCEPT)).orElse("application/json");
+ final Pair<String, MessageSerializer<?>> serializer = chooseSerializer(acceptMime);
+
+ if (req.method() != POST) {
+ sendError(ctx, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED.toString());
+ return;
+ }
+
+ if (null == serializer) {
+ sendError(ctx, BAD_REQUEST, String.format("no serializer for requested Accept header: %s", acceptMime));
+ return;
+ }
+
+ final RequestMessageV4 requestMessage;
+ try {
+ requestMessage = getRequestMessageFromHttpRequest(req, serializers);
+ } catch (IllegalArgumentException | SerializationException | NullPointerException ex) {
+ sendError(ctx, BAD_REQUEST, ex.getMessage());
+ return;
+ }
+
+ // checked in getRequestMessageFromHttpRequest
+ ctx.attr(StateKey.SERIALIZER).set(Pair.with(serializer.getValue0(), (MessageTextSerializerV4) serializer.getValue1()));
+ objects.add(requestMessage);
+ }
+
+ private Pair<String, MessageSerializer<?>> chooseSerializer(final String mimeType) {
+ final List<Pair<String, Double>> ordered = Stream.of(mimeType.split(",")).map(mediaType -> {
+ // parse out each mediaType with its params - keeping it simple and just looking for "quality". if
+ // that value isn't there, default it to 1.0. not really validating here so users better get their
+ // accept headers straight
+ final Matcher matcher = pattern.matcher(mediaType);
+ return (matcher.matches()) ? Pair.with(matcher.group(1), Double.parseDouble(matcher.group(2))) : Pair.with(mediaType, 1.0);
+ }).sorted((o1, o2) -> o2.getValue0().compareTo(o1.getValue0())).collect(Collectors.toList());
+
+ for (Pair<String, Double> p : ordered) {
+ // this isn't perfect as it doesn't really account for wildcards. that level of complexity doesn't seem
+ // super useful for gremlin server really.
+ final String accept = p.getValue0().equals("*/*") ? "application/json" : p.getValue0();
+ if (serializers.containsKey(accept))
+ return Pair.with(accept, serializers.get(accept));
+ else if (accept.equals("text/plain")) {
+ return Pair.with(accept, textPlainSerializer);
+ }
+ }
+
+ return null;
+ }
/**
* Convert a http request into a {@link RequestMessage}.
@@ -105,8 +155,8 @@ public class HttpHandlerUtil {
* Accept header can be any.
* Request body contains serialized RequestMessage
*/
- public static RequestMessage getRequestMessageFromHttpRequest(final FullHttpRequest request,
- Map<String, MessageSerializer<?>> serializers) throws SerializationException {
+ public RequestMessageV4 getRequestMessageFromHttpRequest(final FullHttpRequest request,
+ Map<String, MessageSerializer<?>> serializers) throws SerializationException {
final String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
if (request.method() == POST && contentType != null && !contentType.equals("application/json") && serializers.containsKey(contentType)) {
@@ -135,22 +185,15 @@ public class HttpHandlerUtil {
}
try {
- return serializerV4.deserializeRequestMessageV4(buffer).convertToV1();
+ return serializerV4.deserializeRequestMessageV4(buffer);
} catch (Exception e) {
throw new SerializationException("Unable to deserialize request using: " + serializerV4.getClass().getSimpleName(), e);
}
}
- return getRequestMessageFromHttpRequest(request);
+ return getRequestMessageV4FromHttpRequest(request);
}
- /**
- * Convert a http request into a {@link RequestMessage}.
- */
- public static RequestMessage getRequestMessageFromHttpRequest(final FullHttpRequest request) {
- return getRequestMessageV4FromHttpRequest(request).convertToV1();
- }
-
- public static RequestMessageV4 getRequestMessageV4FromHttpRequest(final FullHttpRequest request) {
+ private RequestMessageV4 getRequestMessageV4FromHttpRequest(final FullHttpRequest request) {
final JsonNode body;
try {
body = mapper.readTree(request.content().toString(CharsetUtil.UTF_8));
@@ -188,7 +231,7 @@ public class HttpHandlerUtil {
return builder.create();
}
- private static Object fromJsonNode(final JsonNode node) {
+ private Object fromJsonNode(final JsonNode node) {
if (node.isNull())
return null;
else if (node.isObject()) {
@@ -216,80 +259,4 @@ public class HttpHandlerUtil {
else
return node.asText();
}
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status,
- final String message, final boolean keepAlive) {
- sendError(ctx, status, null, message, Optional.empty(), keepAlive);
- }
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status, final UUID requestId,
- final String message, final boolean keepAlive) {
- sendError(ctx, status, requestId, message, Optional.empty(), keepAlive);
- }
-
- static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status, final UUID requestId,
- final String message, final Optional<Throwable> t, final boolean keepAlive) {
- if (t.isPresent())
- logger.warn(String.format("Invalid request - responding with %s and %s", status, message), t.get());
- else
- logger.warn(String.format("Invalid request - responding with %s and %s", status, message));
-
- errorMeter.mark();
- final ObjectNode node = mapper.createObjectNode();
- node.put("message", message);
- if (t.isPresent()) {
- // "Exception-Class" needs to go away - didn't realize it was named that way during review for some reason.
- // replaced with the same method for exception reporting as is used with websocket/nio protocol
- node.put("Exception-Class", t.get().getClass().getName());
- final ArrayNode exceptionList = node.putArray(Tokens.STATUS_ATTRIBUTE_EXCEPTIONS);
- ExceptionUtils.getThrowableList(t.get()).forEach(throwable -> exceptionList.add(throwable.getClass().getName()));
- node.put(Tokens.STATUS_ATTRIBUTE_STACK_TRACE, ExceptionUtils.getStackTrace(t.get()));
- }
- if (requestId != null) {
- node.put("requestId", requestId.toString());
- }
-
- final FullHttpResponse response = new DefaultFullHttpResponse(
- HTTP_1_1, status, Unpooled.copiedBuffer(node.toString(), CharsetUtil.UTF_8));
- response.headers().set(CONTENT_TYPE, "application/json");
-
- sendAndCleanupConnection(ctx, keepAlive, response);
- }
-
- static void sendAndCleanupConnection(final ChannelHandlerContext ctx,
- final boolean keepAlive,
- final FullHttpResponse response) {
- HttpUtil.setKeepAlive(response, keepAlive);
- HttpUtil.setContentLength(response, response.content().readableBytes());
-
- final ChannelFuture flushPromise = ctx.writeAndFlush(response);
-
- if (!keepAlive) {
- // Close the connection as soon as the response is sent.
- flushPromise.addListener(ChannelFutureListener.CLOSE);
- }
- }
-
- static void writeError(final Context context, ResponseMessage responseMessage, final MessageSerializer<?> serializer) {
- try {
- final ChannelHandlerContext ctx = context.getChannelHandlerContext();
- final ByteBuf ByteBuf = context.getRequestState() == HttpGremlinEndpointHandler.RequestState.STREAMING
- ? ((MessageChunkSerializer) serializer).writeErrorFooter(responseMessage, ctx.alloc())
- : serializer.serializeResponseAsBinary(responseMessage, ctx.alloc());
-
- context.setRequestState(HttpGremlinEndpointHandler.RequestState.ERROR);
- ctx.writeAndFlush(new DefaultHttpContent(ByteBuf));
-
- sendTrailingHeaders(ctx, responseMessage.getStatus().getCode(), responseMessage.getStatus().getMessage());
- } catch (SerializationException se) {
- logger.warn("Unable to serialize ResponseMessage: {} ", responseMessage);
- }
- }
-
- static void sendTrailingHeaders(final ChannelHandlerContext ctx, final ResponseStatusCode statusCode, final String message) {
- final DefaultLastHttpContent defaultLastHttpContent = new DefaultLastHttpContent();
- defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_CODE, statusCode.getValue());
- defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_MESSAGE, message);
- ctx.writeAndFlush(defaultLastHttpContent);
- }
}
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/StateKey.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/StateKey.java
index f2fd23d550..9e83999160 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/StateKey.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/StateKey.java
@@ -18,11 +18,12 @@
*/
package org.apache.tinkerpop.gremlin.server.handler;
-import org.apache.tinkerpop.gremlin.util.MessageSerializer;
+import io.netty.util.AttributeKey;
import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
import org.apache.tinkerpop.gremlin.server.auth.Authenticator;
-import io.netty.util.AttributeKey;
+import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializerV4;
+import org.javatuples.Pair;
/**
* Keys used in the various handlers to store state in the pipeline.
@@ -36,7 +37,7 @@ public final class StateKey {
/**
* The key for the current serializer requested by the client.
*/
- public static final AttributeKey<MessageSerializer<?>> SERIALIZER = AttributeKey.valueOf("serializer");
+ public static final AttributeKey<Pair<String, MessageTextSerializerV4<?>>> SERIALIZER = AttributeKey.valueOf("serializer");
/**
* The key to indicate if the serializer should use its binary format.
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ContextTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ContextTest.java
index dad908779b..939c88c800 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ContextTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ContextTest.java
@@ -21,7 +21,7 @@ package org.apache.tinkerpop.gremlin.server;
import io.netty.channel.ChannelHandlerContext;
import nl.altindag.log.LogCaptor;
import org.apache.tinkerpop.gremlin.util.Tokens;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -53,9 +53,9 @@ public class ContextTest {
public void shouldParseParametersFromScriptRequest()
{
final ChannelHandlerContext ctx = Mockito.mock(ChannelHandlerContext.class);
- final RequestMessage request = RequestMessage.build(Tokens.OPS_EVAL)
- .addArg(Tokens.ARGS_GREMLIN, "g.with('evaluationTimeout', 1000).with(true).with('materializeProperties', 'tokens').V().out('knows')")
- .create();
+ final RequestMessageV4 request =
+ RequestMessageV4.build("g.with('evaluationTimeout', 1000).with(true).with('materializeProperties', 'tokens').V().out('knows')")
+ .create();
final Settings settings = new Settings();
final Context context = new Context(request, ctx, settings, null, null, null);
@@ -67,10 +67,9 @@ public class ContextTest {
public void shouldSkipInvalidMaterializePropertiesParameterFromScriptRequest()
{
final ChannelHandlerContext ctx = Mockito.mock(ChannelHandlerContext.class);
- final RequestMessage request = RequestMessage.build(Tokens.OPS_EVAL)
- .addArg(Tokens.ARGS_GREMLIN,
- "g.with('evaluationTimeout', 1000).with(true).with('materializeProperties', 'some-invalid-value').V().out('knows')")
- .create();
+ final RequestMessageV4 request =
+ RequestMessageV4.build("g.with('evaluationTimeout', 1000).with(true).with('materializeProperties', 'some-invalid-value').V().out('knows')")
+ .create();
final Settings settings = new Settings();
final Context context = new Context(request, ctx, settings, null, null, null);
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
index b64d050f4c..23c77c1ad3 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
@@ -21,40 +21,22 @@ package org.apache.tinkerpop.gremlin.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.DefaultHttpHeaders;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.util.CharsetUtil;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpVersion;
+import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.EofSensorInputStream;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.io.ChunkedInputStream;
-import org.apache.tinkerpop.gremlin.driver.simple.SimpleClient;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthenticationHandler;
-import org.apache.tinkerpop.gremlin.server.handler.HttpHandlerUtil;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
-import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
-import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1;
-import org.apache.tinkerpop.gremlin.util.Tokens;
import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3;
import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV2;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3;
import org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV4;
import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
import org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator;
@@ -67,23 +49,15 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry;
import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTokens;
-import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
import org.apache.tinkerpop.gremlin.util.ser.Serializers;
import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
-import org.apache.tinkerpop.shaded.jackson.databind.node.ArrayNode;
-import org.hamcrest.CoreMatchers;
import org.junit.Test;
import javax.script.SimpleBindings;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.reflect.Field;
-import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
@@ -99,8 +73,6 @@ import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
-import static org.hamcrest.core.IsNot.not;
-import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.core.StringRegularExpression.matchesRegex;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertEquals;
@@ -134,51 +106,53 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
useTinkerTransactionGraph(settings);
settings.strictTransactionManagement = true;
break;
- case "should200OnPOSTWithGraphSON1d0AcceptHeaderDefaultResultToJson":
- settings.serializers.clear();
- final Settings.SerializerSettings serializerSettingsV1 = new Settings.SerializerSettings();
- serializerSettingsV1.className = GraphSONUntypedMessageSerializerV1.class.getName();
- settings.serializers.add(serializerSettingsV1);
- break;
+// TODO: remove when consensus reached on old serializer support.
+// case "should200OnPOSTWithGraphSON1d0AcceptHeaderDefaultResultToJson":
+// settings.serializers.clear();
+// final Settings.SerializerSettings serializerSettingsV1 = new Settings.SerializerSettings();
+// serializerSettingsV1.className = GraphSONUntypedMessageSerializerV1.class.getName();
+// settings.serializers.add(serializerSettingsV1);
+// break;
case "should200OnPOSTWithGraphSON4d0AcceptHeaderDefaultResultToJson":
settings.serializers.clear();
final Settings.SerializerSettings serializerSettingsV4 = new Settings.SerializerSettings();
serializerSettingsV4.className = GraphSONUntypedMessageSerializerV4.class.getName();
settings.serializers.add(serializerSettingsV4);
break;
- case "should200OnPOSTWithGraphSON2d0AcceptHeaderDefaultResultToJson":
- settings.serializers.clear();
- final Settings.SerializerSettings serializerSettingsV2 = new Settings.SerializerSettings();
- serializerSettingsV2.className = GraphSONMessageSerializerV2.class.getName();
- settings.serializers.add(serializerSettingsV2);
- break;
- case "should200OnPOSTWithGraphSON3d0AcceptHeaderDefaultResultToJson":
- settings.serializers.clear();
- final Settings.SerializerSettings serializerSettingsV3 = new Settings.SerializerSettings();
- serializerSettingsV3.className = GraphSONMessageSerializerV3.class.getName();
- settings.serializers.add(serializerSettingsV3);
- break;
- case "should200OnPOSTWithAnyGraphSONAcceptHeaderDefaultResultToJson":
- settings.serializers.clear();
- final Settings.SerializerSettings serializerSettingsUntypedV1 = new Settings.SerializerSettings();
- serializerSettingsUntypedV1.className = GraphSONUntypedMessageSerializerV1.class.getName();
- settings.serializers.add(serializerSettingsUntypedV1);
- final Settings.SerializerSettings serializerSettingsTypedV1 = new Settings.SerializerSettings();
- serializerSettingsTypedV1.className = GraphSONMessageSerializerV1.class.getName();
- settings.serializers.add(serializerSettingsTypedV1);
- final Settings.SerializerSettings serializerSettingsUntypedV2 = new Settings.SerializerSettings();
- serializerSettingsUntypedV2.className = GraphSONUntypedMessageSerializerV2.class.getName();
- settings.serializers.add(serializerSettingsUntypedV2);
- final Settings.SerializerSettings serializerSettingsTypedV2 = new Settings.SerializerSettings();
- serializerSettingsTypedV2.className = GraphSONMessageSerializerV2.class.getName();
- settings.serializers.add(serializerSettingsTypedV2);
- final Settings.SerializerSettings serializerSettingsUntypedV3 = new Settings.SerializerSettings();
- serializerSettingsUntypedV3.className = GraphSONUntypedMessageSerializerV3.class.getName();
- settings.serializers.add(serializerSettingsUntypedV3);
- final Settings.SerializerSettings serializerSettingsTypedV3 = new Settings.SerializerSettings();
- serializerSettingsTypedV3.className = GraphSONMessageSerializerV3.class.getName();
- settings.serializers.add(serializerSettingsTypedV3);
- break;
+// TODO: remove when consensus reached on old serializer support.
+// case "should200OnPOSTWithGraphSON2d0AcceptHeaderDefaultResultToJson":
+// settings.serializers.clear();
+// final Settings.SerializerSettings serializerSettingsV2 = new Settings.SerializerSettings();
+// serializerSettingsV2.className = GraphSONMessageSerializerV2.class.getName();
+// settings.serializers.add(serializerSettingsV2);
+// break;
+// case "should200OnPOSTWithGraphSON3d0AcceptHeaderDefaultResultToJson":
+// settings.serializers.clear();
+// final Settings.SerializerSettings serializerSettingsV3 = new Settings.SerializerSettings();
+// serializerSettingsV3.className = GraphSONMessageSerializerV3.class.getName();
+// settings.serializers.add(serializerSettingsV3);
+// break;
+// case "should200OnPOSTWithAnyGraphSONAcceptHeaderDefaultResultToJson":
+// settings.serializers.clear();
+// final Settings.SerializerSettings serializerSettingsUntypedV1 = new Settings.SerializerSettings();
+// serializerSettingsUntypedV1.className = GraphSONUntypedMessageSerializerV1.class.getName();
+// settings.serializers.add(serializerSettingsUntypedV1);
+// final Settings.SerializerSettings serializerSettingsTypedV1 = new Settings.SerializerSettings();
+// serializerSettingsTypedV1.className = GraphSONMessageSerializerV1.class.getName();
+// settings.serializers.add(serializerSettingsTypedV1);
+// final Settings.SerializerSettings serializerSettingsUntypedV2 = new Settings.SerializerSettings();
+// serializerSettingsUntypedV2.className = GraphSONUntypedMessageSerializerV2.class.getName();
+// settings.serializers.add(serializerSettingsUntypedV2);
+// final Settings.SerializerSettings serializerSettingsTypedV2 = new Settings.SerializerSettings();
+// serializerSettingsTypedV2.className = GraphSONMessageSerializerV2.class.getName();
+// settings.serializers.add(serializerSettingsTypedV2);
+// final Settings.SerializerSettings serializerSettingsUntypedV3 = new Settings.SerializerSettings();
+// serializerSettingsUntypedV3.className = GraphSONUntypedMessageSerializerV3.class.getName();
+// settings.serializers.add(serializerSettingsUntypedV3);
+// final Settings.SerializerSettings serializerSettingsTypedV3 = new Settings.SerializerSettings();
+// serializerSettingsTypedV3.className = GraphSONMessageSerializerV3.class.getName();
+// settings.serializers.add(serializerSettingsTypedV3);
+// break;
case "should401OnPOSTWithNoAuthorizationHeader":
case "should401OnGETWithBadAuthorizationHeader":
case "should401OnPOSTWithBadAuthorizationHeader":
@@ -419,63 +393,130 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
}
@Test
- public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResult() throws Exception {
+ public void shouldHaveSameVertexResultsWithGremlinLangOrGremlinGroovy() throws Exception {
+ String gremlinLangResult;
+ String gremlinGroovyResult;
+
final CloseableHttpClient httpclient = HttpClients.createDefault();
final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
- httppost.setEntity(new StringEntity("{\"gremlin\":\"gclassic.V()\"}", Consts.UTF_8));
+ httppost.setEntity(new StringEntity("{\"gremlin\":\"g.V()\",\"language\":\"gremlin-lang\",\"g\":\"gmodern\"}", Consts.UTF_8));
try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
assertEquals(200, response.getStatusLine().getStatusCode());
assertEquals("application/json", response.getEntity().getContentType().getValue());
final String json = EntityUtils.toString(response.getEntity());
final JsonNode node = mapper.readTree(json);
- assertEquals(6, node.get("result").get(GraphSONTokens.VALUEPROP).size());
+ gremlinLangResult = node.get("result").get(GraphSONTokens.VALUEPROP).toString();
+ }
+
+ httppost.setEntity(new StringEntity("{\"gremlin\":\"g.V()\",\"language\":\"gremlin-groovy\",\"g\":\"gmodern\"}", Consts.UTF_8));
+ try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/json", response.getEntity().getContentType().getValue());
+ final String json = EntityUtils.toString(response.getEntity());
+ final JsonNode node = mapper.readTree(json);
+ gremlinGroovyResult = node.get("result").get(GraphSONTokens.VALUEPROP).toString();
}
+
+ assertEquals(gremlinGroovyResult, gremlinLangResult);
}
@Test
- public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResultGraphBinaryToString() throws Exception {
+ public void shouldNotHaveBindingsAffectLaterQueries() throws Exception {
+ final String firstResult;
+ final String secondResult;
+
final CloseableHttpClient httpclient = HttpClients.createDefault();
final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
- httppost.setEntity(new StringEntity("{\"gremlin\":\"gclassic.V()\"}", Consts.UTF_8));
- final String mime = SerTokens.MIME_GRAPHBINARY_V1 + "-stringd";
- httppost.addHeader("Accept", mime);
+ httppost.setEntity(new StringEntity("{\"gremlin\":\"g.V()\"}", Consts.UTF_8));
try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(mime, response.getEntity().getContentType().getValue());
-
- final GraphBinaryMessageSerializerV1 serializer = new GraphBinaryMessageSerializerV1(TypeSerializerRegistry.INSTANCE);
- final ResponseMessage msg = serializer.deserializeResponse(toByteBuf(response.getEntity()));
- final List<Object> data = (List<Object>) msg.getResult().getData();
- assertEquals(6, data.size());
- for (Object o : data) {
- assertThat(o, instanceOf(String.class));
- assertThat((String) o, matchesRegex("v\\[\\d\\]"));
- }
+ assertEquals("application/json", response.getEntity().getContentType().getValue());
+ final String json = EntityUtils.toString(response.getEntity());
+ final JsonNode node = mapper.readTree(json);
+ firstResult = node.get("result").get(GraphSONTokens.VALUEPROP).toString();
+ }
+
+ httppost.setEntity(new StringEntity("{\"gremlin\":\"g.V()\",\"g\":\"gmodern\"}", Consts.UTF_8));
+ try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/json", response.getEntity().getContentType().getValue());
+ EntityUtils.toString(response.getEntity());
+ }
+
+ httppost.setEntity(new StringEntity("{\"gremlin\":\"g.V()\"}", Consts.UTF_8));
+ try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/json", response.getEntity().getContentType().getValue());
+ final String json = EntityUtils.toString(response.getEntity());
+ final JsonNode node = mapper.readTree(json);
+ secondResult = node.get("result").get(GraphSONTokens.VALUEPROP).toString();
}
+
+ assertEquals(firstResult, secondResult);
}
@Test
- public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResultTextPlain() throws Exception {
+ public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResult() throws Exception {
final CloseableHttpClient httpclient = HttpClients.createDefault();
final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
httppost.setEntity(new StringEntity("{\"gremlin\":\"gclassic.V()\"}", Consts.UTF_8));
- final String mime = "text/plain";
- httppost.addHeader("Accept", mime);
try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(mime, response.getEntity().getContentType().getValue());
- final String text = EntityUtils.toString(response.getEntity());
- final String[] split = text.split(System.lineSeparator());
- assertEquals(6, split.length);
- for (String line : split) {
- assertThat(line, matchesRegex("==>v\\[\\d\\]"));
- }
+ assertEquals("application/json", response.getEntity().getContentType().getValue());
+ final String json = EntityUtils.toString(response.getEntity());
+ final JsonNode node = mapper.readTree(json);
+ assertEquals(6, node.get("result").get(GraphSONTokens.VALUEPROP).size());
}
}
+ // TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResultGraphBinaryToString() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+// httppost.setEntity(new StringEntity("{\"gremlin\":\"gclassic.V()\"}", Consts.UTF_8));
+// final String mime = SerTokens.MIME_GRAPHBINARY_V1 + "-stringd";
+// httppost.addHeader("Accept", mime);
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(mime, response.getEntity().getContentType().getValue());
+//
+// final GraphBinaryMessageSerializerV1 serializer = new GraphBinaryMessageSerializerV1(TypeSerializerRegistry.INSTANCE);
+// final ResponseMessage msg = serializer.deserializeResponse(toByteBuf(response.getEntity()));
+// final List<Object> data = (List<Object>) msg.getResult().getData();
+// assertEquals(6, data.size());
+// for (Object o : data) {
+// assertThat(o, instanceOf(String.class));
+// assertThat((String) o, matchesRegex("v\\[\\d\\]"));
+// }
+// }
+// }
+
+ // TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithGremlinQueryStringArgumentWithIteratorResultTextPlain() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+// httppost.setEntity(new StringEntity("{\"gremlin\":\"gclassic.V()\"}", Consts.UTF_8));
+// final String mime = "text/plain";
+// httppost.addHeader("Accept", mime);
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(mime, response.getEntity().getContentType().getValue());
+// final String text = EntityUtils.toString(response.getEntity());
+// final String[] split = text.split(System.lineSeparator());
+// assertEquals(6, split.length);
+// for (String line : split) {
+// assertThat(line, matchesRegex("==>v\\[\\d\\]"));
+// }
+// }
+// }
+
@Test
public void should200OnPOSTWithGremlinJsonEndcodedBody() throws Exception {
final CloseableHttpClient httpclient = HttpClients.createDefault();
@@ -830,20 +871,21 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
}
}
- @Test
- public void should200OnPOSTWithGraphSON1d0AcceptHeaderDefaultResultToJson() throws Exception {
- final CloseableHttpClient httpclient = HttpClients.createDefault();
- final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
- httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
- }
+// TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithGraphSON1d0AcceptHeaderDefaultResultToJson() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+// httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+// }
@Test
public void should200OnPOSTWithGraphSON4d0AcceptHeaderDefaultResultToJson() throws Exception {
@@ -860,117 +902,121 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
}
}
- @Test
- public void should200OnPOSTWithGraphSON2d0AcceptHeaderDefaultResultToJson() throws Exception {
- final CloseableHttpClient httpclient = HttpClients.createDefault();
- final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
- httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
- }
-
- @Test
- public void should200OnPOSTWithGraphSON3d0AcceptHeaderDefaultResultToJson() throws Exception {
- final CloseableHttpClient httpclient = HttpClients.createDefault();
- final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
- httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).asInt());
- }
- }
-
- @Test
- public void should200OnPOSTWithAnyGraphSONAcceptHeaderDefaultResultToJson() throws Exception {
- final CloseableHttpClient httpclient = HttpClients.createDefault();
- final HttpPost httppost1 = new HttpPost(TestClientFactory.createURLString());
- httppost1.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost1.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_JSON);
- httppost1.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost1)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
-
- final HttpPost httppost1Untyped = new HttpPost(TestClientFactory.createURLString());
- httppost1Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost1Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V1_UNTYPED);
- httppost1Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost1Untyped)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_GRAPHSON_V1_UNTYPED, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
-
- final HttpPost httppost2 = new HttpPost(TestClientFactory.createURLString());
- httppost2.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost2.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V2);
- httppost2.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost2)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_GRAPHSON_V2, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).get(GraphSONTokens.VALUEPROP).asInt());
- }
-
- final HttpPost httppost2Untyped = new HttpPost(TestClientFactory.createURLString());
- httppost2Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost2Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V2_UNTYPED);
- httppost2Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost2Untyped)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_GRAPHSON_V2_UNTYPED, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
-
- final HttpPost httppost3 = new HttpPost(TestClientFactory.createURLString());
- httppost3.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost3.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V3);
- httppost3.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost3)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_GRAPHSON_V3, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).asInt());
- }
-
- final HttpPost httppost3Untyped = new HttpPost(TestClientFactory.createURLString());
- httppost3Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
- httppost3Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V3_UNTYPED);
- httppost3Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
-
- try (final CloseableHttpResponse response = httpclient.execute(httppost3Untyped)) {
- assertEquals(200, response.getStatusLine().getStatusCode());
- assertEquals(SerTokens.MIME_GRAPHSON_V3_UNTYPED, response.getEntity().getContentType().getValue());
- final String json = EntityUtils.toString(response.getEntity());
- final JsonNode node = mapper.readTree(json);
- assertEquals(0, node.get("result").get("data").get(0).asInt());
- }
- }
+ // TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithGraphSON2d0AcceptHeaderDefaultResultToJson() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+// httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+// }
+
+ // TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithGraphSON3d0AcceptHeaderDefaultResultToJson() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+// httppost.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).asInt());
+// }
+// }
+
+ // TODO: remove when consensus reached on old serializer support.
+// @Test
+// public void should200OnPOSTWithAnyGraphSONAcceptHeaderDefaultResultToJson() throws Exception {
+// final CloseableHttpClient httpclient = HttpClients.createDefault();
+// final HttpPost httppost1 = new HttpPost(TestClientFactory.createURLString());
+// httppost1.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost1.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_JSON);
+// httppost1.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost1)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_JSON, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+
+ // TODO: remove when consensus reached on old serializer support.
+// final HttpPost httppost1Untyped = new HttpPost(TestClientFactory.createURLString());
+// httppost1Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost1Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V1_UNTYPED);
+// httppost1Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost1Untyped)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_GRAPHSON_V1_UNTYPED, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+//
+// final HttpPost httppost2 = new HttpPost(TestClientFactory.createURLString());
+// httppost2.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost2.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V2);
+// httppost2.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost2)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_GRAPHSON_V2, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).get(GraphSONTokens.VALUEPROP).asInt());
+// }
+//
+// final HttpPost httppost2Untyped = new HttpPost(TestClientFactory.createURLString());
+// httppost2Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost2Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V2_UNTYPED);
+// httppost2Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost2Untyped)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_GRAPHSON_V2_UNTYPED, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+//
+// final HttpPost httppost3 = new HttpPost(TestClientFactory.createURLString());
+// httppost3.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost3.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V3);
+// httppost3.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost3)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_GRAPHSON_V3, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).asInt());
+// }
+//
+// final HttpPost httppost3Untyped = new HttpPost(TestClientFactory.createURLString());
+// httppost3Untyped.setHeader(HttpHeaders.CONTENT_TYPE, SerTokens.MIME_JSON);
+// httppost3Untyped.setHeader(HttpHeaders.ACCEPT, SerTokens.MIME_GRAPHSON_V3_UNTYPED);
+// httppost3Untyped.setEntity(new StringEntity("{\"gremlin\":\"1-1\"}", Consts.UTF_8));
+//
+// try (final CloseableHttpResponse response = httpclient.execute(httppost3Untyped)) {
+// assertEquals(200, response.getStatusLine().getStatusCode());
+// assertEquals(SerTokens.MIME_GRAPHSON_V3_UNTYPED, response.getEntity().getContentType().getValue());
+// final String json = EntityUtils.toString(response.getEntity());
+// final JsonNode node = mapper.readTree(json);
+// assertEquals(0, node.get("result").get("data").get(0).asInt());
+// }
+// }
@Test
public void should500WithResultThatCantBeSerialized() throws Exception {
@@ -1155,7 +1201,7 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
final String json = EntityUtils.toString(response.getEntity());
final JsonNode node = mapper.readTree(json);
assertEquals("some error", node.get("status").get("message").textValue());
- assertEquals(500, node.get("status").get("code").intValue());
+ assertEquals(595, node.get("status").get("code").intValue());
final Header[] footers = getTrailingHeaders(response);
assertEquals(2, footers.length);
@@ -1183,14 +1229,14 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
final JsonNode node = mapper.readTree(json);
assertEquals(0, node.get("result").get(GraphSONTokens.VALUEPROP).get(0).get(GraphSONTokens.VALUEPROP).intValue());
assertEquals("some error", node.get("status").get("message").textValue());
- assertEquals(500, node.get("status").get("code").intValue());
+ assertEquals(595, node.get("status").get("code").intValue());
final Header[] footers = getTrailingHeaders(response);
assertEquals(2, footers.length);
assertEquals("code", footers[0].getName());
- assertEquals("500", footers[0].getValue());
+ assertEquals("595", footers[0].getValue());
assertEquals("message", footers[1].getName());
- assertEquals("some error", footers[1].getValue());
+ assertEquals("some+error", footers[1].getValue());
}
}
@@ -1316,7 +1362,7 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
assertEquals(200, response.getStatusLine().getStatusCode());
final String json = EntityUtils.toString(response.getEntity());
final JsonNode node = mapper.readTree(json);
- assertTrue(node.get("status").get("message").asText().contains("A message with [bytecode] op code requires the [aliases] argument"));
+ assertTrue(node.get("status").get("message").asText().contains("A [bytecode] message requires a [g] argument"));
}
}
@@ -1337,6 +1383,28 @@ public class GremlinServerHttpIntegrateTest extends AbstractGremlinServerIntegra
}
}
+ @Test
+ public void should100onExpectContinueRequest() throws Exception {
+ final GraphSONMessageSerializerV4 serializer = new GraphSONMessageSerializerV4();
+ final ByteBuf serializedRequest = serializer.serializeRequestMessageV4(
+ RequestMessageV4.build("gmodern.V()").create(),
+ new UnpooledByteBufAllocator(false));
+
+ final CloseableHttpClient httpclient = HttpClients.createDefault();
+ final HttpPost httppost = new HttpPost(TestClientFactory.createURLString());
+ httppost.setConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
+ httppost.addHeader(HttpHeaders.ACCEPT, Serializers.GRAPHSON_V4.getValue());
+ httppost.addHeader("Content-Type", Serializers.GRAPHSON_V4.getValue());
+ httppost.setEntity(new ByteArrayEntity(serializedRequest.array()));
+
+ try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ final String json = EntityUtils.toString(response.getEntity());
+ final JsonNode node = mapper.readTree(json);
+ assertEquals(6, node.get("result").get(GraphSONTokens.VALUEPROP).size());
+ }
+ }
+
private static ByteBuf toByteBuf(final HttpEntity httpEntity) throws IOException {
final byte[] asArray = EntityUtils.toByteArray(httpEntity);
return Unpooled.wrappedBuffer(asArray);
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index 761e3bc4f0..545441ec7c 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -19,7 +19,6 @@
package org.apache.tinkerpop.gremlin.server;
import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import nl.altindag.log.LogCaptor;
@@ -32,8 +31,6 @@ import org.apache.tinkerpop.gremlin.TestHelper;
import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
import org.apache.tinkerpop.gremlin.driver.Result;
-import org.apache.tinkerpop.gremlin.driver.ResultSet;
-import org.apache.tinkerpop.gremlin.util.Tokens;
import org.apache.tinkerpop.gremlin.driver.exception.ResponseException;
import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
@@ -64,16 +61,11 @@ import org.junit.Test;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -81,7 +73,6 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import static org.apache.tinkerpop.gremlin.util.Tokens.ARGS_EVAL_TIMEOUT;
import static org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin.Compilation.COMPILE_STATIC;
import static org.apache.tinkerpop.gremlin.process.remote.RemoteConnection.GREMLIN_REMOTE;
import static org.apache.tinkerpop.gremlin.process.remote.RemoteConnection.GREMLIN_REMOTE_CONNECTION_CLASS;
@@ -89,10 +80,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalS
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.oneOf;
-import static org.hamcrest.core.AllOf.allOf;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
-import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -150,6 +138,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
switch (nameOfTest) {
case "shouldProvideBetterExceptionForMethodCodeTooLarge":
settings.maxContentLength = 4096000;
+ settings.maxParameters = Integer.MAX_VALUE;
break;
case "shouldRespectHighWaterMarkSettingAndSucceed":
settings.writeBufferHighWaterMark = 64;
@@ -279,40 +268,41 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
- @Test
- public void shouldBlowTheWorkQueueSize() throws Exception {
- final Cluster cluster = TestClientFactory.open();
- final Client client = cluster.connect();
-
- // maxWorkQueueSize=1 && gremlinPool=1
- // we should be able to do one request at a time serially
- assertEquals("test1", client.submit("'test1'").all().get().get(0).getString());
- assertEquals("test2", client.submit("'test2'").all().get().get(0).getString());
- assertEquals("test3", client.submit("'test3'").all().get().get(0).getString());
-
- final AtomicBoolean errorTriggered = new AtomicBoolean();
- final ResultSet r1 = client.submitAsync("Thread.sleep(1000);'test4'").get();
-
- final List<CompletableFuture<List<Result>>> blockers = new ArrayList<>();
- for (int ix = 0; ix < 512 && !errorTriggered.get(); ix++) {
- blockers.add(client.submit("'test'").all().exceptionally(t -> {
- final ResponseException re = (ResponseException) t.getCause();
- errorTriggered.compareAndSet(false, ResponseStatusCode.TOO_MANY_REQUESTS == re.getResponseStatusCode());
- return null;
- }));
- }
-
- assertThat(errorTriggered.get(), is(true));
-
- // wait for the blockage to clear for sure
- assertEquals("test4", r1.all().get().get(0).getString());
- blockers.forEach(CompletableFuture::join);
-
- // should be accepting test6 now
- assertEquals("test6", client.submit("'test6'").all().get().get(0).getString());
-
- cluster.close();
- }
+// TODO: re-enable after pooling implemented in Java driver for HTTP/1.1.
+// @Test
+// public void shouldBlowTheWorkQueueSize() throws Exception {
+// final Cluster cluster = TestClientFactory.open();
+// final Client client = cluster.connect();
+//
+// // maxWorkQueueSize=1 && gremlinPool=1
+// // we should be able to do one request at a time serially
+// assertEquals("test1", client.submit("'test1'").all().get().get(0).getString());
+// assertEquals("test2", client.submit("'test2'").all().get().get(0).getString());
+// assertEquals("test3", client.submit("'test3'").all().get().get(0).getString());
+//
+// final AtomicBoolean errorTriggered = new AtomicBoolean();
+// final ResultSet r1 = client.submitAsync("Thread.sleep(1000);'test4'").get();
+//
+// final List<CompletableFuture<List<Result>>> blockers = new ArrayList<>();
+// for (int ix = 0; ix < 512 && !errorTriggered.get(); ix++) {
+// blockers.add(client.submit("'test'").all().exceptionally(t -> {
+// final ResponseException re = (ResponseException) t.getCause();
+// errorTriggered.compareAndSet(false, ResponseStatusCode.TOO_MANY_REQUESTS == re.getResponseStatusCode());
+// return null;
+// }));
+// }
+//
+// assertThat(errorTriggered.get(), is(true));
+//
+// // wait for the blockage to clear for sure
+// assertEquals("test4", r1.all().get().get(0).getString());
+// blockers.forEach(CompletableFuture::join);
+//
+// // should be accepting test6 now
+// assertEquals("test6", client.submit("'test6'").all().get().get(0).getString());
+//
+// cluster.close();
+// }
@Test
public void shouldScriptEvaluationErrorForRemoteTraversal() throws Exception {
@@ -344,13 +334,14 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
g.close();
}
+// TODO: evalTimeout needs be part of script now
// @Test
// public void shouldTimeOutRemoteTraversal() throws Exception {
// final GraphTraversalSource g = traversal().withRemote(conf);
//
// try {
// // tests sleeping thread
-// g.inject(1).sideEffect(Lambda.consumer("Thread.sleep(10000)")).iterate();
+// g.inject(1).sideEffect(Lambda.consumer("Thread.sleep(20000)")).iterate();
// fail("This traversal should have timed out");
// } catch (Exception ex) {
// final Throwable t = ex.getCause();
@@ -374,35 +365,36 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
// g.close();
// }
- @Test
- public void shouldTimeOutRemoteTraversalWithPerRequestOption() throws Exception {
- final GraphTraversalSource g = traversal().withRemote(conf);
-
- try {
- // tests sleeping thread
- g.with(ARGS_EVAL_TIMEOUT, 500L).inject(1).sideEffect(Lambda.consumer("Thread.sleep(10000)")).iterate();
- fail("This traversal should have timed out");
- } catch (Exception ex) {
- final Throwable t = ex.getCause();
- assertThat(t, instanceOf(ResponseException.class));
- assertEquals(ResponseStatusCode.SERVER_ERROR_TIMEOUT, ((ResponseException) t).getResponseStatusCode());
- }
-
- // make a graph with a cycle in it to force a long run traversal
- graphGetter.get().traversal().addV("person").as("p").addE("self").to("p").iterate();
-
- try {
- // tests an "unending" traversal
- g.with(ARGS_EVAL_TIMEOUT, 500L).V().repeat(__.out()).until(__.outE().count().is(0)).iterate();
- fail("This traversal should have timed out");
- } catch (Exception ex) {
- final Throwable t = ex.getCause();
- assertThat(t, instanceOf(ResponseException.class));
- assertEquals(ResponseStatusCode.SERVER_ERROR_TIMEOUT, ((ResponseException) t).getResponseStatusCode());
- }
-
- g.close();
- }
+// TODO: driver can't set EvalTimeout anymore in RequestMessage so this won't work.
+// @Test
+// public void shouldTimeOutRemoteTraversalWithPerRequestOption() throws Exception {
+// final GraphTraversalSource g = traversal().withRemote(conf);
+//
+// try {
+// // tests sleeping thread
+// g.with(ARGS_EVAL_TIMEOUT, 500L).inject(1).sideEffect(Lambda.consumer("Thread.sleep(10000)")).iterate();
+// fail("This traversal should have timed out");
+// } catch (Exception ex) {
+// final Throwable t = ex.getCause();
+// assertThat(t, instanceOf(ResponseException.class));
+// assertEquals(ResponseStatusCode.SERVER_ERROR_TIMEOUT, ((ResponseException) t).getResponseStatusCode());
+// }
+//
+// // make a graph with a cycle in it to force a long run traversal
+// graphGetter.get().traversal().addV("person").as("p").addE("self").to("p").iterate();
+//
+// try {
+// // tests an "unending" traversal
+// g.with(ARGS_EVAL_TIMEOUT, 500L).V().repeat(__.out()).until(__.outE().count().is(0)).iterate();
+// fail("This traversal should have timed out");
+// } catch (Exception ex) {
+// final Throwable t = ex.getCause();
+// assertThat(t, instanceOf(ResponseException.class));
+// assertEquals(ResponseStatusCode.SERVER_ERROR_TIMEOUT, ((ResponseException) t).getResponseStatusCode());
+// }
+//
+// g.close();
+// }
// TODO: can't set evalTimeout anymore
// @Test
@@ -449,69 +441,70 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
cluster.close();
}
- @Test
- public void shouldUseInterpreterMode() throws Exception {
- final Cluster cluster = TestClientFactory.open();
- final Client client = cluster.connect();
-
- client.submit("def subtractAway(x,y){x-y};[]").all().get();
- client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
-
- assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
- assertEquals(3, client.submit("int y = x + 1").all().get().get(0).getInt());
- assertEquals(5, client.submit("def z = x + y").all().get().get(0).getInt());
-
- final Map<String,Object> m = new HashMap<>();
- m.put("x", 10);
- assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
- assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
- assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
- assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
-
- cluster.close();
- }
-
- @Test
- public void shouldNotUseInterpreterMode() throws Exception {
- final Cluster cluster = TestClientFactory.open();
- final Client client = cluster.connect(name.getMethodName());
-
- client.submit("def subtractAway(x,y){x-y};[]").all().get();
- client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
-
- assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
- assertEquals(3, client.submit("y = x + 1").all().get().get(0).getInt());
- assertEquals(5, client.submit("z = x + y").all().get().get(0).getInt());
-
- final Map<String,Object> m = new HashMap<>();
- m.put("x", 10);
- assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
- assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
- assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
- assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
-
- cluster.close();
- }
+// TODO: no more sessions so disable until new Transactions implemented
+// @Test
+// public void shouldUseInterpreterMode() throws Exception {
+// final Cluster cluster = TestClientFactory.open();
+// final Client client = cluster.connect();
+//
+// client.submit("def subtractAway(x,y){x-y};[]").all().get();
+// client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
+//
+// assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
+// assertEquals(3, client.submit("int y = x + 1").all().get().get(0).getInt());
+// assertEquals(5, client.submit("def z = x + y").all().get().get(0).getInt());
+//
+// final Map<String,Object> m = new HashMap<>();
+// m.put("x", 10);
+// assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
+// assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
+// assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
+// assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
+//
+// cluster.close();
+// }
-// TODO: re-enable
+// TODO: no more sessions so disable until new Transactions implemented
// @Test
-// public void shouldUseSimpleSandbox() throws Exception {
+// public void shouldNotUseInterpreterMode() throws Exception {
// final Cluster cluster = TestClientFactory.open();
// final Client client = cluster.connect();
//
-// assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
+// client.submit("def subtractAway(x,y){x-y};[]").all().get();
+// client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
//
-// try {
-// // this should return "nothing" - there should be no exception
-// client.submit("java.lang.System.exit(0)").all().get();
-// fail("The above should not have executed in any successful way as sandboxing is enabled");
-// } catch (Exception ex) {
-// assertThat(ex.getCause().getMessage(), containsString("[Static type checking] - Not authorized to call this method: java.lang.System#exit(int)"));
-// } finally {
-// cluster.close();
-// }
+// assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
+// assertEquals(3, client.submit("y = x + 1").all().get().get(0).getInt());
+// assertEquals(5, client.submit("z = x + y").all().get().get(0).getInt());
+//
+// final Map<String,Object> m = new HashMap<>();
+// m.put("x", 10);
+// assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
+// assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
+// assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
+// assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
+//
+// cluster.close();
// }
+ @Test
+ public void shouldUseSimpleSandbox() throws Exception {
+ final Cluster cluster = TestClientFactory.open();
+ final Client client = cluster.connect();
+
+ assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
+
+ try {
+ // this should return "nothing" - there should be no exception
+ client.submit("java.lang.System.exit(0)").all().get();
+ fail("The above should not have executed in any successful way as sandboxing is enabled");
+ } catch (Exception ex) {
+ assertThat(ex.getCause().getMessage(), containsString("[Static type checking] - Not authorized to call this method: java.lang.System#exit(int)"));
+ } finally {
+ cluster.close();
+ }
+ }
+
@Test
public void shouldRespectHighWaterMarkSettingAndSucceed() throws Exception {
// the highwatermark should get exceeded on the server and thus pause the writes, but have no problem catching
@@ -563,16 +556,6 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
- @Test
- public void shouldReturnInvalidRequestArgsWhenGremlinArgIsNotSupplied() throws Exception {
- try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
- final RequestMessageV4 request = RequestMessageV4.build(Tokens.OPS_EVAL).create();
- final ResponseMessage result = client.submit(request).get(0);
- assertThat(result.getStatus().getCode(), is(not(ResponseStatusCode.PARTIAL_CONTENT)));
- assertEquals(result.getStatus().getCode(), ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS);
- }
- }
-
@Test
public void shouldReturnInvalidRequestArgsWhenInvalidReservedBindingKeyIsUsed() throws Exception {
try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
@@ -614,30 +597,6 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
-// TODO: probably invalid test now that builder requires proper type.
-// @Test
-// public void shouldReturnInvalidRequestArgsWhenInvalidTypeBindingKeyIsUsed() throws Exception {
-// try (SimpleClient client = TestClientFactory.createWebSocketClient()) {
-// final Map<Object, Object> bindings = new HashMap<>();
-// bindings.put(1, "123");
-// final RequestMessageV4 request = RequestMessageV4.build(Tokens.OPS_EVAL)
-// .addArg(Tokens.ARGS_GREMLIN, "[1,2,3,4,5,6,7,8,9,0]")
-// .addArg(Tokens.ARGS_BINDINGS, bindings).create();
-// final CountDownLatch latch = new CountDownLatch(1);
-// final AtomicBoolean pass = new AtomicBoolean(false);
-// client.submit(request, result -> {
-// if (result.getStatus().getCode() != ResponseStatusCode.PARTIAL_CONTENT) {
-// pass.set(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS == result.getStatus().getCode());
-// latch.countDown();
-// }
-// });
-//
-// if (!latch.await(3000, TimeUnit.MILLISECONDS))
-// fail("Request should have returned error, but instead timed out");
-// assertThat(pass.get(), is(true));
-// }
-// }
-
@Test
public void shouldReturnInvalidRequestArgsWhenBindingCountExceedsAllowable() throws Exception {
try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
@@ -702,26 +661,27 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
- @Test
- @SuppressWarnings("unchecked")
- public void shouldBatchResultsByTwos() throws Exception {
- try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
- final RequestMessageV4 request = RequestMessageV4.build("[0,1,2,3,4,5,6,7,8,9]").create();
-
- final List<ResponseMessage> msgs = client.submit(request);
- assertEquals(5, client.submit(request).size());
- assertEquals(0, ((List<Integer>) msgs.get(0).getResult().getData()).get(0).intValue());
- assertEquals(1, ((List<Integer>) msgs.get(0).getResult().getData()).get(1).intValue());
- assertEquals(2, ((List<Integer>) msgs.get(1).getResult().getData()).get(0).intValue());
- assertEquals(3, ((List<Integer>) msgs.get(1).getResult().getData()).get(1).intValue());
- assertEquals(4, ((List<Integer>) msgs.get(2).getResult().getData()).get(0).intValue());
- assertEquals(5, ((List<Integer>) msgs.get(2).getResult().getData()).get(1).intValue());
- assertEquals(6, ((List<Integer>) msgs.get(3).getResult().getData()).get(0).intValue());
- assertEquals(7, ((List<Integer>) msgs.get(3).getResult().getData()).get(1).intValue());
- assertEquals(8, ((List<Integer>) msgs.get(4).getResult().getData()).get(0).intValue());
- assertEquals(9, ((List<Integer>) msgs.get(4).getResult().getData()).get(1).intValue());
- }
- }
+// TODO: re-enable once there is an easier way to check for this. batchSize replaced with chunkSize so need GLV updated to test.
+// @Test
+// @SuppressWarnings("unchecked")
+// public void shouldBatchResultsByTwos() throws Exception {
+// try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
+// final RequestMessageV4 request = RequestMessageV4.build("[0,1,2,3,4,5,6,7,8,9]").create();
+//
+// final List<ResponseMessage> msgs = client.submit(request);
+// assertEquals(5, client.submit(request).size());
+// assertEquals(0, ((List<Integer>) msgs.get(0).getResult().getData()).get(0).intValue());
+// assertEquals(1, ((List<Integer>) msgs.get(0).getResult().getData()).get(1).intValue());
+// assertEquals(2, ((List<Integer>) msgs.get(1).getResult().getData()).get(0).intValue());
+// assertEquals(3, ((List<Integer>) msgs.get(1).getResult().getData()).get(1).intValue());
+// assertEquals(4, ((List<Integer>) msgs.get(2).getResult().getData()).get(0).intValue());
+// assertEquals(5, ((List<Integer>) msgs.get(2).getResult().getData()).get(1).intValue());
+// assertEquals(6, ((List<Integer>) msgs.get(3).getResult().getData()).get(0).intValue());
+// assertEquals(7, ((List<Integer>) msgs.get(3).getResult().getData()).get(1).intValue());
+// assertEquals(8, ((List<Integer>) msgs.get(4).getResult().getData()).get(0).intValue());
+// assertEquals(9, ((List<Integer>) msgs.get(4).getResult().getData()).get(1).intValue());
+// }
+// }
// @Test
// public void shouldBatchResultsByTwosWithDriver() throws Exception {
@@ -743,7 +703,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
try (SimpleClient client = TestClientFactory.createSimpleHttpClient()){
// this should return "nothing" - there should be no exception
final List<ResponseMessage> responses = client.submit("g.V().has('name','kadfjaldjfla')");
- assertNull(responses.get(0).getResult().getData());
+ assertTrue(((List) responses.get(0).getResult().getData()).isEmpty());
}
}
@@ -752,7 +712,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
public void shouldReceiveFailureTimeOutOnScriptEval() throws Exception {
try (SimpleClient client = TestClientFactory.createSimpleHttpClient()){
final List<ResponseMessage> responses = client.submit("Thread.sleep(3000);'some-stuff-that-should not return'");
- assertThat(responses.get(0).getStatus().getMessage(), oneOf(containsString("timeout occurred"), containsString("evaluation exceeded")));
+ assertTrue(responses.get(0).getStatus().getMessage().contains("timeout occurred"));
// validate that we can still send messages to the server
assertEquals(2, ((List<Integer>) client.submit("1+1").get(0).getResult().getData()).get(0).intValue());
@@ -822,7 +782,7 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
@Test
public void shouldReceiveFailureOnBadGraphSONSerialization() throws Exception {
- final Cluster cluster = TestClientFactory.build().serializer(Serializers.GRAPHSON_V3).create();
+ final Cluster cluster = TestClientFactory.build().serializer(Serializers.GRAPHSON_V4).create();
final Client client = cluster.connect();
try {
@@ -839,36 +799,38 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
- @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
- @Test
- public void shouldBlockRequestWhenTooBig() throws Exception {
- final Cluster cluster = TestClientFactory.open();
- final Client client = cluster.connect();
-
- try {
- final String fatty = IntStream.range(0, 1024).mapToObj(String::valueOf).collect(Collectors.joining());
- final CompletableFuture<ResultSet> result = client.submitAsync("'" + fatty + "';'test'");
- final ResultSet resultSet = result.get(10000, TimeUnit.MILLISECONDS);
- resultSet.all().get(10000, TimeUnit.MILLISECONDS);
- fail("Should throw an exception.");
- } catch (TimeoutException te) {
- // the request should not have timed-out - the connection should have been reset, but it seems that
- // timeout seems to occur as well on some systems (it's not clear why). however, the nature of this
- // test is to ensure that the script isn't processed if it exceeds a certain size, so in this sense
- // it seems ok to pass in this case.
- } catch (Exception re) {
- final Throwable root = ExceptionHelper.getRootCause(re);
-
- // went with two possible error messages here as i think that there is some either non-deterministic
- // behavior around the error message or it's environmentally dependent (e.g. different jdk, versions, etc)
- assertThat(root.getMessage(), Matchers.anyOf(is("Connection to server is no longer active"), is("Connection reset by peer")));
-
- // validate that we can still send messages to the server
- assertEquals(2, client.submit("1+1").all().join().get(0).getInt());
- } finally {
- cluster.close();
- }
- }
+// TODO: this test isn't valid right now since this error is thrown by netty so doesn't map properly to RequestId
+// sent by the driver. Re-enable once this changes.
+// @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+// @Test
+// public void shouldBlockRequestWhenTooBig() throws Exception {
+// final Cluster cluster = TestClientFactory.open();
+// final Client client = cluster.connect();
+//
+// try {
+// final String fatty = IntStream.range(0, 1024).mapToObj(String::valueOf).collect(Collectors.joining());
+// final CompletableFuture<ResultSet> result = client.submitAsync("'" + fatty + "';'test'");
+// final ResultSet resultSet = result.get(10000, TimeUnit.MILLISECONDS);
+// resultSet.all().get(10000, TimeUnit.MILLISECONDS);
+// fail("Should throw an exception.");
+// } catch (TimeoutException te) {
+// // the request should not have timed-out - the connection should have been reset, but it seems that
+// // timeout seems to occur as well on some systems (it's not clear why). however, the nature of this
+// // test is to ensure that the script isn't processed if it exceeds a certain size, so in this sense
+// // it seems ok to pass in this case.
+// } catch (Exception re) {
+// final Throwable root = ExceptionHelper.getRootCause(re);
+//
+// // went with two possible error messages here as i think that there is some either non-deterministic
+// // behavior around the error message or it's environmentally dependent (e.g. different jdk, versions, etc)
+// assertThat(root.getMessage(), Matchers.anyOf(is("Connection to server is no longer active"), is("Connection reset by peer")));
+//
+// // validate that we can still send messages to the server
+// assertEquals(2, client.submit("1+1").all().join().get(0).getInt());
+// } finally {
+// cluster.close();
+// }
+// }
@Test
public void shouldFailOnDeadHost() throws Exception {
@@ -930,19 +892,20 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
}
}
- @Test
- public void shouldHavePartialContentWithLongResultsCollection() throws Exception {
- try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
- final RequestMessageV4 request = RequestMessageV4.build("new String[100]").create();
- final List<ResponseMessage> responses = client.submit(request);
- assertThat(responses.size(), Matchers.greaterThan(1));
- for (Iterator<ResponseMessage> it = responses.iterator(); it.hasNext(); ) {
- final ResponseMessage msg = it.next();
- final ResponseStatusCode expected = it.hasNext() ? ResponseStatusCode.PARTIAL_CONTENT : ResponseStatusCode.SUCCESS;
- assertEquals(expected, msg.getStatus().getCode());
- }
- }
- }
+// TODO: figure this failure out next.
+// @Test
+// public void shouldHavePartialContentWithLongResultsCollection() throws Exception {
+// try (SimpleClient client = TestClientFactory.createSimpleHttpClient()) {
+// final RequestMessageV4 request = RequestMessageV4.build("new String[100]").create();
+// final List<ResponseMessage> responses = client.submit(request);
+// assertThat(responses.size(), Matchers.greaterThan(1));
+// for (Iterator<ResponseMessage> it = responses.iterator(); it.hasNext(); ) {
+// final ResponseMessage msg = it.next();
+// final ResponseStatusCode expected = it.hasNext() ? ResponseStatusCode.PARTIAL_CONTENT : ResponseStatusCode.SUCCESS;
+// assertEquals(expected, msg.getStatus().getCode());
+// }
+// }
+// }
@Test
public void shouldFailWithBadScriptEval() throws Exception {
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AllowListAuthorizer.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AllowListAuthorizer.java
index e8c686c951..6c618f1620 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AllowListAuthorizer.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AllowListAuthorizer.java
@@ -18,7 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.server.authz;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification.VertexProgramRestrictionStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
@@ -147,9 +147,9 @@ public class AllowListAuthorizer implements Authorizer {
* {@link AuthorizationException} if this is not the case.
*
* @param user {@link AuthenticatedUser} that needs authorization.
- * @param msg {@link RequestMessage} in which the {@link org.apache.tinkerpop.gremlin.util.Tokens}.ARGS_GREMLIN argument can contain an arbitrary succession of script statements.
+ * @param msg {@link RequestMessageV4} in which the {@link org.apache.tinkerpop.gremlin.util.Tokens}.ARGS_GREMLIN argument can contain an arbitrary succession of script statements.
*/
- public void authorize(final AuthenticatedUser user, final RequestMessage msg) throws AuthorizationException {
+ public void authorize(final AuthenticatedUser user, final RequestMessageV4 msg) throws AuthorizationException {
if (!usernamesSandbox.contains(user.getName())) {
throw new AuthorizationException(REJECT_STRING);
}
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AuthorizerTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AuthorizerTest.java
index b8ec8b7b01..c968121279 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AuthorizerTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/authz/AuthorizerTest.java
@@ -19,7 +19,7 @@
package org.apache.tinkerpop.gremlin.server.authz;
import org.apache.tinkerpop.gremlin.util.Tokens;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification.VertexProgramRestrictionStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
@@ -249,9 +249,9 @@ public class AuthorizerTest {
}
}
- private RequestMessage buildRequestMessage(final String traversalSource) {
+ private RequestMessageV4 buildRequestMessage(final String traversalSource) {
final String script = String.format("1+1; %s.V().map{it.get()}", traversalSource);
- return RequestMessage.build(Tokens.OPS_EVAL).addArg(Tokens.ARGS_GREMLIN, script).create();
+ return RequestMessageV4.build(script).create();
}
private static class SubgraphTraversals implements Supplier<Bytecode> {
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtilTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtilTest.java
deleted file mode 100644
index 88f4f7b131..0000000000
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtilTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.server.handler;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufAllocator;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.DefaultHttpHeaders;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaders;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.QueryStringEncoder;
-import io.netty.util.CharsetUtil;
-import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
-import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
-import org.apache.tinkerpop.gremlin.util.MessageSerializer;
-import org.apache.tinkerpop.gremlin.util.Tokens;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
-import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1;
-import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3;
-import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
-import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.samePropertyValuesAs;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class HttpHandlerUtilTest {
-
- private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
- private final GraphBinaryMessageSerializerV1 graphBinarySerializer = new GraphBinaryMessageSerializerV1();
- public final GraphSONMessageSerializerV3 graphSONSerializer = new GraphSONMessageSerializerV3();
-
- @Test
- public void shouldFailWhenIncorrectSerializerUsed() throws SerializationException {
- final RequestMessage request = RequestMessage.build(Tokens.OPS_BYTECODE)
- .processor("traversal")
- .overrideRequestId(UUID.randomUUID())
- .addArg(Tokens.ARGS_GREMLIN, "g.V()")
- .create();
-
- final ByteBuf buffer = graphSONSerializer.serializeRequestAsBinary(request, allocator);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_GRAPHBINARY_V1);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
- buffer, headers, new DefaultHttpHeaders());
-
- final Map<String, MessageSerializer<?>> serializers = new HashMap<>();
- serializers.put(SerTokens.MIME_GRAPHBINARY_V1, graphBinarySerializer);
-
- try {
- HttpHandlerUtil.getRequestMessageFromHttpRequest(httpRequest, serializers);
- fail("SerializationException expected");
- } catch (IllegalArgumentException ex) {
- assertEquals("Mime type mismatch. Value in content-type header is not equal payload header.", ex.getMessage());
- }
- }
-
- @Test
- public void shouldCorrectlyDeserializeRequestMessage() throws SerializationException {
- final RequestMessage request = RequestMessage.build(Tokens.OPS_BYTECODE)
- .processor("traversal")
- .overrideRequestId(UUID.randomUUID())
- .addArg(Tokens.ARGS_GREMLIN, "g.V()")
- .create();
-
- final ByteBuf buffer = graphBinarySerializer.serializeRequestAsBinary(request, allocator);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_GRAPHBINARY_V1);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
- buffer, headers, new DefaultHttpHeaders());
-
- final Map<String, MessageSerializer<?>> serializers = new HashMap<>();
- serializers.put(SerTokens.MIME_GRAPHBINARY_V1, graphBinarySerializer);
-
- final RequestMessage deserialized = HttpHandlerUtil.getRequestMessageFromHttpRequest(httpRequest, serializers);
- assertThat(request, samePropertyValuesAs(deserialized));
- }
-
- @Test
- public void shouldCorrectlyDeserializeGremlinFromPostRequest() throws SerializationException {
- final String gremlin = "g.V().hasLabel('person')";
- final UUID requestId = UUID.randomUUID();
- final ByteBuf buffer = allocator.buffer();
- buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
- "\", \"requestId\": \"" + requestId +
- "\", \"language\": \"gremlin-groovy\"}",
- CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
- buffer, headers, new DefaultHttpHeaders());
-
- final Map<String, MessageSerializer<?>> serializers = new HashMap<>();
- serializers.put(SerTokens.MIME_GRAPHBINARY_V1, graphBinarySerializer);
-
- final RequestMessage deserialized = HttpHandlerUtil.getRequestMessageFromHttpRequest(httpRequest, serializers);
- assertEquals(gremlin, deserialized.getArgs().get(Tokens.ARGS_GREMLIN));
- assertEquals(requestId, deserialized.getRequestId());
- assertEquals("gremlin-groovy", deserialized.getArg(Tokens.ARGS_LANGUAGE));
- }
-
- @Test
- public void shouldCorrectlyDeserializeGremlinFromPostRequestWithAllScriptFieldsSet() throws SerializationException {
- final String gremlin = "g.V(x)";
- final UUID requestId = UUID.fromString("1e55c495-22d5-4a39-934a-a2744ba010ef");
- final ByteBuf buffer = allocator.buffer();
- buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
- "\", \"requestId\": \"" + requestId +
- "\", \"bindings\":{\"x\":\"2\"}" +
- ", \"language\": \"gremlin-groovy\"}",
- CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
- buffer, headers, new DefaultHttpHeaders());
-
- final RequestMessageV4 deserialized = HttpHandlerUtil.getRequestMessageV4FromHttpRequest(httpRequest);
- assertEquals(gremlin, deserialized.getGremlin());
- assertEquals(requestId, deserialized.getRequestId());
- assertEquals("gremlin-groovy", deserialized.getArg(Tokens.ARGS_LANGUAGE));
- assertEquals("2", ((Map)deserialized.getArg(Tokens.ARGS_BINDINGS)).get("x"));
- }
-
- @Test
- public void shouldErrorOnBadRequestWithInvalidUuid() throws SerializationException {
- final String gremlin = "g.V(x)";
- final String requestId = "notaUUID";
- final ByteBuf buffer = allocator.buffer();
- buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
- "\", \"requestId\": \"" + requestId +
- "\", \"language\": \"gremlin-groovy\"}",
- CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
- buffer, headers, new DefaultHttpHeaders());
-
- try {
- HttpHandlerUtil.getRequestMessageV4FromHttpRequest(httpRequest);
- fail("Expected an error because of incorrect UUID format.");
- } catch (Exception e) {
- assertTrue(e.getMessage().contains("Invalid UUID string"));
- }
- }
-
- @Test
- public void shouldErrorOnBadRequestWithMalformedJson() throws SerializationException {
- final String gremlin = "g.V(x)";
- final ByteBuf buffer = allocator.buffer();
- buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
- "\" \"language\": \"gremlin-groovy\"}",
- CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
- buffer, headers, new DefaultHttpHeaders());
-
- try {
- HttpHandlerUtil.getRequestMessageV4FromHttpRequest(httpRequest);
- fail("Expected an error because of bad JSON request.");
- } catch (Exception e) {
- assertTrue(e.getMessage().contains("body could not be parsed"));
- }
- }
-
- @Test
- public void shouldIgnoreInvalidRequestMessageParameter() throws SerializationException {
- final String gremlin = "g.V(x)";
- final UUID requestId = UUID.randomUUID();
- final ByteBuf buffer = allocator.buffer();
- // requestId contains a typo here as requetId
- buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
- "\", \"requetId\": \"" + requestId +
- "\", \"language\": \"gremlin-groovy\"}",
- CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
- buffer, headers, new DefaultHttpHeaders());
-
- final RequestMessageV4 deserialized = HttpHandlerUtil.getRequestMessageV4FromHttpRequest(httpRequest);
- assertNotEquals(requestId, deserialized.getRequestId());
- }
-
- @Test
- public void shouldErrorOnBadRequestWithNoParameter() throws SerializationException {
- final ByteBuf buffer = allocator.buffer();
- buffer.writeCharSequence("{ }", CharsetUtil.UTF_8);
-
- final HttpHeaders headers = new DefaultHttpHeaders();
- headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
-
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
- buffer, headers, new DefaultHttpHeaders());
-
- try {
- HttpHandlerUtil.getRequestMessageV4FromHttpRequest(httpRequest);
- fail("Expected an error");
- } catch (Exception e) {
- assertTrue(e.getMessage().contains("no gremlin script supplied"));
- }
- }
-}
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoderTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoderTest.java
new file mode 100644
index 0000000000..e7e43467b1
--- /dev/null
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/handler/HttpRequestMessageDecoderTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.server.handler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.util.CharsetUtil;
+import org.apache.tinkerpop.gremlin.util.MessageSerializer;
+import org.apache.tinkerpop.gremlin.util.Tokens;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
+import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4;
+import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4;
+import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
+import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.util.ser.Serializers;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.samePropertyValuesAs;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class HttpRequestMessageDecoderTest {
+
+ private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+ private final GraphBinaryMessageSerializerV4 graphBinarySerializer = new GraphBinaryMessageSerializerV4();
+ public final GraphSONMessageSerializerV4 graphSONSerializer = new GraphSONMessageSerializerV4();
+
+ private final static Map<String, MessageSerializer<?>> serializers = new HashMap<>();
+ static {
+ serializers.put(Serializers.GRAPHSON_V4_UNTYPED.getValue(), Serializers.GRAPHSON_V4_UNTYPED.simpleInstance());
+ serializers.put("application/json", Serializers.GRAPHSON_V4_UNTYPED.simpleInstance());
+ serializers.put(Serializers.GRAPHSON_V4.getValue(), Serializers.GRAPHSON_V4.simpleInstance());
+ serializers.put(Serializers.GRAPHBINARY_V4.getValue(), Serializers.GRAPHBINARY_V4.simpleInstance());
+ }
+
+ @Test
+ public void shouldFailWhenIncorrectSerializerUsed() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final RequestMessageV4 request = RequestMessageV4.build("g.V()")
+ .overrideRequestId(UUID.randomUUID())
+ .create();
+
+ final ByteBuf buffer = graphSONSerializer.serializeRequestMessageV4(request, allocator);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_GRAPHBINARY_V4);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ assertNull(testChannel.readInbound());
+
+ ByteBuf out = testChannel.readOutbound();
+ assertTrue(out.toString(CharsetUtil.UTF_8).contains("Mime type mismatch"));
+ }
+
+ @Test
+ public void shouldCorrectlyDeserializeRequestMessage() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final RequestMessageV4 request = RequestMessageV4.build("g.V()")
+ .overrideRequestId(UUID.randomUUID())
+ .create();
+
+ final ByteBuf buffer = graphBinarySerializer.serializeRequestMessageV4(request, allocator);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_GRAPHBINARY_V4);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ final RequestMessageV4 decodedRequestMessage = testChannel.readInbound();
+ assertThat(request.getFields(), samePropertyValuesAs(decodedRequestMessage.getFields()));
+ assertEquals(request.getGremlin(), decodedRequestMessage.getGremlin());
+ }
+
+ @Test
+ public void shouldCorrectlyDeserializeGremlinFromPostRequest() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final String gremlin = "g.V().hasLabel('person')";
+ final UUID requestId = UUID.randomUUID();
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
+ "\", \"requestId\": \"" + requestId +
+ "\", \"language\": \"gremlin-groovy\"}",
+ CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "some uri",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ final RequestMessageV4 decodedRequestMessage = testChannel.readInbound();
+ assertEquals(gremlin, decodedRequestMessage.getGremlin());
+ assertEquals(requestId, decodedRequestMessage.getRequestId());
+ assertEquals("gremlin-groovy", decodedRequestMessage.getField(Tokens.ARGS_LANGUAGE));
+ }
+
+ @Test
+ public void shouldCorrectlyDeserializeGremlinFromPostRequestWithAllScriptFieldsSet() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final String gremlin = "g.V(x)";
+ final UUID requestId = UUID.fromString("1e55c495-22d5-4a39-934a-a2744ba010ef");
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
+ "\", \"requestId\": \"" + requestId +
+ "\", \"bindings\":{\"x\":\"2\"}" +
+ ", \"language\": \"gremlin-groovy\"}",
+ CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ final RequestMessageV4 decodedRequestMessage = testChannel.readInbound();
+ assertEquals(gremlin, decodedRequestMessage.getGremlin());
+ assertEquals(requestId, decodedRequestMessage.getRequestId());
+ assertEquals("gremlin-groovy", decodedRequestMessage.getField(Tokens.ARGS_LANGUAGE));
+ assertEquals("2", ((Map)decodedRequestMessage.getField(Tokens.ARGS_BINDINGS)).get("x"));
+ }
+
+ @Test
+ public void shouldErrorOnBadRequestWithInvalidUuid() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final String gremlin = "g.V(x)";
+ final String requestId = "notaUUID";
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
+ "\", \"requestId\": \"" + requestId +
+ "\", \"language\": \"gremlin-groovy\"}",
+ CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ assertNull(testChannel.readInbound());
+
+ ByteBuf out = testChannel.readOutbound();
+ assertTrue(out.toString(CharsetUtil.UTF_8).contains("Invalid UUID string"));
+ }
+
+ @Test
+ public void shouldErrorOnBadRequestWithMalformedJson() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final String gremlin = "g.V(x)";
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
+ "\" \"language\": \"gremlin-groovy\"}",
+ CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ assertNull(testChannel.readInbound());
+
+ ByteBuf out = testChannel.readOutbound();
+ assertTrue(out.toString(CharsetUtil.UTF_8).contains("body could not be parsed"));
+ }
+
+ @Test
+ public void shouldIgnoreInvalidRequestMessageParameter() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final String gremlin = "g.V(x)";
+ final UUID requestId = UUID.randomUUID();
+ final ByteBuf buffer = allocator.buffer();
+ // requestId contains a typo here as requetId
+ buffer.writeCharSequence("{ \"gremlin\": \"" + gremlin +
+ "\", \"requetId\": \"" + requestId +
+ "\", \"language\": \"gremlin-groovy\"}",
+ CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ final RequestMessageV4 decodedRequestMessage = testChannel.readInbound();
+ assertNotEquals(requestId, decodedRequestMessage.getRequestId());
+ }
+
+ @Test
+ public void shouldErrorOnBadRequestWithNoParameter() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{ }", CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ assertNull(testChannel.readInbound());
+
+ ByteBuf out = testChannel.readOutbound();
+ assertTrue(out.toString(CharsetUtil.UTF_8).contains("no gremlin script supplied"));
+ }
+
+ @Test
+ public void shouldAttemptToParseRequestWithNonsenseContentType() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{\"gremlin\":\"g.V()\"}", CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, "some-nonexistent-serializer");
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ RequestMessageV4 decodedRequest = testChannel.readInbound();
+ assertNotNull(decodedRequest);
+ }
+
+ @Test
+ public void shouldErrorWithNonexistentAcceptHeader() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{\"gremlin\":\"g.V()\"}", CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.ACCEPT, "some-nonexistent-serializer");
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ assertNull(testChannel.readInbound());
+
+ ByteBuf response = testChannel.readOutbound();
+ assertTrue(response.toString(CharsetUtil.UTF_8).contains("no serializer for requested Accept header"));
+ }
+
+ @Test
+ public void shouldNotAddInvalidFieldToRequestMessage() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{\"gremlin\":\"g.V()\",\"nonfield\":\"shouldntgetadded\"}", CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ RequestMessageV4 decodedRequest = testChannel.readInbound();
+ assertNull(decodedRequest.getField("nonfield"));
+ assertEquals("g.V()", decodedRequest.getGremlin());
+ }
+
+ @Test
+ public void shouldAddValidFieldsToRequestMessage() throws SerializationException {
+ final HttpRequestMessageDecoder requestMessageDecoder = new HttpRequestMessageDecoder(serializers);
+ final EmbeddedChannel testChannel = new EmbeddedChannel(new HttpServerCodec(), new HttpObjectAggregator(Integer.MAX_VALUE), requestMessageDecoder);
+
+ final UUID rid = UUID.randomUUID();
+ final ByteBuf buffer = allocator.buffer();
+ buffer.writeCharSequence("{\"gremlin\":\"g.V().limit(2)\",\"batchSize\":\"10\",\"language\":\"gremlin-lang\"," +
+ "\"g\":\"gmodern\",\"bindings\":{\"x\":\"1\"},\"requestId\":\"" + rid + "\"}", CharsetUtil.UTF_8);
+
+ final HttpHeaders headers = new DefaultHttpHeaders();
+ headers.add(HttpHeaderNames.CONTENT_TYPE, SerTokens.MIME_JSON);
+
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "",
+ buffer, headers, new DefaultHttpHeaders());
+
+ testChannel.writeInbound(httpRequest);
+ testChannel.finish();
+
+ RequestMessageV4 decodedRequest = testChannel.readInbound();
+ assertEquals("g.V().limit(2)", decodedRequest.getGremlin());
+ assertEquals(10, (int) decodedRequest.getField(Tokens.ARGS_BATCH_SIZE));
+ assertEquals("gremlin-lang", decodedRequest.getField(Tokens.ARGS_LANGUAGE));
+ assertEquals("gmodern", decodedRequest.getField(Tokens.ARGS_G));
+ assertEquals("1", ((Map) decodedRequest.getField(Tokens.ARGS_BINDINGS)).get("x"));
+ assertEquals(1, ((Map) decodedRequest.getField(Tokens.ARGS_BINDINGS)).size());
+ assertEquals(rid, decodedRequest.getField(Tokens.REQUEST_ID));
+ }
+}
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4.java
index 44a88f5c47..4718716ae4 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4.java
@@ -29,19 +29,16 @@ import java.util.Optional;
import java.util.UUID;
/**
- * The model for a request message sent to the server.
- *
- * @author Stephen Mallette (http://stephen.genoprime.com)
+ * The model for a request message in the HTTP body that is sent to the server.
*/
public final class RequestMessageV4 {
/**
* An "invalid" message. Used internally only.
*/
public static final RequestMessageV4 INVALID = new RequestMessageV4();
- private UUID requestId;
-//
+
private String gremlinType; // Type information needed to help deserialize "gremlin" into either String/Bytecode.
-//
+
private Object gremlin; // Should be either a String or Bytecode type.
private Map<String, Object> fields;
@@ -60,7 +57,6 @@ public final class RequestMessageV4 {
this.gremlin = gremlin;
this.fields = fields;
- this.requestId = (UUID) requestId;
if (gremlin instanceof String) {
gremlinType = Tokens.OPS_EVAL;
@@ -84,20 +80,20 @@ public final class RequestMessageV4 {
* value should be unique per request made.
*/
public UUID getRequestId() {
- return requestId;
+ return getField(Tokens.REQUEST_ID);
}
- public <T> Optional<T> optionalArgs(final String key) {
+ public <T> Optional<T> optionalField(final String key) {
final Object o = fields.get(key);
return o == null ? Optional.empty() : Optional.of((T) o);
}
- public <T> T getArg(final String key) {
+ public <T> T getField(final String key) {
return (T) fields.get(key);
}
- public <T> T getArgOrDefault(final String key, final T def) {
- return (T) optionalArgs(key).orElse(def);
+ public <T> T getFieldOrDefault(final String key, final T def) {
+ return (T) optionalField(key).orElse(def);
}
public String getGremlinType() {
@@ -112,6 +108,11 @@ public final class RequestMessageV4 {
return Collections.unmodifiableMap(fields);
}
+ public RequestMessageV4 trimMessage(int size) {
+ gremlin = gremlin.toString().substring(0, size) + "...";
+ return this;
+ }
+
public static Builder from(final RequestMessageV4 msg) {
final Builder builder = build(msg.gremlin);
builder.fields.putAll(msg.getFields());
@@ -132,18 +133,6 @@ public final class RequestMessageV4 {
'}';
}
- public RequestMessage convertToV1() {
- Map<String, String> alias = new HashMap<>();
- if (fields.containsKey(Tokens.ARGS_G)) alias.put(Tokens.ARGS_G, (String) fields.get(Tokens.ARGS_G));
-
- RequestMessage.Builder builder = RequestMessage.build(this.gremlinType);
- builder.overrideRequestId(this.requestId).addArg(Tokens.ARGS_GREMLIN, this.gremlin).addArg(Tokens.ARGS_ALIASES, alias);
- if (fields.containsKey(Tokens.ARGS_LANGUAGE)) builder.addArg(Tokens.ARGS_LANGUAGE, fields.get(Tokens.ARGS_LANGUAGE));
- if (fields.containsKey(Tokens.ARGS_BINDINGS)) builder.addArg(Tokens.ARGS_BINDINGS, fields.get(Tokens.ARGS_BINDINGS));
-
- return builder.create();
- }
-
public static Builder build(final Object gremlin) {
return new Builder(gremlin);
}
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseMessage.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseMessage.java
index 8fed780466..0a92190f6f 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseMessage.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseMessage.java
@@ -107,6 +107,10 @@ public final class ResponseMessage {
return new Builder(requestMessage);
}
+ public static Builder build(final RequestMessageV4 requestMessage) {
+ return new Builder(requestMessage);
+ }
+
public static Builder build(final UUID requestId) {
return new Builder(requestId);
}
@@ -128,6 +132,10 @@ public final class ResponseMessage {
this.requestId = requestMessage.getRequestId();
}
+ private Builder(final RequestMessageV4 requestMessage) {
+ this.requestId = requestMessage.getRequestId();
+ }
+
private Builder(final UUID requestId) {
this.requestId = requestId;
}
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphBinaryMessageSerializerV1.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphBinaryMessageSerializerV1.java
index 01fc57e44a..2a5bd82de1 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphBinaryMessageSerializerV1.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphBinaryMessageSerializerV1.java
@@ -22,14 +22,12 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryIo;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryMapper;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
import org.apache.tinkerpop.gremlin.util.ser.binary.RequestMessageSerializer;
-import org.apache.tinkerpop.gremlin.util.ser.binary.RequestMessageSerializerV4;
import org.apache.tinkerpop.gremlin.util.ser.binary.ResponseMessageSerializer;
import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry;
import org.apache.tinkerpop.gremlin.structure.io.binary.types.CustomTypeSerializer;
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
index 6adb029267..dec9fb5b0f 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
@@ -43,7 +43,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
-public abstract class AbstractGraphSONMessageSerializerV4 extends AbstractGraphSONMessageSerializerV2 implements MessageTextSerializerV4<ObjectMapper>, MessageChunkSerializer<ObjectMapper> {
+public abstract class AbstractGraphSONMessageSerializerV4 extends AbstractGraphSONMessageSerializerV2 implements MessageTextSerializerV4<ObjectMapper> {
private static final Logger logger = LoggerFactory.getLogger(AbstractGraphSONMessageSerializerV4.class);
public AbstractGraphSONMessageSerializerV4() {
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
index ac268b4a32..fd222a52c7 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/GraphBinaryMessageSerializerV4.java
@@ -42,7 +42,7 @@ import java.util.UUID;
import static java.nio.charset.StandardCharsets.UTF_8;
public class GraphBinaryMessageSerializerV4 extends AbstractGraphBinaryMessageSerializerV1
- implements MessageChunkSerializer<GraphBinaryMapper>, MessageTextSerializerV4<GraphBinaryMapper> {
+ implements MessageTextSerializerV4<GraphBinaryMapper> {
private static final NettyBufferFactory bufferFactory = new NettyBufferFactory();
private static final String MIME_TYPE = SerTokens.MIME_GRAPHBINARY_V4;
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageChunkSerializer.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageChunkSerializer.java
deleted file mode 100644
index f90cbe3baf..0000000000
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageChunkSerializer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.util.ser;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufAllocator;
-import org.apache.tinkerpop.gremlin.util.MessageSerializer;
-import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
-
-import java.util.EnumSet;
-
-public interface MessageChunkSerializer<M> extends MessageSerializer<M> {
- public ByteBuf writeHeader(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
-
- public ByteBuf writeChunk(final Object aggregate, final ByteBufAllocator allocator) throws SerializationException;
-
- public ByteBuf writeFooter(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
-
- public ByteBuf writeErrorFooter(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
-
- public ResponseMessage readChunk(final ByteBuf byteBuf, final boolean isFirstChunk) throws SerializationException;
-
- public enum MessageParts {
- HEADER, DATA, FOOTER;
-
- public static final EnumSet<MessageParts> ALL = EnumSet.of(HEADER, DATA, FOOTER);
- public static final EnumSet<MessageParts> START = EnumSet.of(HEADER, DATA);
- public static final EnumSet<MessageParts> CHUNK = EnumSet.of(DATA);
- public static final EnumSet<MessageParts> END = EnumSet.of(DATA, FOOTER);
- public static final EnumSet<MessageParts> ERROR = EnumSet.of(FOOTER);
- }
-}
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageTextSerializerV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageTextSerializerV4.java
index b45f56c22c..475fd35860 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageTextSerializerV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/MessageTextSerializerV4.java
@@ -25,6 +25,8 @@ import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
+import java.util.EnumSet;
+
/**
* An extension to the MessageTextSerializer interface that is used for serializing RequestMessageV4.
*/
@@ -41,4 +43,24 @@ public interface MessageTextSerializerV4<M> extends MessageTextSerializer<M> {
* Deserialize a Netty {@code ByteBuf} into a {@link RequestMessageV4}.
*/
public RequestMessageV4 deserializeRequestMessageV4(final ByteBuf msg) throws SerializationException;
+
+ public ByteBuf writeHeader(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
+
+ public ByteBuf writeChunk(final Object aggregate, final ByteBufAllocator allocator) throws SerializationException;
+
+ public ByteBuf writeFooter(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
+
+ public ByteBuf writeErrorFooter(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException;
+
+ public ResponseMessage readChunk(final ByteBuf byteBuf, final boolean isFirstChunk) throws SerializationException;
+
+ public enum MessageParts {
+ HEADER, DATA, FOOTER;
+
+ public static final EnumSet<MessageParts> ALL = EnumSet.of(HEADER, DATA, FOOTER);
+ public static final EnumSet<MessageParts> START = EnumSet.of(HEADER, DATA);
+ public static final EnumSet<MessageParts> CHUNK = EnumSet.of(DATA);
+ public static final EnumSet<MessageParts> END = EnumSet.of(DATA, FOOTER);
+ public static final EnumSet<MessageParts> ERROR = EnumSet.of(FOOTER);
+ }
}
diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/binary/RequestMessageSerializerV4.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/binary/RequestMessageSerializerV4.java
index a983db54a5..addf5cbc53 100644
--- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/binary/RequestMessageSerializerV4.java
+++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/binary/RequestMessageSerializerV4.java
@@ -50,25 +50,6 @@ public class RequestMessageSerializerV4 {
}
try {
-// final UUID id = context.readValue(buffer, UUID.class, false);
-// final String gremlinType = context.readValue(buffer, String.class, false);
-//
-// final Object gremlin;
-// if (gremlinType.equals(Tokens.OPS_EVAL)) {
-// gremlin = context.readValue(buffer, String.class, false);
-// } else if (gremlinType.equals(Tokens.OPS_BYTECODE)) {
-// gremlin = context.readValue(buffer, Bytecode.class, false);
-// } else {
-// throw new SerializationException("Type " + gremlinType + " not supported for serialization.");
-// }
-//
-// final String lang = context.readValue(buffer, String.class, false);
-// final Map<String, Object> bindings = context.readValue(buffer, Map.class, false);
-// final String g = context.readValue(buffer, String.class, false);
-//
-// final RequestMessageV4.Builder builder = RequestMessageV4.build(gremlin).overrideRequestId(id).addG(g).addLanguage(lang);
-// bindings.forEach(builder::addBinding);
-
final Map<String, Object> fields = context.readValue(buffer, Map.class, false);
final String gremlinType = (String) fields.get("gremlinType");
@@ -108,19 +89,9 @@ public class RequestMessageSerializerV4 {
try {
// Version
buffer.writeByte(GraphBinaryWriter.VERSION_BYTE);
- // RequestId
-// context.writeValue(value.getRequestId(), buffer, false);
- // Gremlin type
-// context.writeValue(value.getGremlinType(), buffer, false);
- // Gremlin
-// context.writeValue(value.getGremlin(), buffer, false);
- // Language
-// context.writeValue(value.getLanguage(), buffer, false);
- // Bindings
-// context.writeValue(value.getBindings(), buffer, false);
- // G
-// context.writeValue(value.getG(), buffer, false);
+ // Fields
context.writeValue(value.getFields(), buffer, false);
+ // Gremlin
context.writeValue(value.getGremlin(), buffer, false);
} catch (IOException ex) {
diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4Test.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4Test.java
index fa99b72c82..f9e9de312f 100644
--- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4Test.java
+++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/message/RequestMessageV4Test.java
@@ -61,7 +61,7 @@ public class RequestMessageV4Test {
bindings.put("a", "b");
bindings.put("g", "gmodern");
final RequestMessageV4 msg = RequestMessageV4.build("gremlin").addBindings(bindings).create();
- assertEquals(bindings, msg.getArg(Tokens.ARGS_BINDINGS));
+ assertEquals(bindings, msg.getField(Tokens.ARGS_BINDINGS));
}
@Test
@@ -73,21 +73,21 @@ public class RequestMessageV4Test {
.addBinding("a", "b")
.addBinding("g", "gmodern")
.create();
- assertEquals(bindings, msg.getArg(Tokens.ARGS_BINDINGS));
+ assertEquals(bindings, msg.getField(Tokens.ARGS_BINDINGS));
}
@Test
public void shouldSetLanguage() {
final String language = "fake-lang";
final RequestMessageV4 msg = RequestMessageV4.build("g").addLanguage(language).create();
- assertEquals(language, msg.getArg(Tokens.ARGS_LANGUAGE));
+ assertEquals(language, msg.getField(Tokens.ARGS_LANGUAGE));
}
@Test
public void shouldSetG() {
final String g = "gmodern";
final RequestMessageV4 msg = RequestMessageV4.build("g").addG(g).create();
- assertEquals(g, msg.getArg(Tokens.ARGS_G));
+ assertEquals(g, msg.getField(Tokens.ARGS_G));
}
@Test
@@ -113,26 +113,26 @@ public class RequestMessageV4Test {
public void shouldGetGAsArg() {
final String g = "gmodern";
final RequestMessageV4 msg = RequestMessageV4.build("g").addG(g).create();
- assertEquals(g, msg.getArg(Tokens.ARGS_G));
+ assertEquals(g, msg.getField(Tokens.ARGS_G));
}
@Test
public void shouldGetGAsArgOrDefault() {
final RequestMessageV4 msg = RequestMessageV4.build("g").create();
- assertEquals("b", msg.getArgOrDefault(Tokens.ARGS_G, "b"));
+ assertEquals("b", msg.getFieldOrDefault(Tokens.ARGS_G, "b"));
}
@Test
public void shouldGetGAsArgAsOptional() {
final String g = "gmodern";
final RequestMessageV4 msg = RequestMessageV4.build("g").addG(g).create();
- assertEquals(g, msg.optionalArgs(Tokens.ARGS_G).get());
+ assertEquals(g, msg.optionalField(Tokens.ARGS_G).get());
}
@Test
public void shouldNotBeAbleToGetGremlinQueryFromArgs() {
final String query = "gmodern";
final RequestMessageV4 msg = RequestMessageV4.build(query).create();
- assertTrue(null == msg.getArg(Tokens.ARGS_GREMLIN));
+ assertTrue(null == msg.getField(Tokens.ARGS_GREMLIN));
}
}