org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher.java

Source

/*
 * 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.servicecomb.transport.rest.vertx;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;

import org.apache.servicecomb.common.rest.AbstractRestInvocation;
import org.apache.servicecomb.common.rest.RestConst;
import org.apache.servicecomb.common.rest.VertxRestInvocation;
import org.apache.servicecomb.core.Const;
import org.apache.servicecomb.core.CseContext;
import org.apache.servicecomb.core.Transport;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
import org.apache.servicecomb.foundation.vertx.http.VertxServerRequestToHttpServletRequest;
import org.apache.servicecomb.foundation.vertx.http.VertxServerResponseToHttpServletResponse;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.config.DynamicPropertyFactory;

import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.CookieHandler;

public class VertxRestDispatcher extends AbstractVertxHttpDispatcher {
    private static final Logger LOGGER = LoggerFactory.getLogger(VertxRestDispatcher.class);

    private static final String KEY_ENABLED = "servicecomb.http.dispatcher.rest.enabled";

    private Transport transport;

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean enabled() {
        return DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_ENABLED, true).get();
    }

    @Override
    public void init(Router router) {
        router.route().handler(CookieHandler.create());
        router.route().handler(createBodyHandler());
        router.route().failureHandler(this::failureHandler).handler(this::onRequest);
    }

    private void failureHandler(RoutingContext context) {
        LOGGER.error("http server failed.", context.failure());

        AbstractRestInvocation restProducerInvocation = context.get(RestConst.REST_PRODUCER_INVOCATION);
        Throwable e = context.failure();
        if (ErrorDataDecoderException.class.isInstance(e)) {
            Throwable cause = e.getCause();
            if (InvocationException.class.isInstance(cause)) {
                e = cause;
            }
        }

        // only when unexpected exception happens, it will run into here.
        // the connection should be closed.
        handleFailureAndClose(context, restProducerInvocation, e);
    }

    /**
     * Try to find out the failure information and send it in response.
     */
    private void handleFailureAndClose(RoutingContext context, AbstractRestInvocation restProducerInvocation,
            Throwable e) {
        if (null != restProducerInvocation) {
            // if there is restProducerInvocation, let it send exception in response. The exception is allowed to be null.
            sendFailResponseByInvocation(context, restProducerInvocation, e);
            return;
        }

        if (null != e) {
            // if there exists exception, try to send this exception by RoutingContext
            sendExceptionByRoutingContext(context, e);
            return;
        }

        // if there is no exception, the response is determined by status code.
        sendFailureRespDeterminedByStatus(context);
    }

    /**
     * Try to determine response by status code, and send response.
     */
    private void sendFailureRespDeterminedByStatus(RoutingContext context) {
        Family statusFamily = Family.familyOf(context.statusCode());
        if (Family.CLIENT_ERROR.equals(statusFamily) || Family.SERVER_ERROR.equals(statusFamily)
                || Family.OTHER.equals(statusFamily)) {
            context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
                    .setStatusCode(context.statusCode()).end();
        } else {
            // it seems the status code is not set properly
            context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
                    .setStatusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode())
                    .setStatusMessage(Status.INTERNAL_SERVER_ERROR.getReasonPhrase())
                    .end(wrapResponseBody(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()));
        }
        context.response().close();
    }

    /**
     * Use routingContext to send failure information in throwable.
     */
    private void sendExceptionByRoutingContext(RoutingContext context, Throwable e) {
        if (InvocationException.class.isInstance(e)) {
            InvocationException invocationException = (InvocationException) e;
            context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
                    .setStatusCode(invocationException.getStatusCode())
                    .setStatusMessage(invocationException.getReasonPhrase())
                    .end(wrapResponseBody(invocationException.getReasonPhrase()));
        } else {
            context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
                    .setStatusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode())
                    .end(wrapResponseBody(e.getMessage()));
        }
        context.response().close();
    }

    /**
     * Consumer will treat the response body as json by default, so it's necessary to wrap response body as Json string
     * to avoid deserialization error.
     *
     * @param message response body
     * @return response body wrapped as Json string
     */
    String wrapResponseBody(String message) {
        if (isValidJson(message)) {
            return message;
        }

        JsonObject jsonObject = new JsonObject();
        jsonObject.put("message", message);

        return jsonObject.toString();
    }

    /**
     * Check if the message is a valid Json string.
     * @param message the message to be checked.
     * @return true if message is a valid Json string, otherwise false.
     */
    private boolean isValidJson(String message) {
        try {
            new JsonObject(message);
        } catch (Exception ignored) {
            return false;
        }
        return true;
    }

    /**
     * Use restProducerInvocation to send failure message. The throwable is allowed to be null.
     */
    private void sendFailResponseByInvocation(RoutingContext context, AbstractRestInvocation restProducerInvocation,
            Throwable e) {
        restProducerInvocation.sendFailResponse(e);
        context.response().close();
    }

    private void onRequest(RoutingContext context) {
        if (transport == null) {
            transport = CseContext.getInstance().getTransportManager().findTransport(Const.RESTFUL);
        }
        HttpServletRequestEx requestEx = new VertxServerRequestToHttpServletRequest(context);
        HttpServletResponseEx responseEx = new VertxServerResponseToHttpServletResponse(context.response());

        VertxRestInvocation vertxRestInvocation = new VertxRestInvocation();
        context.put(RestConst.REST_PRODUCER_INVOCATION, vertxRestInvocation);
        vertxRestInvocation.invoke(transport, requestEx, responseEx, httpServerFilters);
    }
}