All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tinkerpop.gremlin.server.handler.HttpHandlerUtil Maven / Gradle / Ivy

The newest version!
/*
 * 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 com.codahale.metrics.Meter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.CharsetUtil;
import org.apache.tinkerpop.gremlin.server.Context;
import org.apache.tinkerpop.gremlin.server.GremlinServer;
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.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
import org.apache.tinkerpop.shaded.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.codahale.metrics.MetricRegistry.name;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
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.HttpVersion.HTTP_1_1;

/**
 * Provides methods shared by the HTTP handlers.
 *
 * @author Stephen Mallette (http://stephen.genoprime.com)
 */
public class HttpHandlerUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpHandlerUtil.class);
    static final Meter errorMeter = MetricManager.INSTANCE.getMeter(name(GremlinServer.class, "errors"));

    /**
     * A generic mapper to return JSON errors in specific cases.
     */
    private static final ObjectMapper mapper = new ObjectMapper();

    /**
     * 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 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);

        final FullHttpResponse response = new DefaultFullHttpResponse(
                HTTP_1_1, status, Unpooled.copiedBuffer(node.toString(), CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "application/json");
        HttpUtil.setContentLength(response, response.content().readableBytes());

        ctx.writeAndFlush(response);
    }

    /**
     * Writes and flushes a {@link ResponseMessage} that contains an error back to the client. Can be used to send
     * errors while streaming or when no response chunk has been sent. This serves as the end of a response.
     *
     * @param context           The netty context.
     * @param responseMessage   The response to send back.
     * @param serializer        The serializer to use to serialize the error response.
     */
    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
                    ? serializer.writeErrorFooter(responseMessage, ctx.alloc())
                    : serializer.serializeResponseAsBinary(responseMessage, ctx.alloc());

            context.setRequestState(HttpGremlinEndpointHandler.RequestState.ERROR);

            if (!ctx.channel().attr(StateKey.HTTP_RESPONSE_SENT).get()) {
                final HttpResponse responseHeader = new DefaultHttpResponse(HTTP_1_1, responseMessage.getStatus().getCode());
                responseHeader.headers().set(TRANSFER_ENCODING, CHUNKED); // Set this to make it "keep alive" eligible.
                responseHeader.headers().set(HttpHeaderNames.CONTENT_TYPE, ctx.channel().attr(StateKey.SERIALIZER).get().getValue0());
                ctx.writeAndFlush(responseHeader);
                ctx.channel().attr(StateKey.HTTP_RESPONSE_SENT).set(true);
            }

            ctx.writeAndFlush(new DefaultHttpContent(ByteBuf));

            sendTrailingHeaders(ctx, responseMessage.getStatus().getCode(), responseMessage.getStatus().getException());
        } catch (SerializationException se) {
            logger.warn("Unable to serialize ResponseMessage: {} ", responseMessage);
        }
    }

    /**
     * Writes a {@link GremlinError} into the status object of a {@link ResponseMessage} and then flushes it. Used to
     * send specific errors back to the client. This serves as the end of a response.
     *
     * @param context       The netty context.
     * @param error         The GremlinError used to populate the status.
     * @param serializer    The serializer to use to serialize the error response.
     */
    static void writeError(final Context context, final GremlinError error, final MessageSerializer serializer) {
        final ResponseMessage responseMessage = ResponseMessage.build()
                .code(error.getCode())
                .statusMessage(error.getMessage())
                .exception(error.getException())
                .create();

        writeError(context, responseMessage, serializer);
    }

    /**
     * Adds trailing headers specified in the arguments to a {@link DefaultLastHttpContent} and then flushes it. This
     * serves as the end of a response.
     *
     * @param ctx           The netty context.
     * @param statusCode    The status code to include in the trailers.
     * @param exceptionType The type of exception to include in the trailers. Leave blank or null if no error occurred.
     */
    static void sendTrailingHeaders(final ChannelHandlerContext ctx, final HttpResponseStatus statusCode, final String exceptionType) {
        final DefaultLastHttpContent defaultLastHttpContent = new DefaultLastHttpContent();
        defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_CODE, statusCode.code());
        if (exceptionType != null && !exceptionType.isEmpty()) {
            defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_EXCEPTION, exceptionType);
        }

        ctx.writeAndFlush(defaultLastHttpContent);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy