13
0
geforkt von Mirrors/Velocity

Merge branch 'metrics'

Dieser Commit ist enthalten in:
Andrew Steinborn 2019-05-26 18:10:56 -04:00
Commit deeb068825
5 geänderte Dateien mit 780 neuen und 18 gelöschten Zeilen

Datei anzeigen

@ -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;
}));
}
}
}
}

Datei anzeigen

@ -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();
}

Datei anzeigen

@ -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;
}
}
}

Datei anzeigen

@ -164,6 +164,10 @@ public final class ConnectionManager {
return bossGroup;
}
public EventLoopGroup getWorkerGroup() {
return workerGroup;
}
public ServerChannelInitializerHolder getServerChannelInitializer() {
return this.serverChannelInitializer;
}

Datei anzeigen

@ -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;
}
}