geforkt von Mirrors/Velocity
Add Metrics to Velocity
Velocity will send metrics to bStats.org. These statistics are publicly viewable at https://bstats.org/plugin/server-implementation/Velocity You can always opt-out by disabling metrics in your velocity.toml. There is no obligation to allow us to collect metrics, but you can show your support by leaving metrics on.
Dieser Commit ist enthalten in:
Ursprung
3cee15a9cb
Commit
9d14af5a8b
623
proxy/src/main/java/com/velocitypowered/proxy/Metrics.java
Normale Datei
623
proxy/src/main/java/com/velocitypowered/proxy/Metrics.java
Normale Datei
@ -0,0 +1,623 @@
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* bStats collects some data for plugin authors.
|
||||
* <p/>
|
||||
* Check out https://bStats.org/ to learn more about bStats!
|
||||
*/
|
||||
public class Metrics {
|
||||
|
||||
// The version of this bStats class
|
||||
private static final int B_STATS_VERSION = 1;
|
||||
|
||||
// The url to which the data is sent
|
||||
private static final String URL = "https://bstats.org/submitData/server-implementation";
|
||||
|
||||
// Should failed requests be logged?
|
||||
private static boolean logFailedRequests = false;
|
||||
|
||||
// The logger for the failed requests
|
||||
private static Logger logger = LogManager.getLogger(Metrics.class);
|
||||
|
||||
// The name of the server software
|
||||
private final String name;
|
||||
|
||||
// The uuid of the server
|
||||
private final String serverUuid;
|
||||
|
||||
// A list with all custom charts
|
||||
private final List<CustomChart> charts = new ArrayList<>();
|
||||
|
||||
private final VelocityServer server;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
* @param name The name of the server software.
|
||||
* @param serverUuid The uuid of the server.
|
||||
* @param logFailedRequests Whether failed requests should be logged or not.
|
||||
* @param server The Velocity server instance.
|
||||
*/
|
||||
private Metrics(String name, String serverUuid, boolean logFailedRequests,
|
||||
VelocityServer server) {
|
||||
this.name = name;
|
||||
this.serverUuid = serverUuid;
|
||||
Metrics.logFailedRequests = logFailedRequests;
|
||||
this.server = server;
|
||||
|
||||
// Start submitting the data
|
||||
startSubmitting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom chart.
|
||||
*
|
||||
* @param chart The chart to add.
|
||||
*/
|
||||
public void addCustomChart(CustomChart chart) {
|
||||
if (chart == null) {
|
||||
throw new IllegalArgumentException("Chart cannot be null!");
|
||||
}
|
||||
charts.add(chart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Scheduler which submits our data every 30 minutes.
|
||||
*/
|
||||
private void startSubmitting() {
|
||||
final Timer timer = new Timer(true);
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitData();
|
||||
}
|
||||
}, 1000, 1000 * 60 * 30);
|
||||
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough
|
||||
// time to start.
|
||||
//
|
||||
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
|
||||
// WARNING: Just don't do it!
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin specific data.
|
||||
*
|
||||
* @return The plugin specific data.
|
||||
*/
|
||||
private JsonObject getPluginData() {
|
||||
JsonObject data = new JsonObject();
|
||||
|
||||
data.addProperty("pluginName", name); // Append the name of the server software
|
||||
JsonArray customCharts = new JsonArray();
|
||||
for (CustomChart customChart : charts) {
|
||||
// Add the data of the custom charts
|
||||
JsonObject chart = customChart.getRequestJsonObject();
|
||||
if (chart == null) { // If the chart is null, we skip it
|
||||
continue;
|
||||
}
|
||||
customCharts.add(chart);
|
||||
}
|
||||
data.add("customCharts", customCharts);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server specific data.
|
||||
*
|
||||
* @return The server specific data.
|
||||
*/
|
||||
private JsonObject getServerData() {
|
||||
// OS specific data
|
||||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
JsonObject data = new JsonObject();
|
||||
|
||||
data.addProperty("serverUUID", serverUuid);
|
||||
|
||||
data.addProperty("osName", osName);
|
||||
data.addProperty("osArch", osArch);
|
||||
data.addProperty("osVersion", osVersion);
|
||||
data.addProperty("coreCount", coreCount);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the data and sends it afterwards.
|
||||
*/
|
||||
private void submitData() {
|
||||
final JsonObject data = getServerData();
|
||||
|
||||
JsonArray pluginData = new JsonArray();
|
||||
pluginData.add(getPluginData());
|
||||
data.add("plugins", pluginData);
|
||||
|
||||
try {
|
||||
// We are still in the Thread of the timer, so nothing get blocked :)
|
||||
sendData(data);
|
||||
} catch (Exception e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
logger.warn("Could not submit stats of {}", name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the bStats server.
|
||||
*
|
||||
* @param data The data to send.
|
||||
* @throws Exception If the request failed.
|
||||
*/
|
||||
private void sendData(JsonObject data) throws Exception {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Data cannot be null!");
|
||||
}
|
||||
|
||||
// Compress the data to save bandwidth
|
||||
ByteBuf reqBody = createResponseBody(data);
|
||||
|
||||
server.getHttpClient().post(new URL(URL), reqBody, request -> {
|
||||
request.headers().add(HttpHeaderNames.CONTENT_ENCODING, "gzip");
|
||||
request.headers().add(HttpHeaderNames.ACCEPT, "application/json");
|
||||
request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json");
|
||||
})
|
||||
.whenCompleteAsync((resp, exc) -> {
|
||||
if (logFailedRequests) {
|
||||
if (exc != null) {
|
||||
logger.error("Unable to send metrics to bStats", exc);
|
||||
} else if (resp.getCode() != 429) {
|
||||
logger.error("Got HTTP status code {} when sending metrics to bStats",
|
||||
resp.getCode());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static ByteBuf createResponseBody(JsonObject object) throws IOException {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
try (Writer writer =
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new GZIPOutputStream(new ByteBufOutputStream(buf)), StandardCharsets.UTF_8
|
||||
)
|
||||
)
|
||||
) {
|
||||
VelocityServer.GSON.toJson(object, writer);
|
||||
} catch (IOException e) {
|
||||
buf.release();
|
||||
throw e;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom chart.
|
||||
*/
|
||||
public abstract static class CustomChart {
|
||||
|
||||
// The id of the chart
|
||||
final String chartId;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
CustomChart(String chartId) {
|
||||
if (chartId == null || chartId.isEmpty()) {
|
||||
throw new IllegalArgumentException("ChartId cannot be null or empty!");
|
||||
}
|
||||
this.chartId = chartId;
|
||||
}
|
||||
|
||||
private JsonObject getRequestJsonObject() {
|
||||
JsonObject chart = new JsonObject();
|
||||
chart.addProperty("chartId", chartId);
|
||||
try {
|
||||
JsonObject data = getChartData();
|
||||
if (data == null) {
|
||||
// If the data is null we don't send the chart.
|
||||
return null;
|
||||
}
|
||||
chart.add("data", data);
|
||||
} catch (Throwable t) {
|
||||
if (logFailedRequests) {
|
||||
logger.warn("Failed to get data for custom chart with id {}", chartId, t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return chart;
|
||||
}
|
||||
|
||||
protected abstract JsonObject getChartData() throws Exception;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom simple pie.
|
||||
*/
|
||||
public static class SimplePie extends CustomChart {
|
||||
|
||||
private final Callable<String> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimplePie(String chartId, Callable<String> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
JsonObject data = new JsonObject();
|
||||
String value = callable.call();
|
||||
if (value == null || value.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.addProperty("value", value);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom advanced pie.
|
||||
*/
|
||||
public static class AdvancedPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonObject data = new JsonObject();
|
||||
JsonObject values = new JsonObject();
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
values.addProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.add("values", values);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom drilldown pie.
|
||||
*/
|
||||
public static class DrilldownPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Map<String, Integer>>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getChartData() throws Exception {
|
||||
Map<String, Map<String, Integer>> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean reallyAllSkipped = true;
|
||||
JsonObject data = new JsonObject();
|
||||
JsonObject values = new JsonObject();
|
||||
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
|
||||
JsonObject value = new JsonObject();
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
|
||||
value.addProperty(valueEntry.getKey(), valueEntry.getValue());
|
||||
allSkipped = false;
|
||||
}
|
||||
if (!allSkipped) {
|
||||
reallyAllSkipped = false;
|
||||
values.add(entryValues.getKey(), value);
|
||||
}
|
||||
}
|
||||
if (reallyAllSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.add("values", values);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom single line chart.
|
||||
*/
|
||||
public static class SingleLineChart extends CustomChart {
|
||||
|
||||
private final Callable<Integer> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SingleLineChart(String chartId, Callable<Integer> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
JsonObject data = new JsonObject();
|
||||
int value = callable.call();
|
||||
if (value == 0) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.addProperty("value", value);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom multi line chart.
|
||||
*/
|
||||
public static class MultiLineChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
JsonObject data = new JsonObject();
|
||||
JsonObject values = new JsonObject();
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
values.addProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.add("values", values);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom simple bar chart.
|
||||
*/
|
||||
public static class SimpleBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
JsonObject data = new JsonObject();
|
||||
JsonObject values = new JsonObject();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
JsonArray categoryValues = new JsonArray();
|
||||
categoryValues.add(entry.getValue());
|
||||
values.add(entry.getKey(), categoryValues);
|
||||
}
|
||||
data.add("values", values);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom advanced bar chart.
|
||||
*/
|
||||
public static class AdvancedBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, int[]>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObject getChartData() throws Exception {
|
||||
JsonObject data = new JsonObject();
|
||||
JsonObject values = new JsonObject();
|
||||
Map<String, int[]> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
||||
if (entry.getValue().length == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
JsonArray categoryValues = new JsonArray();
|
||||
for (int categoryValue : entry.getValue()) {
|
||||
categoryValues.add(categoryValue);
|
||||
}
|
||||
values.add(entry.getKey(), categoryValues);
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.add("values", values);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class VelocityMetrics {
|
||||
static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics metricsConfig) {
|
||||
if (!metricsConfig.isFromConfig()) {
|
||||
// Log an informational message.
|
||||
logger.info("Velocity collects metrics and sends them to bStats (https://bstats.org).");
|
||||
logger.info("bStats collects some basic information like how many people use Velocity and");
|
||||
logger.info("their player count. This has no impact on performance and this data does not");
|
||||
logger.info("identify your server in any way. However, you may opt-out by editing your");
|
||||
logger.info("velocity.toml and setting enabled = false in the [metrics] section.");
|
||||
}
|
||||
|
||||
// Load the data
|
||||
String serverUuid = metricsConfig.getId();
|
||||
boolean logFailedRequests = metricsConfig.isLogFailure();
|
||||
// Only start Metrics, if it's enabled in the config
|
||||
if (metricsConfig.isEnabled()) {
|
||||
Metrics metrics = new Metrics("Velocity", serverUuid, logFailedRequests, server);
|
||||
|
||||
metrics.addCustomChart(
|
||||
new Metrics.SingleLineChart("players", server::getPlayerCount)
|
||||
);
|
||||
metrics.addCustomChart(
|
||||
new Metrics.SingleLineChart("managed_servers", () -> server.getAllServers().size())
|
||||
);
|
||||
metrics.addCustomChart(
|
||||
new Metrics.SimplePie("online_mode",
|
||||
() -> server.getConfiguration().isOnlineMode() ? "online" : "offline")
|
||||
);
|
||||
metrics.addCustomChart(new Metrics.SimplePie("velocity_version",
|
||||
() -> server.getVersion().getVersion()));
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
|
||||
Map<String, Map<String, Integer>> map = new HashMap<>();
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
Map<String, Integer> entry = new HashMap<>();
|
||||
entry.put(javaVersion, 1);
|
||||
|
||||
// http://openjdk.java.net/jeps/223
|
||||
// Java decided to change their versioning scheme and in doing so modified the
|
||||
// java.version system property to return $major[.$minor][.$security][-ea], as opposed to
|
||||
// 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1",
|
||||
// otherwise, 9+
|
||||
String majorVersion = javaVersion.split("\\.")[0];
|
||||
String release;
|
||||
|
||||
int indexOf = javaVersion.lastIndexOf('.');
|
||||
|
||||
if (majorVersion.equals("1")) {
|
||||
release = "Java " + javaVersion.substring(0, indexOf);
|
||||
} else {
|
||||
// of course, it really wouldn't be all that simple if they didn't add a quirk, now
|
||||
// would it valid strings for the major may potentially include values such as -ea to
|
||||
// denote a pre release
|
||||
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
|
||||
if (versionMatcher.find()) {
|
||||
majorVersion = versionMatcher.group(0);
|
||||
}
|
||||
release = "Java " + majorVersion;
|
||||
}
|
||||
map.put(release, entry);
|
||||
|
||||
return map;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -203,6 +203,8 @@ public class VelocityServer implements ProxyServer {
|
||||
if (configuration.isQueryEnabled()) {
|
||||
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
||||
}
|
||||
|
||||
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
|
||||
}
|
||||
|
||||
@RequiresNonNull({"pluginManager", "eventManager"})
|
||||
@ -243,6 +245,10 @@ public class VelocityServer implements ProxyServer {
|
||||
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
|
||||
}
|
||||
|
||||
public EventLoopGroup getWorkerGroup() {
|
||||
return this.cm.getWorkerGroup();
|
||||
}
|
||||
|
||||
public Bootstrap initializeGenericBootstrap() {
|
||||
return this.cm.createWorker();
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
|
||||
@ -88,6 +89,9 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
@Table("[query]")
|
||||
private final Query query;
|
||||
|
||||
@Table("[metrics]")
|
||||
private final Metrics metrics;
|
||||
|
||||
@Ignore
|
||||
private @MonotonicNonNull Component motdAsComponent;
|
||||
|
||||
@ -95,16 +99,17 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
private @Nullable Favicon favicon;
|
||||
|
||||
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
||||
Query query) {
|
||||
Query query, Metrics metrics) {
|
||||
this.servers = servers;
|
||||
this.forcedHosts = forcedHosts;
|
||||
this.advanced = advanced;
|
||||
this.query = query;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
|
||||
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
|
||||
Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query) {
|
||||
Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
|
||||
this.bind = bind;
|
||||
this.motd = motd;
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
@ -116,6 +121,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
this.forcedHosts = forcedHosts;
|
||||
this.advanced = advanced;
|
||||
this.query = query;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,6 +365,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
return advanced.isProxyProtocol();
|
||||
}
|
||||
|
||||
public Metrics getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@ -389,7 +399,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
if (!path.toFile().exists()) {
|
||||
getLogger().info("No velocity.toml found, creating one for you...");
|
||||
return new VelocityConfiguration(new Servers(), new ForcedHosts(), new Advanced(),
|
||||
new Query());
|
||||
new Query(), new Metrics());
|
||||
} else {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
toml = new Toml().read(reader);
|
||||
@ -400,13 +410,14 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
ForcedHosts forcedHosts = new ForcedHosts(toml.getTable("forced-hosts"));
|
||||
Advanced advanced = new Advanced(toml.getTable("advanced"));
|
||||
Query query = new Query(toml.getTable("query"));
|
||||
Metrics metrics = new Metrics(toml.getTable("metrics"));
|
||||
byte[] forwardingSecret = toml.getString("forwarding-secret", "5up3r53cr3t")
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
|
||||
.toUpperCase(Locale.US);
|
||||
|
||||
VelocityConfiguration configuration = new VelocityConfiguration(
|
||||
return new VelocityConfiguration(
|
||||
toml.getString("bind", "0.0.0.0:25577"),
|
||||
toml.getString("motd", "&3A Velocity Server"),
|
||||
toml.getLong("show-max-players", 500L).intValue(),
|
||||
@ -417,9 +428,9 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
servers,
|
||||
forcedHosts,
|
||||
advanced,
|
||||
query
|
||||
query,
|
||||
metrics
|
||||
);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
private static String generateRandomString(int length) {
|
||||
@ -699,4 +710,54 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Metrics {
|
||||
|
||||
@Comment({"Whether metrics will be reported to bStats (https://bstats.org).",
|
||||
"bStats collects some basic information, like how many people use Velocity and their",
|
||||
"player count. We recommend keeping bStats enabled, but if you're not comfortable with",
|
||||
"this, you can turn this setting off. There is no performance penalty associated with",
|
||||
"having metrics enabled, and data sent to bStats can't identify your server."})
|
||||
@ConfigKey("enabled")
|
||||
private boolean enabled = true;
|
||||
|
||||
@Comment("A unique, anonymous ID to identify this proxy with.")
|
||||
@ConfigKey("id")
|
||||
private String id = UUID.randomUUID().toString();
|
||||
|
||||
@ConfigKey("log-failure")
|
||||
private boolean logFailure = false;
|
||||
|
||||
@Ignore
|
||||
private boolean fromConfig;
|
||||
|
||||
public Metrics() {
|
||||
this.fromConfig = false;
|
||||
}
|
||||
|
||||
public Metrics(Toml toml) {
|
||||
if (toml != null) {
|
||||
this.enabled = toml.getBoolean("enabled", false);
|
||||
this.id = toml.getString("id", UUID.randomUUID().toString());
|
||||
this.logFailure = toml.getBoolean("log-failure", false);
|
||||
this.fromConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isLogFailure() {
|
||||
return logFailure;
|
||||
}
|
||||
|
||||
public boolean isFromConfig() {
|
||||
return fromConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +164,10 @@ public final class ConnectionManager {
|
||||
return bossGroup;
|
||||
}
|
||||
|
||||
public EventLoopGroup getWorkerGroup() {
|
||||
return workerGroup;
|
||||
}
|
||||
|
||||
public ServerChannelInitializerHolder getServerChannelInitializer() {
|
||||
return this.serverChannelInitializer;
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
package com.velocitypowered.proxy.network.http;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpContentCompressor;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
@ -16,6 +20,7 @@ import io.netty.handler.ssl.SslHandler;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
public class NettyHttpClient {
|
||||
@ -33,13 +38,7 @@ public class NettyHttpClient {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an HTTP GET request to the specified URL.
|
||||
* @param url the URL to fetch
|
||||
* @param loop the event loop to use
|
||||
* @return a future representing the response
|
||||
*/
|
||||
public CompletableFuture<SimpleHttpResponse> get(URL url, EventLoop loop) {
|
||||
private ChannelFuture establishConnection(URL url, EventLoop loop) {
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
boolean ssl = url.getProtocol().equals("https");
|
||||
@ -48,8 +47,7 @@ public class NettyHttpClient {
|
||||
}
|
||||
|
||||
InetSocketAddress address = InetSocketAddress.createUnresolved(host, port);
|
||||
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap(loop)
|
||||
return server.initializeGenericBootstrap(loop)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
@ -65,16 +63,33 @@ public class NettyHttpClient {
|
||||
ch.pipeline().addLast("ssl", new SslHandler(engine));
|
||||
}
|
||||
ch.pipeline().addLast("http", new HttpClientCodec());
|
||||
ch.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
|
||||
}
|
||||
})
|
||||
.connect(address)
|
||||
.connect(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an HTTP GET request to the specified URL.
|
||||
* @param url the URL to fetch
|
||||
* @param loop the event loop to use
|
||||
* @return a future representing the response
|
||||
*/
|
||||
public CompletableFuture<SimpleHttpResponse> get(URL url, EventLoop loop) {
|
||||
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
|
||||
establishConnection(url, loop)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
Channel channel = future.channel();
|
||||
|
||||
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
|
||||
|
||||
String pathAndQuery = url.getPath();
|
||||
if (url.getQuery() != null && url.getQuery().length() > 0) {
|
||||
pathAndQuery += "?" + url.getQuery();
|
||||
}
|
||||
|
||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
|
||||
HttpMethod.GET, url.getPath() + "?" + url.getQuery());
|
||||
HttpMethod.GET, pathAndQuery);
|
||||
request.headers().add(HttpHeaderNames.HOST, url.getHost());
|
||||
request.headers().add(HttpHeaderNames.USER_AGENT, userAgent);
|
||||
channel.writeAndFlush(request, channel.voidPromise());
|
||||
@ -84,4 +99,57 @@ public class NettyHttpClient {
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an HTTP POST request to the specified URL.
|
||||
* @param url the URL to fetch
|
||||
* @param body the body to post
|
||||
* @param decorator a consumer that can modify the request as required
|
||||
* @return a future representing the response
|
||||
*/
|
||||
public CompletableFuture<SimpleHttpResponse> post(URL url, ByteBuf body,
|
||||
Consumer<HttpRequest> decorator) {
|
||||
return post(url, server.getWorkerGroup().next(), body, decorator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an HTTP POST request to the specified URL.
|
||||
* @param url the URL to fetch
|
||||
* @param loop the event loop to use
|
||||
* @param body the body to post
|
||||
* @param decorator a consumer that can modify the request as required
|
||||
* @return a future representing the response
|
||||
*/
|
||||
public CompletableFuture<SimpleHttpResponse> post(URL url, EventLoop loop, ByteBuf body,
|
||||
Consumer<HttpRequest> decorator) {
|
||||
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
|
||||
establishConnection(url, loop)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
Channel channel = future.channel();
|
||||
|
||||
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
|
||||
|
||||
String pathAndQuery = url.getPath();
|
||||
if (url.getQuery() != null && url.getQuery().length() > 0) {
|
||||
pathAndQuery += "?" + url.getQuery();
|
||||
}
|
||||
|
||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
|
||||
HttpMethod.POST, pathAndQuery, body);
|
||||
request.headers().add(HttpHeaderNames.HOST, url.getHost());
|
||||
request.headers().add(HttpHeaderNames.USER_AGENT, userAgent);
|
||||
request.headers().add(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());
|
||||
decorator.accept(request);
|
||||
|
||||
System.out.println(request);
|
||||
|
||||
channel.writeAndFlush(request, channel.voidPromise());
|
||||
} else {
|
||||
body.release();
|
||||
reply.completeExceptionally(future.cause());
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren