![JAR search and dependency download from the Maven repository](/logo.png)
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