Documentation
¶
Overview ¶
Package rconhttp serves RCON over HTTP with the standard net/http server.
It turns HTTP requests into RCON commands against a backend, so a web frontend, a script, or curl can administer a game server without speaking the RCON wire protocol. It is built on github.com/cbrgm/rcon/rconclient and, like the rest of the module, depends only on the standard library.
The zero value of Handler is not usable; construct one with New.
The handler executes administrative commands: always place it behind your own authentication middleware and serve it over TLS. Never expose it unauthenticated.
Example ¶
Serve a single fixed backend. Wrap the handler with your own auth middleware and serve it over TLS; it runs administrative commands.
package main
import (
"net/http"
"os"
"github.com/cbrgm/rcon/rconhttp"
)
func main() {
h := rconhttp.New(rconhttp.Backend{
Addr: "127.0.0.1:25575",
Password: os.Getenv("RCON_PASSWORD"),
})
defer h.Close()
mux := http.NewServeMux()
mux.Handle("POST /command", h)
_ = http.ListenAndServe(":8080", mux)
}
Output:
Example (HttpRoundTrip) ¶
A complete round trip: an RCON server, exposed over HTTP by the handler, and a client that POSTs a command and reads the JSON result.
package main
import (
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httptest"
"strings"
"github.com/cbrgm/rcon/rconhttp"
"github.com/cbrgm/rcon/rconserver"
)
func main() {
// A backend RCON server on an ephemeral port.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
backend := &rconserver.Server{
Password: "secret",
Handler: rconserver.HandlerFunc(func(w rconserver.ResponseWriter, r *rconserver.Request) {
_, _ = io.WriteString(w, "3/20 players online")
}),
}
go func() { _ = backend.Serve(ln) }()
defer backend.Close()
// Expose it over HTTP, fronted by a test server.
h := rconhttp.New(rconhttp.Backend{Addr: ln.Addr().String(), Password: "secret"})
defer h.Close()
front := httptest.NewServer(h)
defer front.Close()
// The command is the request body; the reply comes back as JSON.
resp, err := http.Post(front.URL, "text/plain", strings.NewReader("list"))
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Print(string(body))
}
Output: {"command":"list","response":"3/20 players online"}
Index ¶
Examples ¶
Constants ¶
const DefaultIdleTimeout = 5 * time.Minute
DefaultIdleTimeout is how long a cached backend session may sit idle before it is closed and evicted.
Variables ¶
ErrUnauthorized, when returned by a Resolver, maps to HTTP 401.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend struct {
// Addr is the RCON server address as "host:port".
Addr string
// Password is the RCON password.
Password string
}
Backend is an RCON server a Handler talks to. The password is held server-side or produced by a Resolver; it never has to cross the HTTP boundary.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler executes RCON commands over reused, auto-reconnecting sessions. It implements http.Handler and io.Closer.
Requests to the same backend serialize, since RCON has no request multiplexing; different backends run independently. The handler is path-agnostic: it treats any accepted request as "run one command", so you decide where to mount it. It executes administrative commands, so place it behind your own authentication and TLS.
func New ¶
New returns a Handler. By default it targets backend; pass WithResolver to switch backends per request. New performs no I/O; sessions are dialed lazily and cached. Call Close to release them. A request whose resolved backend has no address (an empty fixed backend with no resolver, or a resolver that returns an empty address) fails with 500.
type Option ¶
type Option func(*config)
Option configures a Handler.
func WithClient ¶
func WithClient(cl *rconclient.Client) Option
WithClient supplies a preconfigured rconclient.Client (timeouts, retry, logger). It defaults to rconclient.New().
Example ¶
WithClient hands the handler a preconfigured client, so every backend call shares the same timeout, retry, and logging policy.
package main
import (
"net/http"
"os"
"time"
"github.com/cbrgm/rcon/rconclient"
"github.com/cbrgm/rcon/rconhttp"
)
func main() {
client := rconclient.New(
rconclient.WithTimeout(5*time.Second),
rconclient.WithRetry(2, rconclient.ExponentialBackoff(50*time.Millisecond, time.Second)),
)
h := rconhttp.New(
rconhttp.Backend{Addr: "127.0.0.1:25575", Password: os.Getenv("RCON_PASSWORD")},
rconhttp.WithClient(client),
rconhttp.WithIdleTimeout(10*time.Minute),
)
defer h.Close()
mux := http.NewServeMux()
mux.Handle("POST /command", h)
_ = http.ListenAndServe(":8080", mux)
}
Output:
func WithIdleTimeout ¶
WithIdleTimeout sets how long a cached session may sit idle before eviction.
func WithLogger ¶
WithLogger sets the handler's structured logger. It defaults to a no-op logger.
func WithResolver ¶
WithResolver replaces the fixed backend with a per-request Resolver.
type Resolver ¶
Resolver selects the Backend for a request. Returning an error rejects the request; return ErrUnauthorized to map it to HTTP 401.
func TokenResolver ¶
TokenResolver resolves an "Authorization: Bearer <token>" header to a Backend via byToken, so the RCON password never crosses the wire. A missing or unknown token yields ErrUnauthorized. It is the recommended way to switch backends dynamically.
Example ¶
Switch backends per request with a bearer token that maps to a server-side backend, so the RCON password never crosses the wire.
package main
import (
"net/http"
"os"
"github.com/cbrgm/rcon/rconhttp"
)
func main() {
h := rconhttp.New(rconhttp.Backend{}, rconhttp.WithResolver(
rconhttp.TokenResolver(map[string]rconhttp.Backend{
"tok_prod": {Addr: "10.0.0.5:25575", Password: os.Getenv("PROD_PW")},
"tok_stg": {Addr: "10.0.0.6:25575", Password: os.Getenv("STG_PW")},
}),
))
defer h.Close()
mux := http.NewServeMux()
mux.Handle("POST /command", h)
_ = http.ListenAndServe(":8080", mux)
}
Output:
type ResolverFunc ¶
ResolverFunc adapts an ordinary function to a Resolver.
Example ¶
A ResolverFunc picks the backend however you like, here from a path segment, with the password kept server-side. Return ErrUnauthorized to map to HTTP 401.
package main
import (
"net/http"
"os"
"github.com/cbrgm/rcon/rconhttp"
)
func main() {
servers := map[string]rconhttp.Backend{
"prod": {Addr: "10.0.0.5:25575", Password: os.Getenv("PROD_PW")},
"stg": {Addr: "10.0.0.6:25575", Password: os.Getenv("STG_PW")},
}
resolve := rconhttp.ResolverFunc(func(r *http.Request) (rconhttp.Backend, error) {
b, ok := servers[r.PathValue("server")]
if !ok {
return rconhttp.Backend{}, rconhttp.ErrUnauthorized
}
return b, nil
})
h := rconhttp.New(rconhttp.Backend{}, rconhttp.WithResolver(resolve))
defer h.Close()
mux := http.NewServeMux()
mux.Handle("POST /servers/{server}/command", h)
_ = http.ListenAndServe(":8080", mux)
}
Output: