Documentation
¶
Overview ¶
Package rconclient is a high-level RCON client built on github.com/cbrgm/rcon.
It mirrors the shape of net/http: a DefaultClient, package-level helper functions that delegate to it, and an instantiable Client that is safe for concurrent use. For repeated commands to one server, open a Session.
Most servers work with the default multi-packet response mode. For servers that mishandle the multi-packet terminator sentinel, use WithSinglePacket; for servers that also split large replies across packets (such as Project Zomboid), use WithReadUntilIdle.
Example ¶
For a one-off command, the package-level Execute dials, authenticates, runs the command, and closes the connection, all against DefaultClient.
package main
import (
"context"
"fmt"
"log"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
out, err := rconclient.Execute(context.Background(), "127.0.0.1:25575", "password", "list")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output:
Example (ErrorHandling) ¶
Classify failures with the re-exported sentinels, so you can tell a rejected password from a bad command or a transport problem without importing the core package. errors.Is matches through wrapping.
package main
import (
"context"
"errors"
"fmt"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
_, err := rconclient.Execute(context.Background(), "127.0.0.1:25575", "wrong-password", "list")
switch {
case err == nil:
fmt.Println("ok")
case errors.Is(err, rconclient.ErrAuthFailed):
fmt.Println("wrong password")
case errors.Is(err, rconclient.ErrCommandTooLong):
fmt.Println("command too long")
default:
fmt.Println("connection problem:", err)
}
}
Output:
Example (RoundTrip) ¶
A complete round trip against an in-process server: dial through the default client, run a command, and print the reply.
package main
import (
"context"
"fmt"
"io"
"log"
"net"
"github.com/cbrgm/rcon/rconclient"
"github.com/cbrgm/rcon/rconserver"
)
func main() {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
srv := &rconserver.Server{
Password: "secret",
Handler: rconserver.HandlerFunc(func(w rconserver.ResponseWriter, r *rconserver.Request) {
_, _ = io.WriteString(w, "3/20 players online")
}),
}
go func() { _ = srv.Serve(ln) }()
defer srv.Close()
out, err := rconclient.Execute(context.Background(), ln.Addr().String(), "secret", "list")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: 3/20 players online
Index ¶
Examples ¶
Constants ¶
const ( // DefaultTimeout bounds a single command round-trip. DefaultTimeout = 10 * time.Second // DefaultDialTimeout bounds establishing the connection. DefaultDialTimeout = 5 * time.Second )
Default option values.
Variables ¶
var ( // ErrAuthFailed indicates the server rejected the password. ErrAuthFailed = rcon.ErrAuthFailed // ErrCommandEmpty indicates an empty command was supplied. ErrCommandEmpty = rcon.ErrCommandEmpty // ErrCommandTooLong indicates the command exceeded the maximum length. ErrCommandTooLong = rcon.ErrCommandTooLong )
These sentinels are re-exported from github.com/cbrgm/rcon/rcon so callers can classify errors without importing the core package. errors.Is matches either name.
var DefaultClient = New()
DefaultClient is used by the package-level Execute helper.
Functions ¶
Types ¶
type BackoffFunc ¶
BackoffFunc computes the delay before retry attempt n (1-based).
func ExponentialBackoff ¶
func ExponentialBackoff(base, max time.Duration) BackoffFunc
ExponentialBackoff returns a BackoffFunc that doubles from base up to max, with full jitter.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a reusable, concurrency-safe high-level RCON client. Construct it with New; the zero value is not usable. A Client is safe for use by multiple goroutines, mirroring *http.Client.
Example ¶
Construct a Client to configure timeouts, retries, or logging once, then reuse it across goroutines. It mirrors the shape of http.Client.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
client := rconclient.New(
rconclient.WithTimeout(10*time.Second),
rconclient.WithRetry(3, rconclient.ExponentialBackoff(100*time.Millisecond, 2*time.Second)),
)
out, err := client.Execute(context.Background(), "127.0.0.1:25575", "password", "list")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output:
Example (CustomDialer) ¶
WithDialer runs the client over any transport you supply, for example a dialer with custom timeouts, a SOCKS proxy, or a TLS tunnel, instead of plain TCP.
package main
import (
"context"
"fmt"
"log"
"net"
"time"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
dialer := &net.Dialer{Timeout: 2 * time.Second, KeepAlive: 30 * time.Second}
client := rconclient.New(
rconclient.WithDialer(func(ctx context.Context, address string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", address)
}),
)
out, err := client.Execute(context.Background(), "127.0.0.1:25575", "password", "list")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output:
func (*Client) Dial ¶
Dial opens a Session to address, authenticated with password.
Example ¶
For many commands against the same server, open a Session instead of dialing per command. It keeps one authenticated connection and reconnects on drop.
package main
import (
"context"
"fmt"
"log"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
ctx := context.Background()
client := rconclient.New()
session, err := client.Dial(ctx, "127.0.0.1:25575", "password")
if err != nil {
log.Fatal(err)
}
defer session.Close()
for _, cmd := range []string{"list", "seed", "save-all"} {
out, err := session.Execute(ctx, cmd)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
}
Output:
func (*Client) Execute ¶
Execute dials address, authenticates with password, runs command, and closes the connection. It is the one-shot path; for many commands use Dial + Session. The whole call, including any retries, is bounded by the Client's timeout (see WithTimeout). Connection-level failures (dial error, io.EOF, net.Error) are retried per WithRetry; rcon.ErrAuthFailed, ErrCommandEmpty, and ErrCommandTooLong are never retried.
type DialFunc ¶
DialFunc establishes a raw connection to address. It lets the client run over TLS, an SSH tunnel, a proxy, or any custom transport instead of plain TCP.
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithDialTimeout ¶
WithDialTimeout sets the connection dial timeout.
func WithDialer ¶
WithDialer makes the Client establish connections with d and wrap them via rcon.Open, instead of dialing plain TCP itself. When a dialer is set, WithDialTimeout does not apply, since d owns connection setup. The Client's WithTimeout still bounds the whole call.
d must return a non-nil connection when it returns a nil error. A dial error is retried by WithRetry only when it satisfies net.Error, so wrap transport errors accordingly if you want them retried.
func WithLogger ¶
WithLogger sets the structured logger. The default logger discards output.
func WithReadUntilIdle ¶ added in v0.2.0
WithReadUntilIdle reads response packets until the connection is quiet for window, concatenating their bodies, instead of using the terminator sentinel. It handles servers that mishandle that sentinel but still split large responses across packets, such as Project Zomboid. A window of 0 or less uses rcon.DefaultIdleWindow. See rcon.WithReadUntilIdle for the tradeoffs.
Example ¶
WithReadUntilIdle reads reply packets until the connection goes quiet, for servers like Project Zomboid that split large replies but mishandle the terminator. A window of 0 uses the default.
package main
import (
"context"
"fmt"
"log"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
client := rconclient.New(rconclient.WithReadUntilIdle(0))
out, err := client.Execute(context.Background(), "127.0.0.1:27015", "changeme", "help")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output:
func WithRetry ¶
func WithRetry(attempts int, backoff BackoffFunc) Option
WithRetry sets how many attempts a one-shot Execute makes on connection-level failures and the backoff between them. Attempts of 0 or 1 disables retry.
func WithSinglePacket ¶ added in v0.2.0
func WithSinglePacket() Option
WithSinglePacket makes the client read exactly one response packet per command instead of using the multi-packet terminator sentinel. Use it for servers that mishandle that sentinel and never split a response across packets. Multi-packet mode (the default) is correct for Source-engine servers, whose large responses span several packets and would otherwise be truncated. For servers that mishandle the sentinel yet still split large responses (e.g. Project Zomboid), prefer WithReadUntilIdle, which takes precedence if both are set.
Example ¶
WithSinglePacket reads one reply packet per command, for servers that mishandle the multi-packet terminator and never split a response.
package main
import (
"context"
"fmt"
"log"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
client := rconclient.New(rconclient.WithSinglePacket())
out, err := client.Execute(context.Background(), "127.0.0.1:27015", "password", "players")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output:
func WithTimeout ¶
WithTimeout sets the overall per-command deadline.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is a live authenticated connection to one server, for issuing many commands. Unlike Client.Execute, it keeps the connection open. A Session is not safe for concurrent use; use one per goroutine.
func (*Session) Execute ¶
Execute runs command on the session's connection. On a retryable connection-level error (dial error, io.EOF, net.Error), it reconnects once and retries the command; auth and command-validation errors are returned immediately. The whole call, including the reconnect, is bounded by the Client's timeout (see WithTimeout).
Example ¶
A Session keeps one authenticated connection open and reconnects on drop, so repeated commands avoid re-dialing and re-authenticating each time.
package main
import (
"context"
"fmt"
"log"
"github.com/cbrgm/rcon/rconclient"
)
func main() {
ctx := context.Background()
session, err := rconclient.New().Dial(ctx, "127.0.0.1:25575", "password")
if err != nil {
log.Fatal(err)
}
defer session.Close()
for _, cmd := range []string{"list", "seed"} {
out, err := session.Execute(ctx, cmd)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
}
Output: