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

com.gitee.rabbitnoteeth.bedrock.http.server.HttpServerVerticle Maven / Gradle / Ivy

package com.gitee.rabbitnoteeth.bedrock.http.server;

import com.gitee.rabbitnoteeth.bedrock.core.util.DateUtils;
import com.gitee.rabbitnoteeth.bedrock.core.util.SpringContextUtils;
import com.gitee.rabbitnoteeth.bedrock.core.util.StringUtils;
import com.gitee.rabbitnoteeth.bedrock.http.server.annotation.Controller;
import com.gitee.rabbitnoteeth.bedrock.http.server.annotation.RequestMapping;
import com.gitee.rabbitnoteeth.bedrock.http.server.handler.*;
import com.gitee.rabbitnoteeth.bedrock.http.json.JsonResult;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

public class HttpServerVerticle extends AbstractVerticle {

    private static final String ROUTE_PATH_404 = "^((?!/((api)|(static))/).)*$";
    private static final String METHOD_PARAMETERS_KEY = "method_parameters";
    private static final ExecutableValidator EXECUTABLE_VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();

    @Override
    public void start(Promise promise) throws Exception {
        try {

            JsonObject config = config();
            String beanName = config.getString("beanName");
            Integer port = config.getInteger("port");
            Boolean asyncMode = config.getBoolean("asyncMode");
            String staticRoot = config.getString("staticRoot");
            String staticPath = config.getString("staticPath");
            String apiPath = config.getString("apiPath");
            IHttpServer endpoint = (IHttpServer) SpringContextUtils.getBean(beanName);

            Router router = Router.router(this.vertx);

            // allow cross
            router.route("/*").handler(new CrossHandler());

            // mount static resource route
            if (StringUtils.isNotBlank(staticRoot) && StringUtils.isNotBlank(staticPath)) {
                router.route(staticPath).handler(StaticHandler.create(staticRoot));
            }

            // mount api route
            apiPath = StringUtils.isNotBlank(apiPath) ? apiPath : "/api";
            router.mountSubRouter(apiPath, loadApiRouter(asyncMode, endpoint));

            // mount sockjs
            Map sockJSEndPointMap = loadSockJSEndPoints(endpoint);
            sockJSEndPointMap.forEach((k, v) -> {
                String path = "/sockjs" + k;
                SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000);
                io.vertx.ext.web.handler.sockjs.SockJSHandler sockJSHandler = io.vertx.ext.web.handler.sockjs.SockJSHandler.create(this.vertx, options);
                router.mountSubRouter(path, sockJSHandler.socketHandler(new SockJSHandler(v)));
            });

            // set failure handler
            router.route().failureHandler(new FailureHandler(endpoint));

            HttpServerOptions httpServerOptions = new HttpServerOptions()
                .setIdleTimeout(5)
                .setMaxFormAttributeSize(-1);

            // load websocket endpoints
            Map webSocketEndPointMap = loadWebSocketEndPoints(endpoint);

            this
                .vertx
                .createHttpServer(httpServerOptions)
                .webSocketHandler(new WebSocketHandler(webSocketEndPointMap))
                .requestHandler(router)
                .listen(port, res -> {
                    if (!res.succeeded()) {
                        promise.fail(res.cause());
                    } else {
                        promise.complete();
                    }
                });
        } catch (Exception e) {
            promise.fail(e);
        }
    }

    /**
     * load api route
     *
     * @param asyncMode
     * @param endpoint
     * @return
     */
    private Router loadApiRouter(Boolean asyncMode, IHttpServer endpoint) {
        Router apiRouter = Router.router(this.vertx);

        apiRouter
            .route()
            .handler(BodyHandler.create().setDeleteUploadedFilesOnEnd(true))
            .handler(context -> {
                context.response().setChunked(true);
                context.next();
            });

        mountApiRoutes(apiRouter, asyncMode, endpoint);

        return apiRouter;
    }

    private void mountApiRoutes(Router restApiRouter, boolean reactiveMode, IHttpServer endpoint) {
        Map beanMap = SpringContextUtils.getBeansWithAnnotation(Controller.class);
        if (beanMap.size() == 0) {
            return;
        }
        Map pathMap = new HashMap<>();
        for (Object bean : beanMap.values()) {
            Class beanClass = bean.getClass();
            // get annotation @Controller
            Controller controller = beanClass.getDeclaredAnnotation(Controller.class);
            if (!controller.server().equals(endpoint.getClass())) {
                continue;
            }
            // parse request path
            RequestMapping classRequestMapping = beanClass.getDeclaredAnnotation(RequestMapping.class);
            String classRoutePath = classRequestMapping == null ? "" : classRequestMapping.value();
            Method[] methods = beanClass.getDeclaredMethods();
            for (Method method : methods) {
                method.setAccessible(true);
                RequestMapping methodRequestMapping = method.getDeclaredAnnotation(RequestMapping.class);
                if (methodRequestMapping == null) {
                    continue;
                }
                // get final request path
                String finalPath = classRoutePath + methodRequestMapping.value();
                String methodLocation = beanClass.getTypeName() + "." + method.getName();
                if (pathMap.containsKey(finalPath)) {
                    throw new IllegalStateException(String.format("conflicted http url mapping of [%s] and [%s]", pathMap.get(finalPath), methodLocation));
                } else {
                    pathMap.put(finalPath, methodLocation);
                }
                // mount
                Route route = restApiRouter.route(finalPath);
                // interrupt request
                route.handler(endpoint::onRequest);
                // reflect arguments
                route.handler(routingContext -> this.reflectMethodParams(routingContext, method));
                // validate params
                route.handler(routingContext -> this.validateMethodParams(routingContext, bean, method, endpoint));
                // execute target method
                route.handler(routingContext -> this.executeMethod(routingContext, reactiveMode, bean, method, endpoint));
            }
        }
    }

    private Map loadSockJSEndPoints(IHttpServer server) {
        Map res = new HashMap<>();
        List endPoints = SpringContextUtils.getBeansByType(ISockJSEndPoint.class);
        for (ISockJSEndPoint endPoint : endPoints) {
            String path = endPoint.path();
            Class serverClass = endPoint.server();
            if (!serverClass.equals(server.getClass())) {
                continue;
            }
            if (StringUtils.isBlank(path)) {
                continue;
            }
            res.put(path, endPoint);
        }
        return res;
    }

    private Map loadWebSocketEndPoints(IHttpServer server) {
        Map res = new HashMap<>();
        List endPoints = SpringContextUtils.getBeansByType(IWebSocketEndPoint.class);
        for (IWebSocketEndPoint endPoint : endPoints) {
            String path = endPoint.path();
            Class serverClass = endPoint.server();
            if (!serverClass.equals(server.getClass())) {
                continue;
            }
            if (StringUtils.isBlank(path)) {
                continue;
            }
            res.put("/websocket" + path, endPoint);
        }
        return res;
    }

    private void executeMethod(RoutingContext routingContext, boolean reactiveMode, Object bean, Method method, IHttpServer endpoint) {
        Object[] methodParameters = routingContext.get(METHOD_PARAMETERS_KEY);
        Class returnType = method.getReturnType();
        if (reactiveMode) {
            vertx
                .executeBlocking(future -> {
                    try {
                        Object result = method.invoke(bean, methodParameters);
                        future.complete(result);
                    } catch (Exception e) {
                        future.fail(e);
                    }
                })
                .onSuccess(result -> endpoint.onResponse(routingContext, returnType, result))
                .onFailure(routingContext::fail);
        } else {
            try {
                Object result = method.invoke(bean, methodParameters);
                endpoint.onResponse(routingContext, returnType, result);
            } catch (Exception e) {
                routingContext.fail(e.getCause());
            }
        }
    }

    private void validateMethodParams(RoutingContext routingContext, Object bean, Method method, IHttpServer endpoint) {
        try {
            Object[] methodParameters = routingContext.get(METHOD_PARAMETERS_KEY);
            Set> violationSet = EXECUTABLE_VALIDATOR.validateParameters(bean, method, methodParameters);
            if (violationSet.size() > 0) {
                endpoint.onParamValidFailed(routingContext, violationSet);
            } else {
                routingContext.next();
            }
        } catch (Exception e) {
            routingContext.fail(e);
        }
    }

    private void reflectMethodParams(RoutingContext routingContext, Method method) {
        try {
            MultiMap requestParamMap = routingContext.request().params();
            Parameter[] parameters = method.getParameters();
            Object[] methodParameters = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                String parameterName = parameter.getName();
                Class parameterType = parameter.getType();
                if (isDirectTransformableType(parameterType)) {
                    if (requestParamMap.contains(parameterName)) {
                        methodParameters[i] = createParameterValue(requestParamMap.get(parameterName), parameterType);
                    } else {
                        methodParameters[i] = null;
                    }
                } else if (parameterType == Map.class) {
                    Map map = new HashMap<>();
                    for (Map.Entry entry : requestParamMap) {
                        map.put(entry.getKey(), entry.getValue());
                    }
                    methodParameters[i] = map;
                } else {
                    Object paramInstance = parameterType.getDeclaredConstructor().newInstance();
                    Field[] fields = parameterType.getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        String fieldName = field.getName();
                        Class fieldType = field.getType();
                        if (isDirectTransformableType(fieldType) && requestParamMap.contains(fieldName)) {
                            field.set(paramInstance, createParameterValue(requestParamMap.get(fieldName), fieldType));
                        }
                    }
                    methodParameters[i] = paramInstance;
                }
            }
            routingContext.put(METHOD_PARAMETERS_KEY, methodParameters);
            routingContext.next();
        } catch (Exception e) {
            routingContext.fail(e);
        }
    }

    public boolean isDirectTransformableType(Class parameterType) {
        return
            parameterType == String.class ||
                parameterType == Short.class ||
                parameterType == short.class ||
                parameterType == Integer.class ||
                parameterType == int.class ||
                parameterType == Double.class ||
                parameterType == double.class ||
                parameterType == Long.class ||
                parameterType == long.class ||
                parameterType == Float.class ||
                parameterType == float.class ||
                parameterType == Boolean.class ||
                parameterType == boolean.class ||
                parameterType == Date.class ||
                parameterType == LocalDate.class ||
                parameterType == LocalDateTime.class;
    }

    private Object createParameterValue(String requestParamValue, Class parameterType) throws Exception {
        if (parameterType == String.class) {
            return requestParamValue;
        } else if (parameterType == Short.class) {
            return Short.valueOf(requestParamValue);
        } else if (parameterType == short.class) {
            return Short.parseShort(requestParamValue);
        } else if (parameterType == Integer.class) {
            return Integer.valueOf(requestParamValue);
        } else if (parameterType == int.class) {
            return Integer.parseInt(requestParamValue);
        } else if (parameterType == Double.class) {
            return Double.valueOf(requestParamValue);
        } else if (parameterType == double.class) {
            return Double.parseDouble(requestParamValue);
        } else if (parameterType == Long.class) {
            return Long.valueOf(requestParamValue);
        } else if (parameterType == long.class) {
            return Long.parseLong(requestParamValue);
        } else if (parameterType == Float.class) {
            return Float.valueOf(requestParamValue);
        } else if (parameterType == float.class) {
            return Float.parseFloat(requestParamValue);
        } else if (parameterType == Boolean.class) {
            return Boolean.valueOf(requestParamValue);
        } else if (parameterType == boolean.class) {
            return Boolean.parseBoolean(requestParamValue);
        } else if (parameterType == LocalDate.class) {
            return DateUtils.parseLocalDate(requestParamValue, "yyyy-MM-dd");
        } else if (parameterType == LocalDateTime.class) {
            return DateUtils.parseLocalDateTime(requestParamValue, "yyyy-MM-dd HH:mm:ss");
        } else if (parameterType == Date.class) {
            return DateUtils.parseDate(requestParamValue, "yyyy-MM-dd HH:mm:ss");
        } else {
            return null;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy