create a websocket game server
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// Package config loads the game server configuration from environment
|
||||
// variables.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// ListenAddr is the address the websocket HTTP server binds to
|
||||
// (e.g. ":8081"). The server may sit behind a reverse proxy.
|
||||
ListenAddr string
|
||||
// PublicAddr is the public endpoint address of this server
|
||||
// (e.g. "ws://example.com:8081/ws"). It is used only to identify this
|
||||
// server: the "game_server" claim of a game token must match it
|
||||
// strictly. The main server must be configured to put exactly this
|
||||
// string into the tokens it issues.
|
||||
PublicAddr string
|
||||
// JWTSecret is the HS256 secret shared with the main server; both the
|
||||
// game token and the user token are verified with it.
|
||||
JWTSecret []byte
|
||||
// JoinTimeout is how long the first player waits for the second one
|
||||
// before the match is cancelled.
|
||||
JoinTimeout time.Duration
|
||||
// AllowedOrigins restricts the Origin header of websocket upgrade
|
||||
// requests; ["*"] disables the check.
|
||||
AllowedOrigins []string
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
var cfg Config
|
||||
|
||||
cfg.ListenAddr = getenv("LISTEN_ADDR", ":8081")
|
||||
|
||||
cfg.PublicAddr = os.Getenv("PUBLIC_ADDR")
|
||||
if cfg.PublicAddr == "" {
|
||||
return cfg, fmt.Errorf("PUBLIC_ADDR is required (public endpoint address, e.g. ws://example.com:8081/ws)")
|
||||
}
|
||||
|
||||
secret := os.Getenv("JWT_SECRET")
|
||||
if secret == "" {
|
||||
return cfg, fmt.Errorf("JWT_SECRET is required (HS256 secret shared with the main server)")
|
||||
}
|
||||
cfg.JWTSecret = []byte(secret)
|
||||
|
||||
joinTimeout, err := parseDuration(getenv("JOIN_TIMEOUT", "2m"))
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("invalid JOIN_TIMEOUT: %w", err)
|
||||
}
|
||||
cfg.JoinTimeout = joinTimeout
|
||||
|
||||
origins := getenv("ALLOWED_ORIGINS", "*")
|
||||
cfg.AllowedOrigins = strings.Split(origins, ",")
|
||||
for i := range cfg.AllowedOrigins {
|
||||
cfg.AllowedOrigins[i] = strings.TrimSpace(cfg.AllowedOrigins[i])
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// parseDuration accepts either a Go duration string ("90s", "2m") or a plain
|
||||
// number of seconds ("120").
|
||||
func parseDuration(s string) (time.Duration, error) {
|
||||
if d, err := time.ParseDuration(s); err == nil {
|
||||
return d, nil
|
||||
}
|
||||
if secs, err := strconv.Atoi(s); err == nil {
|
||||
return time.Duration(secs) * time.Second, nil
|
||||
}
|
||||
return 0, fmt.Errorf("not a duration: %q", s)
|
||||
}
|
||||
Reference in New Issue
Block a user