org.apache.camel.main.http.VertxHttpServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-kamelet-main Show documentation
Show all versions of camel-kamelet-main Show documentation
Main to run Kamelet standalone
/*
* 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.camel.main.http;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.StartupListener;
import org.apache.camel.component.platform.http.HttpEndpointModel;
import org.apache.camel.component.platform.http.PlatformHttpComponent;
import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpServer;
import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpServerConfiguration;
import org.apache.camel.console.DevConsole;
import org.apache.camel.console.DevConsoleRegistry;
import org.apache.camel.health.HealthCheck;
import org.apache.camel.health.HealthCheckHelper;
import org.apache.camel.health.HealthCheckRegistry;
import org.apache.camel.main.util.CamelJBangSettingsHelper;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.support.SimpleEventNotifierSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
import org.apache.camel.util.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* To setup vertx http server in the running Camel application
*/
public final class VertxHttpServer {
static VertxPlatformHttpRouter router;
static VertxPlatformHttpServer server;
static PlatformHttpComponent phc;
private static final Logger LOG = LoggerFactory.getLogger(VertxHttpServer.class);
private static final AtomicBoolean REGISTERED = new AtomicBoolean();
private static final AtomicBoolean CONSOLE = new AtomicBoolean();
private static final AtomicBoolean HEALTH_CHECK = new AtomicBoolean();
private VertxHttpServer() {
}
public static void setPlatformHttpComponent(PlatformHttpComponent phc) {
VertxHttpServer.phc = phc;
}
public static void registerServer(CamelContext camelContext, boolean stub) {
if (REGISTERED.compareAndSet(false, true)) {
doRegisterServer(camelContext, 8080, stub);
}
}
public static void registerServer(CamelContext camelContext, int port, boolean stub) {
if (REGISTERED.compareAndSet(false, true)) {
doRegisterServer(camelContext, port, stub);
}
}
private static void doRegisterServer(CamelContext camelContext, int port, boolean stub) {
// need to capture we use http-server
CamelJBangSettingsHelper.writeSettings("camel.jbang.platform-http.port", Integer.toString(port));
if (stub) {
return;
}
try {
VertxPlatformHttpServerConfiguration config = new VertxPlatformHttpServerConfiguration();
config.setPort(port);
server = new VertxPlatformHttpServer(config);
camelContext.addService(server);
server.start();
router = VertxPlatformHttpRouter.lookup(camelContext);
if (phc == null) {
phc = camelContext.getComponent("platform-http", PlatformHttpComponent.class);
}
// after camel is started then add event notifier
camelContext.addStartupListener(new StartupListener() {
@Override
public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception {
camelContext.getManagementStrategy().addEventNotifier(new SimpleEventNotifierSupport() {
private Set last;
@Override
public boolean isEnabled(CamelEvent event) {
return event instanceof CamelEvent.CamelContextStartedEvent
|| event instanceof CamelEvent.RouteReloadedEvent;
}
@Override
public void notify(CamelEvent event) throws Exception {
// when reloading then there may be more routes in the same batch, so we only want
// to log the summary at the end
if (event instanceof CamelEvent.RouteReloadedEvent) {
CamelEvent.RouteReloadedEvent re = (CamelEvent.RouteReloadedEvent) event;
if (re.getIndex() < re.getTotal()) {
return;
}
}
Set endpoints = phc.getHttpEndpoints();
if (endpoints.isEmpty()) {
return;
}
// log only if changed
if (last == null || last.size() != endpoints.size() || !last.containsAll(endpoints)) {
LOG.info("HTTP endpoints summary");
for (HttpEndpointModel u : endpoints) {
String line = "http://0.0.0.0:" + port + u.getUri();
if (u.getVerbs() != null) {
line += " (" + u.getVerbs() + ")";
}
LOG.info(" {}", line);
}
}
// use a defensive copy of last known endpoints
last = new HashSet<>(endpoints);
}
});
}
});
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeException(e);
}
}
public static void registerConsole(CamelContext camelContext) {
if (CONSOLE.compareAndSet(false, true)) {
doRegisterConsole(camelContext);
}
}
private static void doRegisterConsole(CamelContext context) {
Route dev = router.route("/q/dev");
dev.method(HttpMethod.GET);
dev.produces("text/plain");
dev.produces("application/json");
Route devSub = router.route("/q/dev/*");
devSub.method(HttpMethod.GET);
devSub.produces("text/plain");
devSub.produces("application/json");
Handler handler = new Handler() {
@Override
public void handle(RoutingContext ctx) {
String acp = ctx.request().getHeader("Accept");
int pos1 = acp != null ? acp.indexOf("html") : Integer.MAX_VALUE;
if (pos1 == -1) {
pos1 = Integer.MAX_VALUE;
}
int pos2 = acp != null ? acp.indexOf("json") : Integer.MAX_VALUE;
if (pos2 == -1) {
pos2 = Integer.MAX_VALUE;
}
final boolean html = pos1 < pos2;
final boolean json = pos2 < pos1;
final DevConsole.MediaType mediaType = json ? DevConsole.MediaType.JSON : DevConsole.MediaType.TEXT;
ctx.response().putHeader("content-type", "text/plain");
DevConsoleRegistry dcr = context.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class);
if (dcr == null || !dcr.isEnabled()) {
ctx.end("Developer Console is not enabled");
return;
}
String path = StringHelper.after(ctx.request().path(), "/q/dev/");
String s = path;
if (s != null && s.contains("/")) {
s = StringHelper.before(s, "/");
}
String id = s;
// index/home should list each console
if (id == null || id.isEmpty() || id.equals("index")) {
StringBuilder sb = new StringBuilder();
JsonObject root = new JsonObject();
dcr.stream().forEach(c -> {
if (json) {
JsonObject jo = new JsonObject();
jo.put("id", c.getId());
jo.put("displayName", c.getDisplayName());
jo.put("description", c.getDescription());
root.put(c.getId(), jo);
} else {
String link = c.getId();
String eol = "\n";
if (html) {
link = "" + c.getId() + "";
eol = "
\n";
}
sb.append(link).append(": ").append(c.getDescription()).append(eol);
// special for top in processor mode
if ("top".equals(c.getId())) {
link = link.replace("top", "top/*");
sb.append(link).append(": ").append("Display the top processors").append(eol);
}
}
});
if (sb.length() > 0) {
String out = sb.toString();
if (html) {
ctx.response().putHeader("content-type", "text/html");
}
ctx.end(out);
} else if (!root.isEmpty()) {
ctx.response().putHeader("content-type", "application/json");
String out = root.toJson();
ctx.end(out);
}
} else {
Map params = new HashMap<>();
ctx.queryParams().forEach(params::put);
params.put(Exchange.HTTP_PATH, path);
StringBuilder sb = new StringBuilder();
JsonObject root = new JsonObject();
// sort according to index by given id
dcr.stream().sorted((o1, o2) -> {
int p1 = id.indexOf(o1.getId());
int p2 = id.indexOf(o2.getId());
return Integer.compare(p1, p2);
}).forEach(c -> {
boolean include = "all".equals(id) || c.getId().equalsIgnoreCase(id);
if (include && c.supportMediaType(mediaType)) {
Object out = c.call(mediaType, params);
if (out != null && mediaType == DevConsole.MediaType.TEXT) {
sb.append(c.getDisplayName()).append(":");
sb.append("\n\n");
sb.append(out);
sb.append("\n\n");
} else if (out != null && mediaType == DevConsole.MediaType.JSON) {
root.put(c.getId(), out);
}
}
});
if (sb.length() > 0) {
String out = sb.toString();
ctx.end(out);
} else if (!root.isEmpty()) {
ctx.response().putHeader("content-type", "application/json");
String out = root.toJson();
ctx.end(out);
} else {
ctx.end("Developer Console not found: " + id);
}
}
}
};
dev.handler(handler);
devSub.handler(handler);
phc.addHttpEndpoint("/q/dev", null, null);
}
public static void registerHealthCheck(CamelContext camelContext) {
if (HEALTH_CHECK.compareAndSet(false, true)) {
doRegisterHealthCheck(camelContext);
}
}
private static void doRegisterHealthCheck(CamelContext context) {
final Route health = router.route("/q/health");
health.method(HttpMethod.GET);
health.produces("application/json");
final Route live = router.route("/q/health/live");
live.method(HttpMethod.GET);
live.produces("application/json");
final Route ready = router.route("/q/health/ready");
ready.method(HttpMethod.GET);
ready.produces("application/json");
Handler handler = new Handler() {
@Override
public void handle(RoutingContext ctx) {
ctx.response().putHeader("content-type", "application/json");
boolean all = ctx.currentRoute() == health;
boolean liv = ctx.currentRoute() == live;
boolean rdy = ctx.currentRoute() == ready;
Collection res;
if (all) {
res = HealthCheckHelper.invoke(context);
} else if (liv) {
res = HealthCheckHelper.invokeLiveness(context);
} else {
res = HealthCheckHelper.invokeReadiness(context);
}
StringBuilder sb = new StringBuilder();
sb.append("{\n");
HealthCheckRegistry registry = HealthCheckRegistry.get(context);
String level = ctx.request().getParam("exposureLevel");
if (level == null) {
level = registry.getExposureLevel();
}
// are we UP
boolean up = HealthCheckHelper.isResultsUp(res, rdy);
if ("oneline".equals(level)) {
// only brief status
healthCheckStatus(sb, up);
} else if ("full".equals(level)) {
// include all details
List list = new ArrayList<>(res);
healthCheckDetails(sb, list, up);
} else {
// include only DOWN details
List downs = res.stream().filter(r -> r.getState().equals(HealthCheck.State.DOWN))
.collect(Collectors.toList());
healthCheckDetails(sb, downs, up);
}
sb.append("}\n");
if (!up) {
// we need to fail with a http status so lets use 500
ctx.response().setStatusCode(500);
}
ctx.end(sb.toString());
}
};
health.handler(handler);
live.handler(handler);
ready.handler(handler);
phc.addHttpEndpoint("/q/health", null, null);
}
private static void healthCheckStatus(StringBuilder sb, boolean up) {
if (up) {
sb.append(" \"status\": \"UP\"\n");
} else {
sb.append(" \"status\": \"DOWN\"\n");
}
}
private static void healthCheckDetails(StringBuilder sb, List checks, boolean up) {
healthCheckStatus(sb, up);
if (!checks.isEmpty()) {
sb.append(",\n");
sb.append(" \"checks\": [\n");
for (int i = 0; i < checks.size(); i++) {
HealthCheck.Result d = checks.get(i);
sb.append(" {\n");
reportHealthCheck(sb, d);
if (i < checks.size() - 1) {
sb.append(" },\n");
} else {
sb.append(" }\n");
}
}
sb.append(" ]\n");
}
}
private static void reportHealthCheck(StringBuilder sb, HealthCheck.Result d) {
sb.append(" \"name\": \"").append(d.getCheck().getId()).append("\",\n");
sb.append(" \"status\": \"").append(d.getState()).append("\",\n");
if (d.getError().isPresent()) {
String msg = allCausedByErrorMessages(d.getError().get());
sb.append(" \"error-message\": \"").append(msg)
.append("\",\n");
sb.append(" \"error-stacktrace\": \"").append(errorStackTrace(d.getError().get()))
.append("\",\n");
}
if (d.getMessage().isPresent()) {
sb.append(" \"message\": \"").append(d.getMessage().get()).append("\",\n");
}
if (d.getDetails() != null && !d.getDetails().isEmpty()) {
// lets use sorted keys
Iterator it = new TreeSet<>(d.getDetails().keySet()).iterator();
sb.append(" \"data\": {\n");
while (it.hasNext()) {
String k = it.next();
Object v = d.getDetails().get(k);
if (v == null) {
v = ""; // in case of null value
}
boolean last = !it.hasNext();
sb.append(" \"").append(k).append("\": \"").append(v).append("\"");
if (!last) {
sb.append(",");
}
sb.append("\n");
}
sb.append(" }\n");
}
}
private static String allCausedByErrorMessages(Throwable e) {
StringBuilder sb = new StringBuilder();
sb.append(e.getMessage());
while (e.getCause() != null) {
e = e.getCause();
if (e.getMessage() != null) {
sb.append("; Caused by: ");
sb.append(ObjectHelper.classCanonicalName(e));
sb.append(": ");
sb.append(e.getMessage());
}
}
return sb.toString();
}
private static String errorStackTrace(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String trace = sw.toString();
// because the stacktrace is printed in json we need to make it safe
trace = trace.replace('"', '\'');
trace = trace.replace('\t', ' ');
trace = trace.replace(System.lineSeparator(), " ");
return trace;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy