uk.co.spudsoft.mgmt.ManagementRoute Maven / Gradle / Ivy
Show all versions of vertx-management-endpoints Show documentation
/*
* Copyright (C) 2022 jtalbut
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package uk.co.spudsoft.mgmt;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonArray;
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.CorsHandler;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Vertx HTTP Server route for providing easy access to other "management" routes.
*
* It is strongly recommended that this endpoint is not accessible to end users.
*
* A typical deployment of the management endpoints should look something like this:
*
// Create the root router
Router router = Router.router(vertx);
// Create the management endpoints router
Router mgmtRouter = Router.router(vertx);
// Deploy the management endpoints to the management endpoints router (potentially deploying capturing routes to the root router)
ManagementRoute.deployStandardMgmtEndpoints(mgmtRouter, router, params.getManagementEndpoints(), params);
// Add custom management endpoints.
if (ManagementRoute.mgmtEndpointPermitted(params.getManagementEndpoints(), "up")) {
mgmtRouter.get("/up").handler(upCheckHandler).setName("Up");
}
if (ManagementRoute.mgmtEndpointPermitted(params.getManagementEndpoints(), "health")) {
mgmtRouter.get("/health").handler(healthCheckHandler).setName("Health");
}
if (ManagementRoute.mgmtEndpointPermitted(params.getManagementEndpoints(), "prometheus")) {
mgmtRouter.get("/prometheus").handler(new PrometheusScrapingHandlerImpl()).setName("Prometheus");
}
// Set up CORS for the system
CorsHandler corsHandler = null;
if (!Strings.isNullOrEmpty(params.getCorsAllowedOriginRegex())) {
corsHandler = CorsHandler.create()
.addRelativeOrigin(params.getCorsAllowedOriginRegex());
corsHandler.allowedMethods(
ImmutableSet
.<HttpMethod>builder()
.add(HttpMethod.GET)
.add(HttpMethod.PUT)
.add(HttpMethod.DELETE)
.add(HttpMethod.POST)
.build()
);
router.route("/*").handler(corsHandler);
}
// Deploy the management router, potentially creating a new HttpServer
ManagementRoute.createAndDeploy(this.vertx
, router
, params.getHttperServerOptions()
, params.getManagementEndpointPort()
, corsHandler
, mgmtRouter
, params.getManagementEndpointUrl()
);
// Deploy other routes
router.route("/api/*").handler(...);
router.route("/ui/*").handler(...);
router.getWithRegex("/openapi\\..*").blockingHandler(openApiHandler);
router.get("/openapi").handler(openApiHandler.getUiHandler());
router.route("/").handler(rc -> {
rc.response().setStatusCode(307);
rc.redirect("/ui/");
});
// Start the primary HttpServer
httpServer
.requestHandler(router)
.listen()
*
*
* @author jtalbut
*/
public class ManagementRoute implements Handler {
/**
* The path at which the standardDeploy method will put the router.
*/
public static final String PATH = "manage";
private static final Logger logger = LoggerFactory.getLogger(ManagementRoute.class);
private final Router mgmtRouter;
/**
* Constructor.
*
* @param mgmtRouter The router that this route will report.
*/
public ManagementRoute(Router mgmtRouter) {
this.mgmtRouter = mgmtRouter;
}
/**
* Deploy the route to the router passed in at the normal endpoint.
*
* The router passed in should be a sub router that is inaccessible to normal users.
*
* @param router The router that this handler will be attached to.
*/
public void standardDeploy(Router router) {
router.route("/" + PATH + "/*").subRouter(mgmtRouter);
router.route(HttpMethod.GET, "/" + PATH)
.handler(this::handle)
.setName("Management Routes")
.produces(ContentTypes.TYPE_JSON)
.produces(ContentTypes.TYPE_HTML)
.produces(ContentTypes.TYPE_PLAIN)
;
}
/**
* Factory method to do standard deployment.
*
* If the mgmtPort is null the management route will simply be created and added to the rootRouter.
* If the mgmtPort is positive a new HttpServer will be created on that port purely to serve the management endpoints.
* If the mgmtPort is not null or positive then the management route will not be created at all.
*
* If a positive value is provided for mgmtPort a new route may be added to the rootRouter to tell client requests the
* alternate location for the management endpoints.
* This cannot be done by a standard HTTP redirect because the client will not handle CORS in a usable manner on HTTP redirects
* , so it is instead done by providing a JSON object with a single 'location' value.
* The value used for the new URL is the mgmtEndpointUrl parameter and this redirection only happens if this value is not null (or empty).
*
* @param vertx Vertx instance, needed for the creation of a new HttpServer.
* @param rootRouter The root router for the primary HttpServer.
* @param httperServerOptions HttpServerOptions used for the creation of a new HttpServer (will be copied and have the port set correctly).
* @param mgmtPort The port that should be used for the management endpoint - the key control value for this method.
* @param corsHandler The {@link io.vertx.ext.web.handler.CorsHandler} that can be added to the router of the new HttpServer.
* @param mgmtRouter The router that contains the management endpoints.
* @param mgmtEndpointUrl The URL that clients should use (instead of /manage on the standard port) for accessing the management endpoints.
* @return HttpServer A {@link io.vertx.core.Future} containing created HttpServer, if any (otherwise null).
*/
public static Future createAndDeploy(Vertx vertx, Router rootRouter, HttpServerOptions httperServerOptions, Integer mgmtPort, CorsHandler corsHandler, Router mgmtRouter, String mgmtEndpointUrl) {
if (mgmtPort == null) {
ManagementRoute route = new ManagementRoute(mgmtRouter);
route.standardDeploy(rootRouter);
return null;
} else if (mgmtPort > 0) {
Router mgmtParentRouter = Router.router(vertx);
if (corsHandler != null) {
mgmtParentRouter.route("/*").handler(corsHandler);
}
ManagementRoute route = new ManagementRoute(mgmtRouter);
route.standardDeploy(mgmtParentRouter);
HttpServerOptions options = new HttpServerOptions(httperServerOptions);
options.setPort(mgmtPort);
HttpServer mgmtHttpServer = vertx.createHttpServer(options);
mgmtHttpServer.requestHandler(mgmtParentRouter);
if (mgmtEndpointUrl != null && !mgmtEndpointUrl.isEmpty() && rootRouter != null) {
rootRouter.get("/" + PATH).handler(rc -> {
HttpServerResponse response = rc.response();
response.setStatusCode(200);
response.putHeader("Content-Type", "application/json");
JsonObject data = new JsonObject();
data.put("location", mgmtEndpointUrl);
rc.end(data.toBuffer());
});
}
return mgmtHttpServer.listen(mgmtPort);
} else {
return null;
}
}
/**
* Simple helper method to determine whether path is in the enabledEndpoints.
*
* Returns true if either enabledEndpoints is empty or it contains path.
* @param enabledEndpoints The list of enabled endpoints (may not be null, but if empty all endpoints are enabled).
* @param path The path being tested.
* @return true if either enabledEndpoints is empty or it contains path.
*/
public static boolean mgmtEndpointPermitted(List enabledEndpoints, String path) {
if (enabledEndpoints.isEmpty()) {
return true;
} else {
return enabledEndpoints.contains(path);
}
}
/**
* Helper method that creates all the standard management routes.
*
* Use of this method is entirely optional, but you are likely to end up with a similar implementation if you don't use it.
*
* This should be called as the first thing you do after creating the rootRouter in order that the logging routes can capture all traffic.
*
* It is not necessary for {@link createAndDeploy} to be called early, that can be left until later in your setup process.
*
* This method primarily makes changes to the mgmtRouter, but routes are added the rootRouter for capturing purposes.
*
* The enabledEndpoints parameter can be used to control which routes are enabled.
* If the list is empty all routes are enabled, otherwise only those routes whose sub path is in the list are enabled.
* The available values are:
*
* - parameters
* Dumps the full set of configuration parameters.
*
- envvars
* Dumps all environment variables.
*
- sysprops
* Dumps all system properties.
*
- accesslog
* Reports the past few requests to the system.
*
- inflight
* Reports all requests made to the system that have not yet completed.
*
- threads
* Dump stack traces from all threads.
*
- heapdump
* Download a heap dump.
*
*
* It is encouraged to add more routes to the mgmtRouter outside of this method for service-specific management endpoint
* (such as health and metrics).
*
* @param mgmtRouter The router that will have additional output routes added.
* @param rootRouter The root router on the primary endpoint for the service, this will have capturing routes added to it.
* @param enabledEndpoints A {@link java.util.List} of Strings that are the endpoints that should be enabled.
* @param params {@link java.util.concurrent.atomic.AtomicReference} to the parameters object that will be reported by the 'parameters' endpoint.
* If the parameters endpoint is enabled this must be a valid object that can be processed by the Vertx JSON object mapper.
*/
public static void deployStandardMgmtEndpoints(Router mgmtRouter, Router rootRouter, List enabledEndpoints, AtomicReference