Better timeout support

This commit is contained in:
Jacob Gunther
2023-08-20 10:04:54 -05:00
parent 70114696b7
commit aa21aeaae1
5 changed files with 274 additions and 197 deletions

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.19
require ( require (
github.com/go-redsync/redsync/v4 v4.8.1 github.com/go-redsync/redsync/v4 v4.8.1
github.com/gofiber/fiber/v2 v2.48.0 github.com/gofiber/fiber/v2 v2.48.0
github.com/mcstatus-io/mcutil v1.4.0 github.com/mcstatus-io/mcutil/v2 v2.0.1
github.com/redis/go-redis/v9 v9.1.0 github.com/redis/go-redis/v9 v9.1.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

4
go.sum
View File

@@ -64,8 +64,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mcstatus-io/mcutil v1.4.0 h1:UpEaOm/PGIDcPFQqAXY14uhsgaclM3ZsEf6ojuAXZGE= github.com/mcstatus-io/mcutil/v2 v2.0.1 h1:tIyOBYoa+DygzCkGIhU7/STA2gOIcWqS45/p4U58xmE=
github.com/mcstatus-io/mcutil v1.4.0/go.mod h1:VPWRCaYXfQheaTt4XJGYOGFzrp2E+B8g393SQiYO5mA= github.com/mcstatus-io/mcutil/v2 v2.0.1/go.mod h1:GmQmohJjxYHkCI9qyK7rvfrZDqwwyKomnMbvXAz/US4=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

View File

@@ -1,18 +1,18 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"main/src/assets" "main/src/assets"
"net/http" "net/http"
"strconv" "strconv"
"time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
"github.com/mcstatus-io/mcutil" "github.com/mcstatus-io/mcutil/v2"
"github.com/mcstatus-io/mcutil/options" "github.com/mcstatus-io/mcutil/v2/options"
) )
func init() { func init() {
@@ -54,6 +54,12 @@ func FaviconHandler(ctx *fiber.Ctx) error {
// JavaStatusHandler returns the status of the Java edition Minecraft server specified in the address parameter. // JavaStatusHandler returns the status of the Java edition Minecraft server specified in the address parameter.
func JavaStatusHandler(ctx *fiber.Ctx) error { func JavaStatusHandler(ctx *fiber.Ctx) error {
opts, err := GetStatusOptions(ctx)
if err != nil {
return err
}
host, port, err := ParseAddress(ctx.Params("address"), 25565) host, port, err := ParseAddress(ctx.Params("address"), 25565)
if err != nil { if err != nil {
@@ -64,7 +70,7 @@ func JavaStatusHandler(ctx *fiber.Ctx) error {
return err return err
} }
response, expiresAt, err := GetJavaStatus(host, port, ctx.QueryBool("query", true)) response, expiresAt, err := GetJavaStatus(host, port, opts)
if err != nil { if err != nil {
return err return err
@@ -81,6 +87,12 @@ func JavaStatusHandler(ctx *fiber.Ctx) error {
// BedrockStatusHandler returns the status of the Bedrock edition Minecraft server specified in the address parameter. // BedrockStatusHandler returns the status of the Bedrock edition Minecraft server specified in the address parameter.
func BedrockStatusHandler(ctx *fiber.Ctx) error { func BedrockStatusHandler(ctx *fiber.Ctx) error {
opts, err := GetStatusOptions(ctx)
if err != nil {
return err
}
host, port, err := ParseAddress(ctx.Params("address"), 19132) host, port, err := ParseAddress(ctx.Params("address"), 19132)
if err != nil { if err != nil {
@@ -91,7 +103,7 @@ func BedrockStatusHandler(ctx *fiber.Ctx) error {
return err return err
} }
response, expiresAt, err := GetBedrockStatus(host, port) response, expiresAt, err := GetBedrockStatus(host, port, opts)
if err != nil { if err != nil {
return err return err
@@ -108,13 +120,19 @@ func BedrockStatusHandler(ctx *fiber.Ctx) error {
// IconHandler returns the server icon for the specified Java edition Minecraft server. // IconHandler returns the server icon for the specified Java edition Minecraft server.
func IconHandler(ctx *fiber.Ctx) error { func IconHandler(ctx *fiber.Ctx) error {
opts, err := GetStatusOptions(ctx)
if err != nil {
return err
}
host, port, err := ParseAddress(ctx.Params("address"), 25565) host, port, err := ParseAddress(ctx.Params("address"), 25565)
if err != nil { if err != nil {
return ctx.Status(http.StatusBadRequest).SendString("Invalid address value") return ctx.Status(http.StatusBadRequest).SendString("Invalid address value")
} }
icon, expiresAt, err := GetServerIcon(host, port) icon, expiresAt, err := GetServerIcon(host, port, opts)
if err != nil { if err != nil {
return err return err
@@ -131,7 +149,7 @@ func IconHandler(ctx *fiber.Ctx) error {
// SendVoteHandler allows sending of Votifier votes to the specified server. // SendVoteHandler allows sending of Votifier votes to the specified server.
func SendVoteHandler(ctx *fiber.Ctx) error { func SendVoteHandler(ctx *fiber.Ctx) error {
opts, err := ParseVoteOptions(ctx) opts, err := GetVoteOptions(ctx)
if err != nil { if err != nil {
return ctx.Status(http.StatusBadRequest).SendString(err.Error()) return ctx.Status(http.StatusBadRequest).SendString(err.Error())
@@ -140,26 +158,34 @@ func SendVoteHandler(ctx *fiber.Ctx) error {
switch opts.Version { switch opts.Version {
case 1: case 1:
{ {
if err = mcutil.SendLegacyVote(opts.Host, opts.Port, options.LegacyVote{ c, cancel := context.WithTimeout(context.Background(), opts.Timeout)
defer cancel()
if err = mcutil.SendLegacyVote(c, opts.Host, opts.Port, options.LegacyVote{
PublicKey: opts.PublicKey, PublicKey: opts.PublicKey,
ServiceName: opts.ServiceName, ServiceName: opts.ServiceName,
Username: opts.Username, Username: opts.Username,
IPAddress: opts.IPAddress, IPAddress: opts.IPAddress,
Timestamp: opts.Timestamp, Timestamp: opts.Timestamp,
Timeout: time.Second * 5, Timeout: opts.Timeout,
}); err != nil { }); err != nil {
return ctx.Status(http.StatusBadRequest).SendString(err.Error()) return ctx.Status(http.StatusBadRequest).SendString(err.Error())
} }
} }
case 2: case 2:
{ {
if err = mcutil.SendVote(opts.Host, opts.Port, options.Vote{ c, cancel := context.WithTimeout(context.Background(), opts.Timeout)
defer cancel()
if err = mcutil.SendVote(c, opts.Host, opts.Port, options.Vote{
ServiceName: opts.ServiceName, ServiceName: opts.ServiceName,
Username: opts.Username, Username: opts.Username,
Token: opts.Token, Token: opts.Token,
UUID: opts.UUID, UUID: opts.UUID,
Timestamp: opts.Timestamp, Timestamp: opts.Timestamp,
Timeout: time.Second * 5, Timeout: opts.Timeout,
}); err != nil { }); err != nil {
return ctx.Status(http.StatusBadRequest).SendString(err.Error()) return ctx.Status(http.StatusBadRequest).SendString(err.Error())
} }

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -10,10 +11,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/mcstatus-io/mcutil" "github.com/mcstatus-io/mcutil/v2"
"github.com/mcstatus-io/mcutil/description" "github.com/mcstatus-io/mcutil/v2/formatting"
"github.com/mcstatus-io/mcutil/options" "github.com/mcstatus-io/mcutil/v2/options"
"github.com/mcstatus-io/mcutil/response" "github.com/mcstatus-io/mcutil/v2/response"
) )
// BaseStatus is the base response properties for returning any status response from the API. // BaseStatus is the base response properties for returning any status response from the API.
@@ -114,8 +115,8 @@ type Plugin struct {
} }
// GetJavaStatus returns the status response of a Java Edition server, either using cache or fetching a fresh status. // GetJavaStatus returns the status response of a Java Edition server, either using cache or fetching a fresh status.
func GetJavaStatus(host string, port uint16, query bool) (*JavaStatusResponse, time.Duration, error) { func GetJavaStatus(host string, port uint16, opts *StatusOptions) (*JavaStatusResponse, time.Duration, error) {
cacheKey := GetCacheKey(host, port, query) cacheKey := GetCacheKey(host, port, opts)
// Wait for any other processes to finish fetching the status of this server // Wait for any other processes to finish fetching the status of this server
if conf.Cache.EnableLocks { if conf.Cache.EnableLocks {
@@ -144,7 +145,7 @@ func GetJavaStatus(host string, port uint16, query bool) (*JavaStatusResponse, t
// Fetch a fresh status from the server itself // Fetch a fresh status from the server itself
{ {
response := FetchJavaStatus(host, port, query) response := FetchJavaStatus(host, port, opts)
data, err := json.Marshal(response) data, err := json.Marshal(response)
@@ -161,8 +162,8 @@ func GetJavaStatus(host string, port uint16, query bool) (*JavaStatusResponse, t
} }
// GetBedrockStatus returns the status response of a Bedrock Edition server, either using cache or fetching a fresh status. // GetBedrockStatus returns the status response of a Bedrock Edition server, either using cache or fetching a fresh status.
func GetBedrockStatus(host string, port uint16) (*BedrockStatusResponse, time.Duration, error) { func GetBedrockStatus(host string, port uint16, opts *StatusOptions) (*BedrockStatusResponse, time.Duration, error) {
cacheKey := GetCacheKey(host, port, false) cacheKey := GetCacheKey(host, port, nil)
// Wait for any other processes to finish fetching the status of this server // Wait for any other processes to finish fetching the status of this server
if conf.Cache.EnableLocks { if conf.Cache.EnableLocks {
@@ -191,7 +192,7 @@ func GetBedrockStatus(host string, port uint16) (*BedrockStatusResponse, time.Du
// Fetch a fresh status from the server itself // Fetch a fresh status from the server itself
{ {
response := FetchBedrockStatus(host, port) response := FetchBedrockStatus(host, port, opts)
data, err := json.Marshal(response) data, err := json.Marshal(response)
@@ -208,8 +209,8 @@ func GetBedrockStatus(host string, port uint16) (*BedrockStatusResponse, time.Du
} }
// GetServerIcon returns the icon image of a Java Edition server, either using cache or fetching a fresh image. // GetServerIcon returns the icon image of a Java Edition server, either using cache or fetching a fresh image.
func GetServerIcon(host string, port uint16) ([]byte, time.Duration, error) { func GetServerIcon(host string, port uint16, opts *StatusOptions) ([]byte, time.Duration, error) {
cacheKey := GetCacheKey(host, port, false) cacheKey := GetCacheKey(host, port, nil)
// Fetch the cached icon if it exists // Fetch the cached icon if it exists
{ {
@@ -230,7 +231,11 @@ func GetServerIcon(host string, port uint16) ([]byte, time.Duration, error) {
// Fetch the icon from the server itself // Fetch the icon from the server itself
{ {
status, err := mcutil.Status(host, port) ctx, cancel := context.WithTimeout(context.Background(), opts.Timeout)
defer cancel()
status, err := mcutil.Status(ctx, host, port)
if err == nil && status.Favicon != nil && strings.HasPrefix(*status.Favicon, "data:image/png;base64,") { if err == nil && status.Favicon != nil && strings.HasPrefix(*status.Favicon, "data:image/png;base64,") {
data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(*status.Favicon, "data:image/png;base64,")) data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(*status.Favicon, "data:image/png;base64,"))
@@ -254,38 +259,74 @@ func GetServerIcon(host string, port uint16) ([]byte, time.Duration, error) {
} }
// FetchJavaStatus fetches fresh information about a Java Edition Minecraft server. // FetchJavaStatus fetches fresh information about a Java Edition Minecraft server.
func FetchJavaStatus(host string, port uint16, enableQuery bool) JavaStatusResponse { func FetchJavaStatus(host string, port uint16, opts *StatusOptions) JavaStatusResponse {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(2)
if enableQuery { if opts.Query {
wg.Add(1) wg.Add(1)
} }
statusContext, statusCancel := context.WithTimeout(context.Background(), opts.Timeout)
legacyContext, legacyCancel := context.WithTimeout(context.Background(), opts.Timeout)
queryContext, queryCancel := context.WithTimeout(context.Background(), opts.Timeout)
defer statusCancel()
defer legacyCancel()
defer queryCancel()
var ( var (
statusResult interface{} = nil statusResult *response.JavaStatus = nil
legacyStatusResult *response.JavaStatusLegacy = nil
queryResult *response.FullQuery = nil queryResult *response.FullQuery = nil
) )
// Status // Status
{ {
go func() { go func() {
if result, _ := mcutil.Status(host, port); result != nil { if result, _ := mcutil.Status(statusContext, host, port, options.JavaStatus{
statusResult = result Timeout: opts.Timeout - time.Millisecond*100,
} else if result, _ := mcutil.StatusLegacy(host, port); result != nil { }); result != nil {
statusResult = result statusResult = result
} }
wg.Done() wg.Done()
legacyCancel()
time.Sleep(time.Millisecond * 250)
if queryResult == nil {
queryCancel()
}
}()
}
// Legacy Status
{
go func() {
if result, _ := mcutil.StatusLegacy(legacyContext, host, port, options.JavaStatusLegacy{
Timeout: opts.Timeout - time.Millisecond*100,
}); result != nil {
legacyStatusResult = result
}
wg.Done()
time.Sleep(time.Millisecond * 250)
if queryResult == nil {
queryCancel()
}
}() }()
} }
// Query // Query
if enableQuery { if opts.Query {
go func() { go func() {
queryResult, _ = mcutil.FullQuery(host, port, options.Query{ queryResult, _ = mcutil.FullQuery(queryContext, host, port, options.Query{
Timeout: time.Second, Timeout: opts.Timeout - time.Millisecond*100,
}) })
wg.Done() wg.Done()
@@ -294,18 +335,22 @@ func FetchJavaStatus(host string, port uint16, enableQuery bool) JavaStatusRespo
wg.Wait() wg.Wait()
return BuildJavaResponse(host, port, statusResult, queryResult) return BuildJavaResponse(host, port, statusResult, legacyStatusResult, queryResult)
} }
// FetchBedrockStatus fetches a fresh status of a Bedrock Edition server. // FetchBedrockStatus fetches a fresh status of a Bedrock Edition server.
func FetchBedrockStatus(host string, port uint16) BedrockStatusResponse { func FetchBedrockStatus(host string, port uint16, opts *StatusOptions) BedrockStatusResponse {
status, _ := mcutil.StatusBedrock(host, port) ctx, cancel := context.WithTimeout(context.Background(), opts.Timeout)
defer cancel()
status, _ := mcutil.StatusBedrock(ctx, host, port)
return BuildBedrockResponse(host, port, status) return BuildBedrockResponse(host, port, status)
} }
// BuildJavaResponse builds the response data from the status and query information. // BuildJavaResponse builds the response data from the status and query information.
func BuildJavaResponse(host string, port uint16, status interface{}, query *response.FullQuery) (result JavaStatusResponse) { func BuildJavaResponse(host string, port uint16, status *response.JavaStatus, legacyStatus *response.JavaStatusLegacy, query *response.FullQuery) (result JavaStatusResponse) {
result = JavaStatusResponse{ result = JavaStatusResponse{
BaseStatus: BaseStatus{ BaseStatus: BaseStatus{
Online: false, Online: false,
@@ -319,39 +364,33 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
} }
// Status // Status
switch s := status.(type) { if status != nil {
case *response.JavaStatus:
{
if s == nil {
break
}
result.Online = true result.Online = true
result.JavaStatus = &JavaStatus{ result.JavaStatus = &JavaStatus{
Version: &JavaVersion{ Version: &JavaVersion{
NameRaw: s.Version.NameRaw, NameRaw: status.Version.NameRaw,
NameClean: s.Version.NameClean, NameClean: status.Version.NameClean,
NameHTML: s.Version.NameHTML, NameHTML: status.Version.NameHTML,
Protocol: s.Version.Protocol, Protocol: status.Version.Protocol,
}, },
Players: JavaPlayers{ Players: JavaPlayers{
Online: s.Players.Online, Online: status.Players.Online,
Max: s.Players.Max, Max: status.Players.Max,
List: make([]Player, 0), List: make([]Player, 0),
}, },
MOTD: MOTD{ MOTD: MOTD{
Raw: s.MOTD.Raw, Raw: status.MOTD.Raw,
Clean: s.MOTD.Clean, Clean: status.MOTD.Clean,
HTML: s.MOTD.HTML, HTML: status.MOTD.HTML,
}, },
Icon: s.Favicon, Icon: status.Favicon,
Mods: make([]Mod, 0), Mods: make([]Mod, 0),
Plugins: make([]Plugin, 0), Plugins: make([]Plugin, 0),
} }
if s.Players.Sample != nil { if status.Players.Sample != nil {
for _, player := range s.Players.Sample { for _, player := range status.Players.Sample {
result.Players.List = append(result.Players.List, Player{ result.Players.List = append(result.Players.List, Player{
UUID: player.ID, UUID: player.ID,
NameRaw: player.NameRaw, NameRaw: player.NameRaw,
@@ -361,53 +400,42 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
} }
} }
if s.ModInfo != nil { if status.ModInfo != nil {
for _, mod := range s.ModInfo.Mods { for _, mod := range status.ModInfo.Mods {
result.Mods = append(result.Mods, Mod{ result.Mods = append(result.Mods, Mod{
Name: mod.ID, Name: mod.ID,
Version: mod.Version, Version: mod.Version,
}) })
} }
} }
} else if legacyStatus != nil {
break
}
case *response.JavaStatusLegacy:
{
if s == nil {
break
}
result.Online = true result.Online = true
result.JavaStatus = &JavaStatus{ result.JavaStatus = &JavaStatus{
Version: nil, Version: nil,
Players: JavaPlayers{ Players: JavaPlayers{
Online: &s.Players.Online, Online: &legacyStatus.Players.Online,
Max: &s.Players.Max, Max: &legacyStatus.Players.Max,
List: make([]Player, 0), List: make([]Player, 0),
}, },
MOTD: MOTD{ MOTD: MOTD{
Raw: s.MOTD.Raw, Raw: legacyStatus.MOTD.Raw,
Clean: s.MOTD.Clean, Clean: legacyStatus.MOTD.Clean,
HTML: s.MOTD.HTML, HTML: legacyStatus.MOTD.HTML,
}, },
Icon: nil, Icon: nil,
Mods: make([]Mod, 0), Mods: make([]Mod, 0),
Plugins: make([]Plugin, 0), Plugins: make([]Plugin, 0),
} }
if s.Version != nil { if legacyStatus.Version != nil {
result.Version = &JavaVersion{ result.Version = &JavaVersion{
NameRaw: s.Version.NameRaw, NameRaw: legacyStatus.Version.NameRaw,
NameClean: s.Version.NameClean, NameClean: legacyStatus.Version.NameClean,
NameHTML: s.Version.NameHTML, NameHTML: legacyStatus.Version.NameHTML,
Protocol: s.Version.Protocol, Protocol: legacyStatus.Version.Protocol,
} }
} }
break
}
} }
// Query // Query
@@ -424,7 +452,7 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
} }
if motd, ok := query.Data["hostname"]; ok { if motd, ok := query.Data["hostname"]; ok {
if parsedMOTD, err := description.ParseFormatting(motd); err == nil { if parsedMOTD, err := formatting.Parse(motd); err == nil {
result.MOTD = MOTD{ result.MOTD = MOTD{
Raw: parsedMOTD.Raw, Raw: parsedMOTD.Raw,
Clean: parsedMOTD.Clean, Clean: parsedMOTD.Clean,
@@ -450,7 +478,7 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
} }
if version, ok := query.Data["version"]; ok { if version, ok := query.Data["version"]; ok {
parsedValue, err := description.ParseFormatting(version) parsedValue, err := formatting.Parse(version)
if err == nil { if err == nil {
result.Version = &JavaVersion{ result.Version = &JavaVersion{
@@ -490,7 +518,7 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
continue continue
} }
parsedName, err := description.ParseFormatting(username) parsedName, err := formatting.Parse(username)
if err == nil { if err == nil {
result.Players.List = append(result.Players.List, Player{ result.Players.List = append(result.Players.List, Player{
@@ -507,7 +535,7 @@ func BuildJavaResponse(host string, port uint16, status interface{}, query *resp
} }
// BuildBedrockResponse builds the response data from the status information. // BuildBedrockResponse builds the response data from the status information.
func BuildBedrockResponse(host string, port uint16, status interface{}) (result BedrockStatusResponse) { func BuildBedrockResponse(host string, port uint16, status *response.BedrockStatus) (result BedrockStatusResponse) {
result = BedrockStatusResponse{ result = BedrockStatusResponse{
BaseStatus: BaseStatus{ BaseStatus: BaseStatus{
Online: false, Online: false,
@@ -520,25 +548,19 @@ func BuildBedrockResponse(host string, port uint16, status interface{}) (result
BedrockStatus: nil, BedrockStatus: nil,
} }
switch s := status.(type) { if status != nil {
case *response.BedrockStatus:
{
if s == nil {
break
}
result.Online = true result.Online = true
result.BedrockStatus = &BedrockStatus{ result.BedrockStatus = &BedrockStatus{
Version: nil, Version: nil,
Players: nil, Players: nil,
MOTD: nil, MOTD: nil,
Gamemode: s.Gamemode, Gamemode: status.Gamemode,
ServerID: s.ServerID, ServerID: status.ServerID,
Edition: s.Edition, Edition: status.Edition,
} }
if s.Version != nil { if status.Version != nil {
if result.Version == nil { if result.Version == nil {
result.Version = &BedrockVersion{ result.Version = &BedrockVersion{
Name: nil, Name: nil,
@@ -546,10 +568,10 @@ func BuildBedrockResponse(host string, port uint16, status interface{}) (result
} }
} }
result.Version.Name = s.Version result.Version.Name = status.Version
} }
if s.ProtocolVersion != nil { if status.ProtocolVersion != nil {
if result.Version == nil { if result.Version == nil {
result.Version = &BedrockVersion{ result.Version = &BedrockVersion{
Name: nil, Name: nil,
@@ -557,10 +579,10 @@ func BuildBedrockResponse(host string, port uint16, status interface{}) (result
} }
} }
result.Version.Protocol = s.ProtocolVersion result.Version.Protocol = status.ProtocolVersion
} }
if s.OnlinePlayers != nil { if status.OnlinePlayers != nil {
if result.Players == nil { if result.Players == nil {
result.Players = &BedrockPlayers{ result.Players = &BedrockPlayers{
Online: nil, Online: nil,
@@ -568,10 +590,10 @@ func BuildBedrockResponse(host string, port uint16, status interface{}) (result
} }
} }
result.Players.Online = s.OnlinePlayers result.Players.Online = status.OnlinePlayers
} }
if s.MaxPlayers != nil { if status.MaxPlayers != nil {
if result.Players == nil { if result.Players == nil {
result.Players = &BedrockPlayers{ result.Players = &BedrockPlayers{
Online: nil, Online: nil,
@@ -579,19 +601,16 @@ func BuildBedrockResponse(host string, port uint16, status interface{}) (result
} }
} }
result.Players.Max = s.MaxPlayers result.Players.Max = status.MaxPlayers
} }
if s.MOTD != nil { if status.MOTD != nil {
result.MOTD = &MOTD{ result.MOTD = &MOTD{
Raw: s.MOTD.Raw, Raw: status.MOTD.Raw,
Clean: s.MOTD.Clean, Clean: status.MOTD.Clean,
HTML: s.MOTD.HTML, HTML: status.MOTD.HTML,
} }
} }
break
}
} }
return return

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"math"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -38,6 +39,13 @@ type VoteOptions struct {
Token string Token string
PublicKey string PublicKey string
Timestamp time.Time Timestamp time.Time
Timeout time.Duration
}
// StatusOptions is the options provided as query parameters to the status route.
type StatusOptions struct {
Query bool
Timeout time.Duration
} }
// MutexArray is a thread-safe array for storing and retrieving values. // MutexArray is a thread-safe array for storing and retrieving values.
@@ -140,8 +148,8 @@ func ParseAddress(address string, defaultPort uint16) (string, uint16, error) {
return host, uint16(port), nil return host, uint16(port), nil
} }
// ParseVoteOptions parses the vote options from the provided query parameters. // GetVoteOptions parses the vote options from the provided query parameters.
func ParseVoteOptions(ctx *fiber.Ctx) (*VoteOptions, error) { func GetVoteOptions(ctx *fiber.Ctx) (*VoteOptions, error) {
result := VoteOptions{} result := VoteOptions{}
// Version // Version
@@ -232,9 +240,30 @@ func ParseVoteOptions(ctx *fiber.Ctx) (*VoteOptions, error) {
} }
} }
// Timeout
{
result.Timeout = time.Duration(math.Max(float64(time.Second)*ctx.QueryFloat("timeout", 5.0), float64(time.Millisecond*250)))
}
return &result, nil return &result, nil
} }
func GetStatusOptions(ctx *fiber.Ctx) (*StatusOptions, error) {
result := &StatusOptions{}
// Query
{
result.Query = ctx.QueryBool("query", true)
}
// Timeout
{
result.Timeout = time.Duration(math.Max(float64(time.Second)*ctx.QueryFloat("timeout", 5.0), float64(time.Millisecond*500)))
}
return result, nil
}
// GetInstanceID returns the INSTANCE_ID environment variable parsed as an unsigned 16-bit integer. // GetInstanceID returns the INSTANCE_ID environment variable parsed as an unsigned 16-bit integer.
func GetInstanceID() (uint16, error) { func GetInstanceID() (uint16, error) {
if instanceID := os.Getenv("INSTANCE_ID"); len(instanceID) > 0 { if instanceID := os.Getenv("INSTANCE_ID"); len(instanceID) > 0 {
@@ -251,11 +280,14 @@ func GetInstanceID() (uint16, error) {
} }
// GetCacheKey generates a unique key used for caching status results in Redis. // GetCacheKey generates a unique key used for caching status results in Redis.
func GetCacheKey(host string, port uint16, query bool) string { func GetCacheKey(host string, port uint16, opts *StatusOptions) string {
values := &url.Values{} values := &url.Values{}
values.Set("host", host) values.Set("host", host)
values.Set("port", strconv.FormatUint(uint64(port), 10)) values.Set("port", strconv.FormatUint(uint64(port), 10))
values.Set("query", strconv.FormatBool(query))
if opts != nil {
values.Set("query", strconv.FormatBool(opts.Query))
}
return SHA256(values.Encode()) return SHA256(values.Encode())
} }