This is an automated email from the ASF dual-hosted git repository. valentyn pushed a commit to branch valentyn/http-error-handling in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 42e292cb0b6f9015bb068b83ef02524120b33170 Author: Valentyn Kahamlyk <[email protected]> AuthorDate: Fri Apr 19 13:47:47 2024 -0700 errors mapping --- .../gremlin/server/ProcessingException.java | 14 +- .../server/handler/HttpGremlinEndpointHandler.java | 162 ++++++------------- .../gremlin/server/handler/HttpHandlerUtil.java | 40 ++--- .../gremlin/server/util/GremlinError.java | 173 +++++++++++++++++++++ .../gremlin/server/HttpDriverIntegrateTest.java | 2 +- .../gremlin/util/message/ResponseStatusCode.java | 15 ++ .../ser/AbstractGraphSONMessageSerializerV4.java | 5 +- 7 files changed, 263 insertions(+), 148 deletions(-) diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java index 2174e96e67..a68d9d3eea 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java @@ -18,20 +18,20 @@ */ package org.apache.tinkerpop.gremlin.server; -import org.apache.tinkerpop.gremlin.util.message.ResponseMessage; +import org.apache.tinkerpop.gremlin.server.util.GremlinError; /** * @author Stephen Mallette (http://stephen.genoprime.com) */ public class ProcessingException extends Exception { - private final ResponseMessage responseMessage; + private final GremlinError error; - public ProcessingException(final String message, final ResponseMessage responseMessage) { - super(message); - this.responseMessage = responseMessage; + public ProcessingException(final GremlinError error) { + super(error.getMessage()); + this.error = error; } - public ResponseMessage getResponseMessage() { - return this.responseMessage; + public GremlinError getError() { + return this.error; } } 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 2196795250..e784826f1d 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 @@ -49,6 +49,7 @@ import org.apache.tinkerpop.gremlin.server.GremlinServer; 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.GremlinError; import org.apache.tinkerpop.gremlin.server.util.MetricManager; import org.apache.tinkerpop.gremlin.server.util.TraverserIterator; import org.apache.tinkerpop.gremlin.structure.Column; @@ -81,7 +82,6 @@ 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; @@ -217,21 +217,9 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ 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.buildV4() - .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST) - .statusMessage(msgInvalid) - .create()); + throw new ProcessingException(GremlinError.invalidGremlinType(requestMessage)); default: - final String msgDefault = - String.format("Message with gremlin of type [%s] is not recognized.", requestMessage.getGremlinType()); - throw new ProcessingException(msgDefault, - ResponseMessage.buildV4() - .code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST) - .statusMessage(msgDefault) - .create()); + throw new ProcessingException(GremlinError.unknownGremlinType(requestMessage)); } } catch (Throwable t) { writeError(requestCtx, formErrorResponseMessage(t, requestMessage), serializer.getValue1()); @@ -257,27 +245,16 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ 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.buildV4() - .code(ResponseStatusCode.SERVER_ERROR_TIMEOUT) - .statusMessage(errorMessage) - .create(), - serializer.getValue1()); + writeError(requestCtx, GremlinError.timeout(requestMessage), serializer.getValue1()); } }, seto, TimeUnit.MILLISECONDS)); } } catch (RejectedExecutionException ree) { - writeError(requestCtx, - ResponseMessage.buildV4().code(ResponseStatusCode.TOO_MANY_REQUESTS).statusMessage("Rate limiting").create(), - serializer.getValue1()); + writeError(requestCtx, GremlinError.rateLimiting(), serializer.getValue1()); } } - private ResponseMessage formErrorResponseMessage(Throwable t, RequestMessageV4 requestMessage) { - final ResponseMessage errorResponseMessage; - String logMessage = String.format("Exception processing request [%s].", requestMessage); - + private GremlinError formErrorResponseMessage(Throwable t, RequestMessageV4 requestMessage) { if (t instanceof UndeclaredThrowableException) t = t.getCause(); // if any exception in the chain is TemporaryException or Failure then we should respond with the @@ -285,63 +262,43 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t); if (possibleSpecialException.isPresent()) { final Throwable special = possibleSpecialException.get(); - final ResponseMessage.Builder specialResponseMsg = ResponseMessage.buildV4(). - 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()); + return GremlinError.temporary(special); } - errorResponseMessage = specialResponseMsg.create(); - } else if (t instanceof ProcessingException) { - errorResponseMessage = ((ProcessingException) t).getResponseMessage(); - } else { - t = ExceptionHelper.getRootCause(t); - - if (t instanceof TooLongFrameException) { - return ResponseMessage.buildV4() - .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.buildV4() - .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.buildV4().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.buildV4().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.buildV4().code(ResponseStatusCode.SERVER_ERROR_EVALUATION) - .statusMessage(errorMessage) - .statusAttributeException(t).create(); - } else { - errorResponseMessage = ResponseMessage.buildV4() - .code(ResponseStatusCode.SERVER_ERROR_EVALUATION) - .statusMessage((t.getMessage() == null) ? t.toString() : t.getMessage()) - .statusAttributeException(t) - .create(); + if (special instanceof Failure) { + return GremlinError.failStep((Failure) special); } + return GremlinError.general(special); + } + if (t instanceof ProcessingException) { + return ((ProcessingException) t).getError(); + } + t = ExceptionHelper.getRootCause(t); + + if (t instanceof TooLongFrameException) { + return GremlinError.longFrame(t); + } + if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) { + return GremlinError.timeout(requestMessage); + } + if (t instanceof TimedInterruptTimeoutException) { + // occurs when the TimedInterruptCustomizerProvider is in play + logger.warn(String.format("A timeout occurred within the script during evaluation of [%s] - consider increasing the limit given to TimedInterruptCustomizerProvider", requestMessage)); + return GremlinError.timedInterruptTimeout(); + } + if (t instanceof TimeoutException) { + logger.warn(String.format("Script evaluation exceeded the configured threshold for request [%s]", requestMessage)); + return GremlinError.timeout(requestMessage); + } + if (t instanceof MultipleCompilationErrorsException && t.getMessage().contains("Method too large") && + ((MultipleCompilationErrorsException) t).getErrorCollector().getErrorCount() == 1) { + final GremlinError error = GremlinError.longRequest(requestMessage); + logger.warn(error.getMessage()); + return error; } - logger.warn(logMessage, t); - return errorResponseMessage; + logger.warn(String.format("Exception processing request [%s].", requestMessage)); + return GremlinError.general(t); } private void iterateScriptEvalResult(final Context context, MessageTextSerializerV4<?> serializer, final RequestMessageV4 message) @@ -349,21 +306,17 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ 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.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.binding()); } final Set<String> badBindings = IteratorUtils.set(IteratorUtils.<String>filter(bindings.keySet().iterator(), INVALID_BINDINGS_KEYS::contains)); if (!badBindings.isEmpty()) { - final String msg = String.format("The [%s] message supplies one or more invalid parameters key of [%s] - these are reserved names.", Tokens.OPS_EVAL, badBindings); - throw new ProcessingException(msg, ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.binding(badBindings)); } // ignore control bindings that get passed in with the "#jsr223" prefix - those aren't used in compilation if (IteratorUtils.count(IteratorUtils.filter(bindings.keySet().iterator(), k -> !k.toString().startsWith("#jsr223"))) > settings.maxParameters) { - final String msg = String.format("The [%s] message contains %s bindings which is more than is allowed by the server %s configuration", - Tokens.OPS_EVAL, bindings.size(), settings.maxParameters); - throw new ProcessingException(msg, ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.binding(bindings.size(), settings.maxParameters)); } } @@ -380,21 +333,17 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ private static Traversal.Admin<?,?> translateBytecodeToTraversal(Context ctx) throws ProcessingException { 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.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.gremlinType()); } 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.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.traversalSource()); } 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.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); + throw new ProcessingException(GremlinError.traversalSource(traversalSourceName)); } final TraversalSource g = ctx.getGraphManager().getTraversalSource(traversalSourceName); @@ -407,16 +356,10 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ return ctx.getGremlinExecutor().eval(bytecode, EMPTY_BINDINGS, lambdaLanguage.get(), traversalSourceName); } catch (ScriptException ex) { logger.error("Traversal contains a lambda that cannot be compiled", ex); - throw new ProcessingException("Traversal contains a lambda that cannot be compiled", - ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_EVALUATION) - .statusMessage(ex.getMessage()) - .statusAttributeException(ex).create()); + throw new ProcessingException(GremlinError.lambdaNotSupported(ex)); } catch (Exception ex) { logger.error("Could not deserialize the Traversal instance", ex); - throw new ProcessingException("Could not deserialize the Traversal instance", - ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_SERIALIZATION) - .statusMessage(ex.getMessage()) - .statusAttributeException(ex).create()); + throw new ProcessingException(GremlinError.deserializeTraversal(ex)); } } @@ -456,10 +399,7 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ // 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.buildV4() - .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(error).create()); + throw new ProcessingException(GremlinError.binding(aliased)); } } @@ -658,13 +598,7 @@ public class HttpGremlinEndpointHandler extends SimpleChannelInboundHandler<Requ } catch (Exception ex) { logger.warn("The result [{}] in the request {} could not be serialized and returned.", aggregate, msg.getRequestId(), ex); - final String errorMessage = String.format("Error during serialization: %s", ExceptionHelper.getMessageFromExceptionOrCause(ex)); - writeError(ctx, - ResponseMessage.build(msg.getRequestId()) - .statusMessage(errorMessage) - .statusAttributeException(ex) - .code(ResponseStatusCode.SERVER_ERROR_SERIALIZATION).create(), - serializer); + writeError(ctx, GremlinError.serialization(ex), serializer); throw ex; } } 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 61f16280cc..bfdf18fe24 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 @@ -21,56 +21,36 @@ 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.GremlinError; import org.apache.tinkerpop.gremlin.server.util.MetricManager; 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.MessageTextSerializerV4; import org.apache.tinkerpop.gremlin.util.ser.SerTokens; import org.apache.tinkerpop.gremlin.util.ser.SerializationException; -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.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; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; 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; /** * Provides methods shared by the HTTP handlers. @@ -90,9 +70,9 @@ public class HttpHandlerUtil { * 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. + * @param ctx The netty channel context. + * @param status The HTTP error status code. + * @param message The error message to contain the body. */ 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)); @@ -108,7 +88,7 @@ public class HttpHandlerUtil { ctx.writeAndFlush(response); } - static void writeError(final Context context, ResponseMessage responseMessage, final MessageSerializer<?> serializer) { + static void writeError(final Context context, final ResponseMessage responseMessage, final MessageSerializer<?> serializer) { try { final ChannelHandlerContext ctx = context.getChannelHandlerContext(); final ByteBuf ByteBuf = context.getRequestState() == HttpGremlinEndpointHandler.RequestState.STREAMING @@ -124,6 +104,16 @@ public class HttpHandlerUtil { } } + static void writeError(final Context context, final GremlinError error, final MessageSerializer<?> serializer) { + final ResponseMessage responseMessage = ResponseMessage.buildV4() + .code(error.getCode()) + .statusMessage(error.getMessage()) + .exception(error.getException()) + .create(); + + writeError(context, responseMessage, serializer); + } + 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()); diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java new file mode 100644 index 0000000000..1e1185e3fd --- /dev/null +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java @@ -0,0 +1,173 @@ +/* + * 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.util; + +import org.apache.tinkerpop.gremlin.process.traversal.Failure; +import org.apache.tinkerpop.gremlin.util.ExceptionHelper; +import org.apache.tinkerpop.gremlin.util.Tokens; +import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4; +import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode; + +import java.util.Set; + +public class GremlinError { + private final ResponseStatusCode code; + private final String message; + private final String exception; + + private GremlinError(ResponseStatusCode code, String message, String exception) { + + this.code = code; + this.message = message; + this.exception = exception; + } + + public ResponseStatusCode getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public String getException() { + return exception; + } + + // ------------ request validation errors + + // script type errors + public static GremlinError invalidGremlinType(final RequestMessageV4 requestMessage ) { + final String message = String.format("Message could not be parsed. Check the format of the request. [%s]", + requestMessage); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError unknownGremlinType(final RequestMessageV4 requestMessage ) { + final String message = String.format("Message with gremlin of type [%s] is not recognized.", + requestMessage.getGremlinType()); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + // script errors + public static GremlinError binding() { + final String message = 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); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError binding(final Set<String> badBindings) { + final String message = String.format("The [%s] message supplies one or more invalid parameters key of [%s] - these are reserved names.", + Tokens.OPS_EVAL, badBindings); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError binding(final int bindingsCount, final int allowedSize) { + final String message = String.format("The [%s] message contains %s bindings which is more than is allowed by the server %s configuration", + Tokens.OPS_EVAL, bindingsCount, allowedSize); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError binding(final String aliased) { + final String message = String.format("Could not alias [%s] to [%s] as [%s] not in the Graph or TraversalSource global bindings", + Tokens.ARGS_G, aliased, aliased); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + // bytecode errors + + public static GremlinError gremlinType() { + final String message = String.format("A [%s] message requires a gremlin argument that is of type %s.", + Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError traversalSource() { + final String message = String.format("A [%s] message requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_G); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError traversalSource(final String traversalSourceName ) { + final String message = String.format("The traversal source [%s] for alias [%s] is not configured on the server.", + traversalSourceName, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS); + return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, "InvalidRequestException"); + } + + public static GremlinError lambdaNotSupported(final Throwable t) { + return new GremlinError(ResponseStatusCode.BAD_REQUEST, t.getMessage(), "InvalidRequestException"); + } + + public static GremlinError deserializeTraversal(final Throwable t) { + return new GremlinError(ResponseStatusCode.BAD_REQUEST, t.getMessage(), "InvalidRequestException"); + } + + // execution errors + public static GremlinError timeout(final RequestMessageV4 requestMessage ) { + final String message = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", + requestMessage); + return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, "ServerTimeoutExceededException"); + } + + public static GremlinError timedInterruptTimeout() { + return new GremlinError(ResponseStatusCode.SERVER_ERROR, + "Timeout during script evaluation triggered by TimedInterruptCustomizerProvider", + "ServerTimeoutExceededException"); + } + + public static GremlinError rateLimiting() { + return new GremlinError(ResponseStatusCode.TOO_MANY_REQUESTS, + "Too many requests have been sent in a given amount of time.", "TooManyRequestsException"); + } + + public static GremlinError serialization(Exception ex) { + final String message = String.format("Error during serialization: %s", ExceptionHelper.getMessageFromExceptionOrCause(ex)); + return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, "ServerSerializationException"); + } + + public static GremlinError wrongSerializer(Exception ex) { + final String message = String.format("Error during serialization: %s", ExceptionHelper.getMessageFromExceptionOrCause(ex)); + return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, "ServerSerializationException"); + } + + public static GremlinError longFrame(Throwable t) { + final String message = t.getMessage() + " - increase the maxContentLength"; + // todo: ResponseEntityTooLargeException? !!! + return new GremlinError(ResponseStatusCode.PAYLOAD_TOO_LARGE, message, "RequestEntityTooLargeException"); + } + + public static GremlinError longRequest(final RequestMessageV4 requestMessage ) { + final String message = 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)); + return new GremlinError(ResponseStatusCode.PAYLOAD_TOO_LARGE, message, "RequestEntityTooLargeException"); + } + + public static GremlinError temporary(final Throwable t) { + return new GremlinError(ResponseStatusCode.SERVER_ERROR, t.getMessage(), "ServerEvaluationException"); + } + + public static GremlinError failStep(final Failure failure) { + // todo: double check message + return new GremlinError(ResponseStatusCode.SERVER_ERROR, + failure.getMessage() + ". " + failure.format(), "ServerFailStepException"); + } + + public static GremlinError general(final Throwable t) { + final String message = (t.getMessage() == null) ? t.toString() : t.getMessage(); + return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, "ServerErrorException"); + } +} diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java index 40bf9dd811..ff61b50d08 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java @@ -486,7 +486,7 @@ public class HttpDriverIntegrateTest extends AbstractGremlinServerIntegrationTes } catch (Exception ex) { final Throwable inner = ExceptionHelper.getRootCause(ex); assertTrue(inner instanceof ResponseException); - assertEquals(ResponseStatusCode.SERVER_ERROR_SERIALIZATION, ((ResponseException) inner).getResponseStatusCode()); + assertEquals(ResponseStatusCode.SERVER_ERROR, ((ResponseException) inner).getResponseStatusCode()); } // should not die completely just because we had a bad serialization error. that kind of stuff happens diff --git a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java index 98c727e7c0..45a2eed281 100644 --- a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java +++ b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java @@ -59,6 +59,14 @@ public enum ResponseStatusCode { */ PARTIAL_CONTENT(206), + /** + * The server cannot or will not process the request due to an apparent client error (e.g., malformed request + * syntax, size too large, invalid request message framing, or deceptive request routing). + * + * @since 4.0.0 + */ + BAD_REQUEST(400), + /** * Code 401: The server could not authenticate the request or the client requested a resource it did not have * access to. @@ -82,6 +90,13 @@ public enum ResponseStatusCode { */ AUTHENTICATE(407), + /** + * The request is larger than the server is willing or able to process. + * + * @since 4.0.0 + */ + PAYLOAD_TOO_LARGE(413), + /** * Code 429: Indicates that too many requests have been sent in a given amount of time. * 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 489bf0ac4f..0b3a5b69ba 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 @@ -366,7 +366,10 @@ public abstract class AbstractGraphSONMessageSerializerV4 extends AbstractGraphS GraphSONUtil.writeStartObject(responseMessage, jsonGenerator, typeSerializer); jsonGenerator.writeStringField(SerTokens.TOKEN_MESSAGE, responseMessage.getStatus().getMessage()); jsonGenerator.writeNumberField(SerTokens.TOKEN_CODE, responseMessage.getStatus().getCode().getValue()); - jsonGenerator.writeStringField(SerTokens.TOKEN_EXCEPTION, responseMessage.getStatus().getException()); + if (responseMessage.getStatus().getCode() != ResponseStatusCode.SUCCESS && + responseMessage.getStatus().getException() != null) { + jsonGenerator.writeStringField(SerTokens.TOKEN_EXCEPTION, responseMessage.getStatus().getException()); + } GraphSONUtil.writeEndObject(responseMessage, jsonGenerator, typeSerializer); GraphSONUtil.writeEndObject(responseMessage, jsonGenerator, typeSerializer);
