Refactor server.NewServer
This commit is contained in:
parent
bdceb06447
commit
00e4e4da52
8 changed files with 107 additions and 88 deletions
|
@ -105,7 +105,7 @@ formatted responses are more easily parsed by computers.
|
|||
- Aliases: `k`
|
||||
|
||||
- `rematch`
|
||||
- Request (or accept) a rematch after a match has been finished.
|
||||
- Offer (or accept) a rematch after a match has been finished.
|
||||
- Aliases: `rm`
|
||||
|
||||
- `say <message>`
|
||||
|
|
|
@ -14,47 +14,46 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
op := &server.Options{}
|
||||
var (
|
||||
tcpAddress string
|
||||
wsAddress string
|
||||
tz string
|
||||
dataSource string
|
||||
mailServer string
|
||||
passwordSalt string
|
||||
resetSalt string
|
||||
ipSalt string
|
||||
verbose bool
|
||||
debug int
|
||||
debugPort int
|
||||
debugCommands bool
|
||||
rollStatistics bool
|
||||
)
|
||||
flag.StringVar(&tcpAddress, "tcp", "localhost:1337", "TCP listen address")
|
||||
flag.StringVar(&wsAddress, "ws", "", "WebSocket listen address")
|
||||
flag.StringVar(&tz, "tz", "", "Time zone used when calculating statistics")
|
||||
flag.StringVar(&dataSource, "db", "", "Database data source (postgres://username:password@localhost:5432/database_name")
|
||||
flag.StringVar(&mailServer, "smtp", "", "SMTP server address")
|
||||
flag.BoolVar(&verbose, "verbose", false, "Print all client messages")
|
||||
flag.IntVar(&debug, "debug", 0, "print debug information and serve pprof on specified port")
|
||||
flag.StringVar(&op.TZ, "tz", "", "Time zone used when calculating statistics")
|
||||
flag.StringVar(&op.DataSource, "db", "", "Database data source (postgres://username:password@localhost:5432/database_name")
|
||||
flag.StringVar(&op.MailServer, "smtp", "", "SMTP server address")
|
||||
flag.BoolVar(&op.Verbose, "verbose", false, "Print all client messages")
|
||||
flag.IntVar(&debugPort, "debug", 0, "print debug information and serve pprof on specified port")
|
||||
flag.BoolVar(&debugCommands, "debug-commands", false, "allow players to use restricted commands")
|
||||
flag.BoolVar(&rollStatistics, "statistics", false, "print dice roll statistics and exit")
|
||||
flag.Parse()
|
||||
|
||||
if dataSource == "" {
|
||||
dataSource = os.Getenv("BGAMMON_DB")
|
||||
if debugPort > 0 {
|
||||
op.Debug = true
|
||||
op.Verbose = true
|
||||
}
|
||||
|
||||
if mailServer == "" {
|
||||
mailServer = os.Getenv("BGAMMON_SMTP")
|
||||
if op.DataSource == "" {
|
||||
op.DataSource = os.Getenv("BGAMMON_DB")
|
||||
}
|
||||
|
||||
passwordSalt = os.Getenv("BGAMMON_SALT_PASSWORD")
|
||||
resetSalt = os.Getenv("BGAMMON_SALT_RESET")
|
||||
ipSalt = os.Getenv("BGAMMON_SALT_IP")
|
||||
if op.MailServer == "" {
|
||||
op.MailServer = os.Getenv("BGAMMON_SMTP")
|
||||
}
|
||||
|
||||
certDomain := os.Getenv("BGAMMON_CERT_DOMAIN")
|
||||
certFolder := os.Getenv("BGAMMON_CERT_FOLDER")
|
||||
certEmail := os.Getenv("BGAMMON_CERT_EMAIL")
|
||||
certAddress := os.Getenv("BGAMMON_CERT_ADDRESS")
|
||||
op.ResetSalt = os.Getenv("BGAMMON_SALT_RESET")
|
||||
op.PasswordSalt = os.Getenv("BGAMMON_SALT_PASSWORD")
|
||||
op.IPAddressSalt = os.Getenv("BGAMMON_SALT_IP")
|
||||
|
||||
op.CertDomain = os.Getenv("BGAMMON_CERT_DOMAIN")
|
||||
op.CertFolder = os.Getenv("BGAMMON_CERT_FOLDER")
|
||||
op.CertEmail = os.Getenv("BGAMMON_CERT_EMAIL")
|
||||
op.CertAddress = os.Getenv("BGAMMON_CERT_ADDRESS")
|
||||
|
||||
if rollStatistics {
|
||||
printRollStatistics()
|
||||
|
@ -65,13 +64,13 @@ func main() {
|
|||
log.Fatal("Error: A TCP and/or WebSocket listen address must be specified.")
|
||||
}
|
||||
|
||||
if debug > 0 {
|
||||
if debugPort > 0 {
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf("localhost:%d", debug), nil))
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf("localhost:%d", debugPort), nil))
|
||||
}()
|
||||
}
|
||||
|
||||
s := server.NewServer(tz, dataSource, mailServer, passwordSalt, resetSalt, ipSalt, certDomain, certFolder, certEmail, certAddress, false, verbose || debug > 0, debugCommands)
|
||||
s := server.NewServer(op)
|
||||
if tcpAddress != "" {
|
||||
s.Listen("tcp", tcpAddress)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ const (
|
|||
CommandMove = "move" // Move checkers.
|
||||
CommandReset = "reset" // Reset checker movement.
|
||||
CommandOk = "ok" // Confirm checker movement and pass turn to next player.
|
||||
CommandRematch = "rematch" // Confirm checker movement and pass turn to next player.
|
||||
CommandRematch = "rematch" // Offer (or accept) a rematch after a match has been finished.
|
||||
CommandFollow = "follow" // Follow a player.
|
||||
CommandUnfollow = "unfollow" // Un-follow a player.
|
||||
CommandBoard = "board" // Print current board state in human-readable form.
|
||||
|
|
|
@ -26,7 +26,6 @@ type socketClient struct {
|
|||
func newSocketClient(conn net.Conn, commands chan<- []byte, events chan []byte, verbose bool) *socketClient {
|
||||
return &socketClient{
|
||||
conn: conn,
|
||||
address: hashIP(conn.RemoteAddr().String()),
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
|
|
|
@ -38,7 +38,6 @@ func newWebSocketClient(r *http.Request, w http.ResponseWriter, commands chan<-
|
|||
|
||||
return &webSocketClient{
|
||||
conn: conn,
|
||||
address: hashIP(r.RemoteAddr),
|
||||
events: events,
|
||||
commands: commands,
|
||||
verbose: verbose,
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"code.rocket9labs.com/tslocum/gotext"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
|
@ -34,10 +35,6 @@ var (
|
|||
alphaNumericUnderscore = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
|
||||
)
|
||||
|
||||
var allowDebugCommands bool
|
||||
|
||||
var ipSalt string
|
||||
|
||||
//go:embed locales
|
||||
var assetFS embed.FS
|
||||
|
||||
|
@ -82,9 +79,10 @@ type server struct {
|
|||
|
||||
sortedCommands []string
|
||||
|
||||
mailServer string
|
||||
passwordSalt string
|
||||
resetSalt string
|
||||
mailServer string
|
||||
resetSalt string
|
||||
passwordSalt string
|
||||
ipAddressSalt string
|
||||
|
||||
tz *time.Location
|
||||
languageTags []language.Tag
|
||||
|
@ -97,28 +95,53 @@ type server struct {
|
|||
|
||||
relayChat bool // Chats are not relayed normally. This option is only used by local servers.
|
||||
verbose bool
|
||||
debug bool // Allow users to run debug commands.
|
||||
|
||||
shutdownTime time.Time
|
||||
shutdownReason string
|
||||
}
|
||||
|
||||
func NewServer(tz string, dataSource string, mailServer string, passwordSalt string, resetSalt string, ipAddressSalt string, certDomain string, certFolder string, certEmail string, certAddress string, relayChat bool, verbose bool, allowDebug bool) *server {
|
||||
type Options struct {
|
||||
TZ string
|
||||
DataSource string
|
||||
MailServer string
|
||||
|
||||
RelayChat bool
|
||||
Verbose bool
|
||||
Debug bool
|
||||
|
||||
CertDomain string
|
||||
CertFolder string
|
||||
CertEmail string
|
||||
CertAddress string
|
||||
|
||||
ResetSalt string
|
||||
PasswordSalt string
|
||||
IPAddressSalt string
|
||||
}
|
||||
|
||||
func NewServer(op *Options) *server {
|
||||
if op == nil {
|
||||
op = &Options{}
|
||||
}
|
||||
const bufferSize = 10
|
||||
s := &server{
|
||||
newGameIDs: make(chan int),
|
||||
newClientIDs: make(chan int),
|
||||
commands: make(chan serverCommand, bufferSize),
|
||||
welcome: []byte("hello Welcome to bgammon.org! Please log in by sending the 'login' command. You may specify a username, otherwise you will be assigned a random username. If you specify a username, you may also specify a password. Have fun!"),
|
||||
defcon: 5,
|
||||
mailServer: mailServer,
|
||||
passwordSalt: passwordSalt,
|
||||
resetSalt: resetSalt,
|
||||
certDomain: certDomain,
|
||||
certFolder: certFolder,
|
||||
certEmail: certEmail,
|
||||
certAddress: certAddress,
|
||||
relayChat: relayChat,
|
||||
verbose: verbose,
|
||||
newGameIDs: make(chan int),
|
||||
newClientIDs: make(chan int),
|
||||
commands: make(chan serverCommand, bufferSize),
|
||||
welcome: []byte("hello Welcome to bgammon.org! Please log in by sending the 'login' command. You may specify a username, otherwise you will be assigned a random username. If you specify a username, you may also specify a password. Have fun!"),
|
||||
defcon: 5,
|
||||
mailServer: op.MailServer,
|
||||
resetSalt: op.ResetSalt,
|
||||
passwordSalt: op.PasswordSalt,
|
||||
ipAddressSalt: op.IPAddressSalt,
|
||||
certDomain: op.CertDomain,
|
||||
certFolder: op.CertFolder,
|
||||
certEmail: op.CertEmail,
|
||||
certAddress: op.CertAddress,
|
||||
relayChat: op.RelayChat,
|
||||
verbose: op.Verbose,
|
||||
debug: op.Debug,
|
||||
}
|
||||
s.loadLocales()
|
||||
|
||||
|
@ -127,20 +150,18 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
|
|||
}
|
||||
sort.Slice(s.sortedCommands, func(i, j int) bool { return s.sortedCommands[i] < s.sortedCommands[j] })
|
||||
|
||||
if tz != "" {
|
||||
if op.TZ != "" {
|
||||
var err error
|
||||
s.tz, err = time.LoadLocation(tz)
|
||||
s.tz, err = time.LoadLocation(op.TZ)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse timezone %s: %s", tz, err)
|
||||
log.Fatalf("failed to parse timezone %s: %s", op.TZ, err)
|
||||
}
|
||||
} else {
|
||||
s.tz = time.UTC
|
||||
}
|
||||
|
||||
ipSalt = ipAddressSalt
|
||||
|
||||
if dataSource != "" {
|
||||
err := connectDB(dataSource)
|
||||
if op.DataSource != "" {
|
||||
err := connectDB(op.DataSource)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to database: %s", err)
|
||||
}
|
||||
|
@ -155,8 +176,6 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
|
|||
log.Println("Connected to database successfully")
|
||||
}
|
||||
|
||||
allowDebugCommands = allowDebug
|
||||
|
||||
go s.handleNewGameIDs()
|
||||
go s.handleNewClientIDs()
|
||||
go s.handleCommands()
|
||||
|
@ -408,6 +427,9 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
|
||||
now := time.Now().Unix()
|
||||
|
||||
sc := newSocketClient(conn, commands, events, s.verbose)
|
||||
sc.address = s.hashIP(conn.RemoteAddr().String())
|
||||
|
||||
c := &serverClient{
|
||||
id: <-s.newClientIDs,
|
||||
language: "bgammon-en",
|
||||
|
@ -415,7 +437,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
connected: now,
|
||||
active: now,
|
||||
commands: commands,
|
||||
Client: newSocketClient(conn, commands, events, s.verbose),
|
||||
Client: sc,
|
||||
}
|
||||
s.sendWelcome(c)
|
||||
s.handleClient(c)
|
||||
|
@ -530,6 +552,23 @@ func (s *server) gameByClient(c *serverClient) *serverGame {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *server) hashIP(address string) string {
|
||||
leftBracket, rightBracket := strings.IndexByte(address, '['), strings.IndexByte(address, ']')
|
||||
if leftBracket != -1 && rightBracket != -1 && rightBracket > leftBracket {
|
||||
address = address[1:rightBracket]
|
||||
} else if strings.IndexByte(address, '.') != -1 {
|
||||
colon := strings.IndexByte(address, ':')
|
||||
if colon != -1 {
|
||||
address = address[:colon]
|
||||
}
|
||||
}
|
||||
|
||||
buf := []byte(address + s.ipAddressSalt)
|
||||
h := make([]byte, 64)
|
||||
sha3.ShakeSum256(h, buf)
|
||||
return fmt.Sprintf("%x\n", h)
|
||||
}
|
||||
|
||||
func (s *server) handleShutdown() {
|
||||
var mins time.Duration
|
||||
var minutes int
|
||||
|
|
|
@ -1554,7 +1554,7 @@ COMMANDS:
|
|||
|
||||
isIP := bytes.ContainsRune(params[0], '.') || bytes.ContainsRune(params[0], ':')
|
||||
if isIP {
|
||||
ip := hashIP(string(params[0]))
|
||||
ip := s.hashIP(string(params[0]))
|
||||
err := addBan(ip, 0, cmd.client.accountID, reason)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to add ban: " + err.Error())
|
||||
|
@ -1626,7 +1626,7 @@ COMMANDS:
|
|||
|
||||
isIP := bytes.ContainsRune(params[0], '.') || bytes.ContainsRune(params[0], ':')
|
||||
if isIP {
|
||||
err := deleteBan(hashIP(string(params[0])), 0)
|
||||
err := deleteBan(s.hashIP(string(params[0])), 0)
|
||||
if err != nil {
|
||||
cmd.client.sendNotice("Failed to remove ban: " + err.Error())
|
||||
continue
|
||||
|
@ -1667,7 +1667,7 @@ COMMANDS:
|
|||
|
||||
s.shutdown(time.Duration(minutes)*time.Minute, string(bytes.Join(params[1:], []byte(" "))))
|
||||
case "endgame":
|
||||
if !allowDebugCommands {
|
||||
if !s.debug {
|
||||
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not allowed to use that command."))
|
||||
continue
|
||||
} else if clientGame == nil {
|
||||
|
@ -1680,9 +1680,9 @@ COMMANDS:
|
|||
clientGame.Roll2 = 5
|
||||
clientGame.Roll3 = 0
|
||||
clientGame.Variant = bgammon.VariantAceyDeucey
|
||||
clientGame.Player1.Entered = false
|
||||
clientGame.Player1.Entered = true
|
||||
clientGame.Player2.Entered = true
|
||||
clientGame.Board = []int8{1, 2, 1, 3, 2, 2, 2, 0, 0, 0, -3, -7, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, -3, 0, 0, -2}
|
||||
clientGame.Board = []int8{0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}
|
||||
|
||||
log.Println(clientGame.Board[0:28])
|
||||
|
||||
|
|
|
@ -16,12 +16,11 @@ import (
|
|||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func (s *server) Listen(network string, address string) {
|
||||
if s.passwordSalt == "" || s.resetSalt == "" || ipSalt == "" {
|
||||
log.Fatal("error: password, reset and ip salts must be configured")
|
||||
if s.passwordSalt == "" || s.resetSalt == "" || s.ipAddressSalt == "" {
|
||||
log.Fatal("error: password, reset and ip salts must be configured before listening for remote clients")
|
||||
}
|
||||
|
||||
if strings.ToLower(network) == "ws" {
|
||||
|
@ -125,6 +124,7 @@ func (s *server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
|||
if wsClient == nil {
|
||||
return
|
||||
}
|
||||
wsClient.address = s.hashIP(r.RemoteAddr)
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
|
@ -400,20 +400,3 @@ func (s *server) handlePrintWildBGStats(w http.ResponseWriter, r *http.Request)
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(s.cachedStats(4))
|
||||
}
|
||||
|
||||
func hashIP(address string) string {
|
||||
leftBracket, rightBracket := strings.IndexByte(address, '['), strings.IndexByte(address, ']')
|
||||
if leftBracket != -1 && rightBracket != -1 && rightBracket > leftBracket {
|
||||
address = address[1:rightBracket]
|
||||
} else if strings.IndexByte(address, '.') != -1 {
|
||||
colon := strings.IndexByte(address, ':')
|
||||
if colon != -1 {
|
||||
address = address[:colon]
|
||||
}
|
||||
}
|
||||
|
||||
buf := []byte(address + ipSalt)
|
||||
h := make([]byte, 64)
|
||||
sha3.ShakeSum256(h, buf)
|
||||
return fmt.Sprintf("%x\n", h)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue