The server API provides interfaces for representing backend Minecraft servers and managing their registration with the proxy.
Overview
The server API consists of three main components:
ServerInfo - Basic server information (name and address)
RegisteredServer - A server registered with the proxy
ServerConnection - An active player connection to a server
Source: pkg/edition/java/proxy/server.go
ServerInfo
ServerInfo represents basic information about a backend server.
Creating ServerInfo
import (
"go.minekube.com/gate/pkg/edition/java/proxy"
"go.minekube.com/gate/pkg/util/netutil"
)
// Parse server address
addr, err := netutil.Parse("localhost:25566", "tcp")
if err != nil {
return err
}
// Create server info
info := proxy.NewServerInfo("lobby", addr)
The server name (e.g., “lobby”, “survival”).
The server network address.
ServerInfo Methods
// Get server name
name := info.Name()
// Get server address
addr := info.Addr()
log.Info("server",
"name", name,
"network", addr.Network(),
"address", addr.String(),
)
Returns the server address.
Comparing ServerInfo
Check if two ServerInfo instances are equal:
// Compare two ServerInfo instances
if proxy.ServerInfoEqual(info1, info2) {
log.Info("servers are the same")
}
First ServerInfo to compare.
Second ServerInfo to compare.
Returns true if both ServerInfo instances have the same name, address string, and network. Always returns false if either is nil.
RegisteredServer
A RegisteredServer represents a backend server that has been registered with the proxy.
Getting RegisteredServer
// Get a registered server by name
server := p.Server("lobby")
if server == nil {
log.Info("server not found")
return
}
// Get all registered servers
allServers := p.Servers()
for _, srv := range allServers {
log.Info("server", "name", srv.ServerInfo().Name())
}
RegisteredServer Methods
// Get server info
info := server.ServerInfo()
log.Info("server details",
"name", info.Name(),
"addr", info.Addr(),
)
// Get players on this server (on THIS proxy only)
players := server.Players()
count := players.Len()
log.Info("players on server", "count", count)
// Iterate through players
players.Range(func(p proxy.Player) bool {
log.Info("player", "name", p.Username())
return true // continue iteration
})
Returns the server’s information.
Returns the players connected to this server on THIS proxy instance only.
Comparing RegisteredServer
Check if two RegisteredServer instances are equal:
// Compare two RegisteredServer instances
if proxy.RegisteredServerEqual(server1, server2) {
log.Info("servers are the same")
}
Returns true if both servers have equal ServerInfo. Always returns false if either is nil.
Server Registration
Registering Servers
Register a backend server with the proxy:
// Create server info
addr, _ := netutil.Parse("play.example.com:25565", "tcp")
info := proxy.NewServerInfo("survival", addr)
// Register the server
registered, err := p.Register(info)
if err == proxy.ErrServerAlreadyExists {
log.Info("server already registered")
// err also returns the existing server as 'registered'
} else if err != nil {
return fmt.Errorf("registration failed: %w", err)
}
log.Info("server registered", "name", registered.ServerInfo().Name())
The server information to register. Must have a valid name and address.
The registered server instance.
Returns ErrServerAlreadyExists if a server with the same name is already registered. The existing server is also returned.
Unregistering Servers
Remove a server from the proxy:
// Unregister by ServerInfo
if p.Unregister(info) {
log.Info("server unregistered", "name", info.Name())
} else {
log.Info("server not found or info doesn't match")
}
The ServerInfo that exactly matches the registered server.
Returns true if the server was found and unregistered.
The ServerInfo must exactly match the registered server’s info (same name and address). Use ServerInfoEqual() to verify.
ServerConnection
ServerConnection represents an active connection from a player to a backend server.
Source: pkg/edition/java/proxy/server.go:208
Getting ServerConnection
// Get current server connection from player
conn := player.CurrentServer()
if conn == nil {
log.Info("player not connected to any server")
return
}
ServerConnection Methods
// Get the server
server := conn.Server()
log.Info("connected to", "server", server.ServerInfo().Name())
// Get the player
player := conn.Player()
log.Info("player on server", "username", player.Username())
Returns the server that this connection is connected to.
Returns the player associated with this connection.
Sending Plugin Messages
Send plugin messages to the backend server:
import "go.minekube.com/gate/pkg/edition/java/proxy/message"
// Get current server connection
conn := player.CurrentServer()
if conn == nil {
return
}
// Send plugin message to the backend server
channel := message.MinecraftNamespace("mybrand")
data := []byte{...}
err := conn.SendPluginMessage(channel, data)
if err != nil {
log.Error(err, "failed to send plugin message")
}
identifier
message.ChannelIdentifier
required
The plugin message channel identifier.
The message data to send.
This sends the message to the backend server, not to the player’s client. To send to the client, use player.SendPluginMessage().
Custom Server Dialer
Implement custom connection logic for servers:
import (
"context"
"net"
"go.minekube.com/gate/pkg/edition/java/proxy"
)
// Custom server info with custom dialer
type CustomServerInfo struct {
proxy.ServerInfo
}
func (s *CustomServerInfo) Dial(ctx context.Context, player proxy.Player) (net.Conn, error) {
// Custom connection logic
log.Info("custom dial", "player", player.Username())
// Example: Connect through a SOCKS proxy
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "tcp", s.Addr().String())
}
// Register server with custom dialer
baseInfo := proxy.NewServerInfo("custom", addr)
customInfo := &CustomServerInfo{ServerInfo: baseInfo}
registered, err := p.Register(customInfo)
Context for the dial operation.
The player initiating the connection.
The established network connection to the server.
Custom Handshake Address
Customize the server address sent in the handshake packet:
// Custom server info with custom handshake address
type CustomHandshakeServer struct {
proxy.ServerInfo
}
func (s *CustomHandshakeServer) HandshakeAddr(
defaultPlayerVirtualHost string,
player proxy.Player,
) string {
// Customize the handshake address
// This is useful for IP forwarding or custom routing
return fmt.Sprintf("%s\x00%s",
defaultPlayerVirtualHost,
player.RemoteAddr().String(),
)
}
The default virtual host from the player’s connection.
The player connecting to the server.
The modified server address to use in the handshake packet.
Working with Players List
The Players interface provides safe concurrent access to server players:
// Get players on a server
players := server.Players()
// Get count
count := players.Len()
// Iterate all players
players.Range(func(p proxy.Player) bool {
// Send message to each player
p.SendMessage(&component.Text{
Content: "Server announcement!",
})
return true // continue iteration, false to break
})
// Convert to slice
playerSlice := proxy.PlayersToSlice[proxy.Player](players)
for _, p := range playerSlice {
log.Info("player", "name", p.Username())
}
Returns the number of players.
Iterates through players. The callback returns false to stop iteration.
Broadcasting Messages
Broadcast plugin messages to all players on a server:
import "go.minekube.com/gate/pkg/edition/java/proxy/message"
// Get all players on the server as message sinks
players := server.Players()
sinks := proxy.PlayersToSlice[message.ChannelMessageSink](players)
// Broadcast plugin message
channel := message.MinecraftNamespace("broadcast")
data := []byte("Hello everyone!")
proxy.BroadcastPluginMessage(sinks, channel, data)
Complete Example
package main
import (
"context"
"fmt"
"time"
"go.minekube.com/common/minecraft/component"
"go.minekube.com/gate/pkg/edition/java/proxy"
"go.minekube.com/gate/pkg/util/netutil"
)
func setupServers(p *proxy.Proxy) error {
// Register multiple servers
servers := map[string]string{
"lobby": "localhost:25566",
"survival": "localhost:25567",
"creative": "localhost:25568",
}
for name, addrStr := range servers {
addr, err := netutil.Parse(addrStr, "tcp")
if err != nil {
return fmt.Errorf("invalid address for %s: %w", name, err)
}
info := proxy.NewServerInfo(name, addr)
if _, err := p.Register(info); err != nil {
return fmt.Errorf("failed to register %s: %w", name, err)
}
fmt.Printf("Registered server: %s (%s)\n", name, addrStr)
}
return nil
}
func monitorServers(p *proxy.Proxy) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
servers := p.Servers()
fmt.Printf("\n=== Server Status ===\n")
for _, srv := range servers {
info := srv.ServerInfo()
players := srv.Players()
fmt.Printf("%s (%s): %d players\n",
info.Name(),
info.Addr().String(),
players.Len(),
)
// List players on this server
players.Range(func(p proxy.Player) bool {
fmt.Printf(" - %s\n", p.Username())
return true
})
}
}
}
func handleServerSwitch(player proxy.Player, targetName string, p *proxy.Proxy) error {
// Get target server
target := p.Server(targetName)
if target == nil {
return fmt.Errorf("server %s not found", targetName)
}
// Check current server
current := player.CurrentServer()
if current != nil && proxy.RegisteredServerEqual(current.Server(), target) {
player.SendMessage(&component.Text{
Content: "You are already on this server!",
})
return nil
}
// Create and execute connection request
request := player.CreateConnectionRequest(target)
result, err := request.Connect(context.Background())
if err != nil {
return fmt.Errorf("connection error: %w", err)
}
if result.Successful() {
player.SendMessage(&component.Text{
Content: fmt.Sprintf("Connected to %s", targetName),
})
} else {
player.SendMessage(&component.Text{
Content: "Failed to connect to server",
})
}
return nil
}
See Also