
org.swisspush.gateleen.routing.Router Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gateleen-routing Show documentation
Show all versions of gateleen-routing Show documentation
Middleware library based on Vert.x to build advanced JSON/REST communication servers
The newest version!
package org.swisspush.gateleen.routing;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.swisspush.gateleen.monitoring.MonitoringHandler;
import org.swisspush.gateleen.core.storage.ResourceStorage;
import org.swisspush.gateleen.core.util.Address;
import org.swisspush.gateleen.core.util.ResourcesUtils;
import org.swisspush.gateleen.core.util.StatusCode;
import org.swisspush.gateleen.core.util.StringUtils;
import org.swisspush.gateleen.logging.LoggingResourceManager;
import org.swisspush.gateleen.validation.ValidationException;
import org.swisspush.gateleen.core.refresh.Refreshable;
import java.net.HttpCookie;
import java.util.*;
/**
* @author https://github.com/lbovet [Laurent Bovet]
*/
public class Router implements Refreshable {
/**
* How long to let the http clients live before closing them after a re-configuration
*/
private static final int GRACE_PERIOD = 30000;
public static final String ROUTER_STATE_MAP = "router_state_map";
public static final String ROUTER_BROKEN_KEY = "router_broken";
private String rulesUri;
private String userProfileUri;
private String serverUri;
private io.vertx.ext.web.Router router;
private LoggingResourceManager loggingResourceManager;
private MonitoringHandler monitoringHandler;
private Logger log = LoggerFactory.getLogger(Router.class);
private Vertx vertx;
private Set httpClients = new HashSet<>();
private HttpClient selfClient;
private ResourceStorage storage;
private JsonObject info;
private final Map properties;
private Handler doneHandlers[];
private LocalMap sharedData;
private int storagePort;
private String routingRulesSchema;
public Router(Vertx vertx,
LocalMap sharedData,
final ResourceStorage storage,
final Map properties,
LoggingResourceManager loggingResourceManager,
MonitoringHandler monitoringHandler,
HttpClient selfClient,
String serverPath,
String rulesPath,
String userProfilePath,
JsonObject info,
Handler... doneHandlers) {
this(vertx,
sharedData,
storage,
properties,
loggingResourceManager,
monitoringHandler,
selfClient,
serverPath,
rulesPath,
userProfilePath,
info,
8989,
doneHandlers);
}
public Router(Vertx vertx,
final ResourceStorage storage,
final Map properties,
LoggingResourceManager loggingResourceManager,
MonitoringHandler monitoringHandler,
HttpClient selfClient,
String serverPath,
String rulesPath,
String userProfilePath,
JsonObject info,
Handler... doneHandlers) {
this(vertx,
vertx.sharedData(). getLocalMap(ROUTER_STATE_MAP),
storage,
properties,
loggingResourceManager,
monitoringHandler,
selfClient,
serverPath,
rulesPath,
userProfilePath,
info,
8989,
doneHandlers);
}
public Router(Vertx vertx,
final ResourceStorage storage,
final Map properties,
LoggingResourceManager loggingResourceManager,
MonitoringHandler monitoringHandler,
HttpClient selfClient,
String serverPath,
String rulesPath,
String userProfilePath,
JsonObject info,
int storagePort,
Handler... doneHandlers) {
this(vertx,
vertx.sharedData(). getLocalMap(ROUTER_STATE_MAP),
storage,
properties,
loggingResourceManager,
monitoringHandler,
selfClient,
serverPath,
rulesPath,
userProfilePath,
info,
storagePort,
doneHandlers);
}
public Router(Vertx vertx,
LocalMap sharedData,
final ResourceStorage storage,
final Map properties,
LoggingResourceManager loggingResourceManager,
MonitoringHandler monitoringHandler,
HttpClient selfClient,
String serverPath,
String rulesPath,
String userProfilePath,
JsonObject info,
int storagePort,
Handler... doneHandlers) {
this.storage = storage;
this.properties = properties;
this.loggingResourceManager = loggingResourceManager;
this.monitoringHandler = monitoringHandler;
this.selfClient = selfClient;
this.vertx = vertx;
this.sharedData = sharedData;
this.rulesUri = rulesPath;
this.userProfileUri = userProfilePath;
this.serverUri = serverPath;
this.info = info;
this.storagePort = storagePort;
this.doneHandlers = doneHandlers;
routingRulesSchema = ResourcesUtils.loadResource("gateleen_routing_schema_routing_rules", true);
final JsonObject initialRules = new JsonObject()
.put("/(.*)",new JsonObject()
.put("name", "resource_storage")
.put("url", "http://localhost:" + String.valueOf(storagePort) + "/$1"));
storage.get(rulesPath, buffer -> {
try {
if (buffer != null) {
try {
log.info("Applying rules");
updateRouting(buffer);
} catch (ValidationException e) {
log.error("Could not reconfigure routing", e);
updateRouting(initialRules);
setRoutingBrokenMessage(e);
}
} else {
log.warn("No rules in storage, using initial routing");
updateRouting(initialRules);
}
} catch (ValidationException e) {
log.error("Could not reconfigure routing", e);
setRoutingBrokenMessage(e);
}
});
// Receive update notifications
vertx.eventBus().consumer(Address.RULE_UPDATE_ADDRESS, (Handler>) event -> storage.get(rulesUri, buffer -> {
if (buffer != null) {
try {
log.info("Applying rules");
updateRouting(buffer);
} catch (ValidationException e) {
log.error("Could not reconfigure routing", e);
}
} else {
log.warn("Could not get URL '" + (rulesUri == null ? "" : rulesUri) + "' (getting rules).");
}
}));
}
public void route(final HttpServerRequest request) {
// Intercept rule configuration
if (request.uri().equals(rulesUri) && HttpMethod.PUT == request.method()) {
request.bodyHandler(buffer -> {
try {
new RuleFactory(properties, routingRulesSchema).parseRules(buffer);
} catch (ValidationException validationException) {
log.error("Could not parse rules: " + validationException.toString());
request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode());
request.response().setStatusMessage(StatusCode.BAD_REQUEST.getStatusMessage() + " " + validationException.getMessage());
if(validationException.getValidationDetails() != null){
request.response().headers().add("content-type", "application/json");
request.response().end(validationException.getValidationDetails().encode());
} else {
request.response().end(validationException.getMessage());
}
return;
}
storage.put(rulesUri, buffer, status -> {
if (status == StatusCode.OK.getStatusCode()) {
vertx.eventBus().publish(Address.RULE_UPDATE_ADDRESS, true);
resetRouterBrokenState();
} else {
request.response().setStatusCode(status);
}
request.response().end();
});
});
} else {
String routingBrokenMessage = getRoutingBrokenMessage();
boolean isRoutingBroken = routingBrokenMessage != null;
if (isRoutingBroken) {
if (request.uri().equals(rulesUri) && HttpMethod.GET == request.method()) {
storage.get(rulesUri, buffer -> {
request.response().setStatusCode(StatusCode.OK.getStatusCode());
request.response().setStatusMessage(StatusCode.OK.getStatusMessage());
request.response().end(buffer);
});
} else {
request.response().setStatusCode(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode());
request.response().setStatusMessage(StatusCode.INTERNAL_SERVER_ERROR.getStatusMessage());
request.response().end(ErrorPageCreator.createRoutingBrokenHTMLErrorPage(routingBrokenMessage, rulesUri, rulesUri));
}
} else {
if (router!= null) {
router.accept(request);
} else {
request.response().setStatusCode(StatusCode.SERVICE_UNAVAILABLE.getStatusCode());
request.response().setStatusMessage("Server not yet ready");
request.response().end(request.response().getStatusMessage());
}
}
}
}
public boolean isRoutingBroken() {
return getRoutingBrokenMessage() != null;
}
public String getRoutingBrokenMessage() {
return (String) getRouterStateMap().get(ROUTER_BROKEN_KEY);
}
private void setRoutingBrokenMessage(ValidationException exception) {
StringBuilder msgBuilder = new StringBuilder(exception.getMessage());
if(exception.getValidationDetails() != null){
msgBuilder.append(": ").append(exception.getValidationDetails().toString());
}
String message = msgBuilder.toString();
if (StringUtils.isEmpty(message)) {
message = "No Message provided!";
}
getRouterStateMap().put(ROUTER_BROKEN_KEY, message);
log.error("routing is broken. message: " + message);
}
private void resetRouterBrokenState() {
if (getRouterStateMap().keySet().contains(ROUTER_BROKEN_KEY)) {
log.info("reset router broken state. Routing is not broken anymore");
}
getRouterStateMap().remove(ROUTER_BROKEN_KEY);
}
private LocalMap getRouterStateMap() {
return sharedData;
}
private HttpClientOptions buildHttpClientOptions(Rule rule){
HttpClientOptions options = new HttpClientOptions()
.setDefaultHost(rule.getHost())
.setDefaultPort(rule.getPort())
.setMaxPoolSize(rule.getPoolSize())
.setConnectTimeout(rule.getTimeout())
.setKeepAlive(rule.isKeepAlive())
.setPipelining(false);
if (rule.getScheme().equals("https")) {
options.setSsl(true).setVerifyHost(false).setTrustAll(true);
}
return options;
}
private void createForwarders(List rules, io.vertx.ext.web.Router newRouter, Set newClients) {
for (Rule rule : rules) {
HttpClient client = vertx.createHttpClient(buildHttpClientOptions(rule));
/*
* in case of a null - routing
* the host field of the rule
* is null.
*/
Handler forwarder;
if (rule.getPath() == null) {
forwarder = new NullForwarder(rule, loggingResourceManager, monitoringHandler);
} else if (rule.getStorage() != null) {
forwarder = new StorageForwarder(vertx.eventBus(), rule, loggingResourceManager, monitoringHandler);
} else if (rule.getScheme().equals("local")) {
forwarder = new Forwarder(vertx, selfClient, rule, this.storage, loggingResourceManager, monitoringHandler, userProfileUri);
} else {
forwarder = new Forwarder(vertx, client, rule, this.storage, loggingResourceManager, monitoringHandler, userProfileUri);
}
if (rule.getMethods() == null) {
log.info("Installing " + rule.getScheme().toUpperCase() + " forwarder for all methods: " + rule.getUrlPattern());
newRouter.routeWithRegex(rule.getUrlPattern()).handler(forwarder);
} else {
installMethodForwarder(newRouter, rule, forwarder);
}
newClients.add(client);
}
}
private void installMethodForwarder(io.vertx.ext.web.Router newRouter, Rule rule, Handler forwarder) {
for (String method : rule.getMethods()) {
log.info("Installing " + rule.getScheme().toUpperCase() + " forwarder for methods " + method + " to " + rule.getUrlPattern());
switch (method) {
case "GET":
newRouter.getWithRegex(rule.getUrlPattern()).handler(forwarder);
break;
case "PUT":
newRouter.putWithRegex(rule.getUrlPattern()).handler(forwarder);
break;
case "POST":
newRouter.postWithRegex(rule.getUrlPattern()).handler(forwarder);
break;
case "DELETE":
newRouter.deleteWithRegex(rule.getUrlPattern()).handler(forwarder);
break;
}
}
}
private void cleanup() {
final HashSet clientsToClose = new HashSet<>(httpClients);
vertx.setTimer(GRACE_PERIOD, event -> {
if (clientsToClose.size() > 0) {
log.debug("Cleaning up {} clients", clientsToClose.size());
}
for (HttpClient client : clientsToClose) {
client.close();
}
});
}
private void updateRouting(JsonObject rules) throws ValidationException {
updateRouting(new RuleFactory(properties, routingRulesSchema).createRules(rules));
}
private void updateRouting(Buffer buffer) throws ValidationException {
List rules = new RuleFactory(properties, routingRulesSchema).parseRules(buffer);
updateRouting(rules);
}
private void updateRouting(List rules) {
io.vertx.ext.web.Router newRouter = io.vertx.ext.web.Router.router(vertx);
newRouter.put(serverUri + "/simulator/.*").handler(ctx -> ctx.request().bodyHandler(buffer -> {
try {
final JsonObject obj = new JsonObject(buffer.toString());
log.debug("Simulator got " + obj.getLong("delay") + " " + obj.getLong("size"));
vertx.setTimer(obj.getLong("delay"), event -> {
try {
char[] body = new char[obj.getInteger("size")];
ctx.response().end(new String(body));
log.debug("Simulator sent response");
} catch (Exception e) {
log.error("Simulator error " + e.getMessage());
ctx.response().end();
}
});
} catch (Exception e) {
log.error("Simulator error " + e.getMessage());
ctx.response().end();
}
}));
newRouter.get(serverUri + "/info").handler(ctx -> {
if (HttpMethod.GET == ctx.request().method()) {
ctx.response().headers().set("Content-Type", "application/json");
ctx.response().end(info.toString());
} else {
ctx.response().setStatusCode(StatusCode.METHOD_NOT_ALLOWED.getStatusCode());
ctx.response().setStatusMessage(StatusCode.METHOD_NOT_ALLOWED.getStatusMessage());
ctx.response().end();
}
});
newRouter.getWithRegex("/[^/]+/debug").handler(ctx -> {
ctx.response().headers().set("Content-Type", "text/plain");
StringBuilder body = new StringBuilder();
body.append("* Headers *\n\n");
SortedSet keys = new TreeSet<>(ctx.request().headers().names());
for (String key : keys) {
String value = ctx.request().headers().get(key);
if ("cookie".equals(key)) {
body.append("cookie:\n");
for (HttpCookie cookie : HttpCookie.parse(value)) {
body.append(" ");
body.append(cookie.toString());
}
}
body.append(key).append(": ").append(value).append("\n");
}
body.append("\n");
body.append("* System Properties *\n\n");
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy