mirror of
https://github.com/PaperMC/Velocity.git
synced 2024-11-21 03:11:38 +08:00
Introduce a fluent connection request API.
This commit is contained in:
parent
fbdaae5ac7
commit
0ba85fe83f
@ -0,0 +1,84 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a connection request. A connection request is created using {@link Player#createConnectionRequest(ServerInfo)}
|
||||
* and is used to allow a plugin to compose and request a connection to another Minecraft server using a fluent API.
|
||||
*/
|
||||
public interface ConnectionRequestBuilder {
|
||||
/**
|
||||
* Returns the server that this connection request represents.
|
||||
* @return the server this request will connect to
|
||||
*/
|
||||
ServerInfo getServer();
|
||||
|
||||
/**
|
||||
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user
|
||||
* has logged on. No messages will be communicated to the client: the user is responsible for all error handling.
|
||||
* @return a {@link CompletableFuture} representing the status of this connection
|
||||
*/
|
||||
CompletableFuture<Result> connect();
|
||||
|
||||
/**
|
||||
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error
|
||||
* handling code to notify the user.
|
||||
*/
|
||||
void fireAndForget();
|
||||
|
||||
/**
|
||||
* Represents the result of a connection request.
|
||||
*/
|
||||
interface Result {
|
||||
/**
|
||||
* Determines whether or not the connection request was successful.
|
||||
* @return whether or not the request succeeded
|
||||
*/
|
||||
default boolean isSuccessful() {
|
||||
return getStatus() == Status.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status associated with this result.
|
||||
* @return the status for this result
|
||||
*/
|
||||
Status getStatus();
|
||||
|
||||
/**
|
||||
* Returns a reason for the failure to connect to the server. None may be provided.
|
||||
* @return the reason why the user could not connect to the server
|
||||
*/
|
||||
Optional<Component> getReason();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the status of a connection request initiated by a {@link ConnectionRequestBuilder}.
|
||||
*/
|
||||
enum Status {
|
||||
/**
|
||||
* The player was successfully connected to the server.
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* The player is already connected to this server.
|
||||
*/
|
||||
ALREADY_CONNECTED,
|
||||
/**
|
||||
* The connection is already in progress.
|
||||
*/
|
||||
CONNECTION_IN_PROGRESS,
|
||||
/**
|
||||
* A plugin has cancelled this connection.
|
||||
*/
|
||||
CONNECTION_CANCELLED,
|
||||
/**
|
||||
* The server disconnected the user. A reason may be provided in the {@link Result} object.
|
||||
*/
|
||||
SERVER_DISCONNECTED
|
||||
}
|
||||
|
||||
}
|
@ -57,4 +57,11 @@ public interface Player {
|
||||
* @param position the position for the message
|
||||
*/
|
||||
void sendMessage(@Nonnull Component component, @Nonnull MessagePosition position);
|
||||
|
||||
/**
|
||||
* Creates a new connection request so that the player can connect to another server.
|
||||
* @param info the server to connect to
|
||||
* @return a new connection request
|
||||
*/
|
||||
ConnectionRequestBuilder createConnectionRequest(@Nonnull ServerInfo info);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.IPForwardingMode;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
@ -65,6 +66,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect disconnect = (Disconnect) packet;
|
||||
connection.disconnect();
|
||||
|
||||
// Do we have an outstanding notification? If so, fulfill it.
|
||||
ServerConnection.ConnectionNotifier n = connection.getMinecraftConnection().getChannel()
|
||||
.pipeline().get(ServerConnection.ConnectionNotifier.class);
|
||||
if (n != null) {
|
||||
n.getResult().complete(ConnectionRequestResults.forDisconnect(disconnect));
|
||||
}
|
||||
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect);
|
||||
} else if (packet instanceof SetCompression) {
|
||||
SetCompression sc = (SetCompression) packet;
|
||||
@ -80,6 +89,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
// The previous server connection should become obsolete.
|
||||
existingConnection.disconnect();
|
||||
}
|
||||
|
||||
// Do we have an outstanding notification? If so, fulfill it.
|
||||
ServerConnection.ConnectionNotifier n = connection.getMinecraftConnection().getChannel()
|
||||
.pipeline().get(ServerConnection.ConnectionNotifier.class);
|
||||
if (n != null) {
|
||||
n.onComplete();
|
||||
connection.getMinecraftConnection().getChannel().pipeline().remove(n);
|
||||
}
|
||||
|
||||
connection.getMinecraftConnection().setSessionHandler(new BackendPlaySessionHandler(connection));
|
||||
connection.getProxyPlayer().setConnectedServer(connection);
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.proxy.config.IPForwardingMode;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
@ -17,6 +19,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.network.Connections.FRAME_DECODER;
|
||||
@ -28,6 +31,8 @@ import static com.velocitypowered.network.Connections.READ_TIMEOUT;
|
||||
import static com.velocitypowered.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
|
||||
|
||||
public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
static final String CONNECTION_NOTIFIER = "connection-notifier";
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
@ -39,7 +44,8 @@ public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap()
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
@ -49,7 +55,8 @@ public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
|
||||
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND));
|
||||
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND))
|
||||
.addLast(CONNECTION_NOTIFIER, new ConnectionNotifier(result));
|
||||
|
||||
MinecraftConnection connection = new MinecraftConnection(ch);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
@ -68,10 +75,11 @@ public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
minecraftConnection.setSessionHandler(new LoginSessionHandler(ServerConnection.this));
|
||||
startHandshake();
|
||||
} else {
|
||||
proxyPlayer.handleConnectionException(serverInfo, future.cause());
|
||||
result.completeExceptionally(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private String createBungeeForwardingAddress() {
|
||||
@ -131,4 +139,25 @@ public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
|
||||
}
|
||||
|
||||
static class ConnectionNotifier extends ChannelInboundHandlerAdapter {
|
||||
private final CompletableFuture<ConnectionRequestBuilder.Result> result;
|
||||
|
||||
public ConnectionNotifier(CompletableFuture<ConnectionRequestBuilder.Result> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onComplete() {
|
||||
result.complete(ConnectionRequestResults.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
result.completeExceptionally(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,8 +83,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
Chat chat = (Chat) packet;
|
||||
if (chat.getMessage().equals("/connect")) {
|
||||
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25566));
|
||||
ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer());
|
||||
connection.connect();
|
||||
player.createConnectionRequest(info).fireAndForget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,13 @@ package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
@ -28,6 +31,7 @@ import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
private static final PlainComponentSerializer PASS_THRU_TRANSLATE = new PlainComponentSerializer((c) -> "", TranslatableComponent::key);
|
||||
@ -58,7 +62,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
|
||||
@Override
|
||||
public Optional<ServerInfo> getCurrentServer() {
|
||||
return Optional.empty();
|
||||
return connectedServer != null ? Optional.of(connectedServer.getServerInfo()) : Optional.empty();
|
||||
}
|
||||
|
||||
public GameProfile getProfile() {
|
||||
@ -102,6 +106,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connection.write(chat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRequestBuilder createConnectionRequest(@Nonnull ServerInfo info) {
|
||||
return new ConnectionRequestBuilderImpl(info);
|
||||
}
|
||||
|
||||
public ServerConnection getConnectedServer() {
|
||||
return connectedServer;
|
||||
}
|
||||
@ -118,8 +127,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
String error = ThrowableUtils.briefDescription(throwable);
|
||||
String userMessage;
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: exception occurred in connection to {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception in server " + info.getName();
|
||||
} else {
|
||||
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + info.getName();
|
||||
}
|
||||
handleConnectionException(info, TextComponent.builder()
|
||||
@ -151,7 +162,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ServerInfo> getNextServerToTry() {
|
||||
Optional<ServerInfo> getNextServerToTry() {
|
||||
List<String> serversToTry = VelocityServer.getServer().getConfiguration().getAttemptConnectionOrder();
|
||||
if (tryIndex >= serversToTry.size()) {
|
||||
return Optional.empty();
|
||||
@ -162,7 +173,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return VelocityServer.getServer().getServers().getServer(toTryName);
|
||||
}
|
||||
|
||||
public void connect(ServerInfo info) {
|
||||
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
|
||||
if (connectionInFlight != null) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS)
|
||||
);
|
||||
}
|
||||
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED)
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise, initiate the connection.
|
||||
ServerConnection connection = new ServerConnection(request.getServer(), this, VelocityServer.getServer());
|
||||
return connection.connect();
|
||||
}
|
||||
|
||||
void connect(ServerInfo info) {
|
||||
Preconditions.checkNotNull(info, "info");
|
||||
Preconditions.checkState(connectionInFlight == null, "A connection is already active!");
|
||||
ServerConnection connection = new ServerConnection(info, this, VelocityServer.getServer());
|
||||
@ -194,4 +223,48 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
public String toString() {
|
||||
return "[connected player] " + getProfile().getName() + " (" + getRemoteAddress() + ")";
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
private final ServerInfo info;
|
||||
|
||||
public ConnectionRequestBuilderImpl(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServer() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Result> connect() {
|
||||
return ConnectedPlayer.this.connect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireAndForget() {
|
||||
connect()
|
||||
.whenComplete((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
handleConnectionException(info, throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status.getStatus()) {
|
||||
case ALREADY_CONNECTED:
|
||||
sendMessage(ConnectionMessages.ALREADY_CONNECTED);
|
||||
break;
|
||||
case CONNECTION_IN_PROGRESS:
|
||||
sendMessage(ConnectionMessages.IN_PROGRESS);
|
||||
break;
|
||||
case CONNECTION_CANCELLED:
|
||||
// Ignored; the plugin probably already handled this.
|
||||
break;
|
||||
case SERVER_DISCONNECTED:
|
||||
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +140,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
inbound.setAssociation(player);
|
||||
inbound.setState(StateRegistry.PLAY);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
player.connect(toTry.get());
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package com.velocitypowered.proxy.connection.util;
|
||||
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
public class ConnectionMessages {
|
||||
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
|
||||
|
||||
private ConnectionMessages() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.velocitypowered.proxy.connection.util;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ConnectionRequestResults {
|
||||
public static final ConnectionRequestBuilder.Result SUCCESSFUL = plainResult(ConnectionRequestBuilder.Status.SUCCESS);
|
||||
|
||||
private ConnectionRequestResults() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ConnectionRequestBuilder.Result plainResult(ConnectionRequestBuilder.Status status) {
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect) {
|
||||
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.of(deserialized);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user