rconhttp

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

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

View Source
const DefaultIdleTimeout = 5 * time.Minute

DefaultIdleTimeout is how long a cached backend session may sit idle before it is closed and evicted.

Variables

View Source
var ErrUnauthorized = errors.New("rconhttp: unauthorized")

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

func New(backend Backend, opts ...Option) *Handler

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.

func (*Handler) Close

func (h *Handler) Close() error

Close releases all cached backend sessions. It expects no concurrent in-flight requests: call it during shutdown, after the server has stopped accepting new requests, because a request racing Close could otherwise dial a session that Close does not observe.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

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

func WithIdleTimeout

func WithIdleTimeout(d time.Duration) Option

WithIdleTimeout sets how long a cached session may sit idle before eviction.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the handler's structured logger. It defaults to a no-op logger.

func WithResolver

func WithResolver(res Resolver) Option

WithResolver replaces the fixed backend with a per-request Resolver.

type Resolver

type Resolver interface {
	Resolve(r *http.Request) (Backend, error)
}

Resolver selects the Backend for a request. Returning an error rejects the request; return ErrUnauthorized to map it to HTTP 401.

func TokenResolver

func TokenResolver(byToken map[string]Backend) Resolver

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

type ResolverFunc

type ResolverFunc func(r *http.Request) (Backend, error)

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

func (ResolverFunc) Resolve

func (f ResolverFunc) Resolve(r *http.Request) (Backend, error)

Resolve calls f(r).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL