Skip to content

Commit 397c807

Browse files
authored
HTTP/HTTPS adjustments (#314)
# Changes * Validate private/public key pair before attempting to start server on HTTPS port. * Improved logging of staging of web server start up/failure. * Renaming `WebPort` to `HttpPort` in config.yaml * Adding `HttpsRedirect` boolean. * if true, will redirect http traffic to https. Must have both http and https running for this to work. * Possible to run no web server at all by setting http/https ports to zero.
1 parent e758340 commit 397c807

File tree

5 files changed

+119
-38
lines changed

5 files changed

+119
-38
lines changed

_datafiles/config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,16 @@ Network:
385385
# - LocalPort -
386386
# A port that can only be accessed via localhost, but will not limit based on connection count
387387
LocalPort: 9999
388-
# - WebPort -
388+
# - HttpPort -
389389
# The port the server listens on for web requests
390-
WebPort: 80
390+
HttpPort: 80
391391
# - HttpsPort -
392392
# The port the server listens on for web requests
393393
# Note: Must have a cert/key file (See FilePaths)
394394
HttpsPort: 443
395+
# - HttpsRedirect -
396+
# If true, will send all http traffic to https with a redirect
397+
HttpsRedirect: false
395398
# - AfkSeconds -
396399
# If this many seconds pass without player input, they are flagged as afk
397400
# Set to zero to never mark anyone as AFK

internal/configs/config.network.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ type Network struct {
44
MaxTelnetConnections ConfigInt `yaml:"MaxTelnetConnections"` // Maximum number of telnet connections to accept
55
TelnetPort ConfigSliceString `yaml:"TelnetPort"` // One or more Ports used to accept telnet connections
66
LocalPort ConfigInt `yaml:"LocalPort"` // Port used for admin connections, localhost only
7-
WebPort ConfigInt `yaml:"WebPort"` // Port used for web requests
7+
HttpPort ConfigInt `yaml:"HttpPort"` // Port used for web requests
88
HttpsPort ConfigInt `yaml:"HttpsPort"` // Port used for web https requests
9+
HttpsRedirect ConfigBool `yaml:"HttpsRedirect"` // If true, http traffic will be redirected to https
910
AfkSeconds ConfigInt `yaml:"AfkSeconds"` // How long until a player is marked as afk?
1011
MaxIdleSeconds ConfigInt `yaml:"MaxIdleSeconds"` // How many seconds a player can go without a command in game before being kicked.
1112
TimeoutMods ConfigBool `yaml:"TimeoutMods"` // Whether to kick admin/mods when idle too long.
@@ -23,8 +24,12 @@ func (n *Network) Validate() {
2324
n.MaxTelnetConnections = 50 // default
2425
}
2526

26-
if n.WebPort < 0 {
27-
n.WebPort = 0 // default
27+
if n.HttpPort < 0 {
28+
n.HttpPort = 0 // default
29+
}
30+
31+
if n.HttpsPort < 0 {
32+
n.HttpsPort = 0 // default
2833
}
2934

3035
if n.AfkSeconds < 0 {

internal/web/web.go

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package web
22

33
import (
44
"context"
5+
"crypto/tls"
56
"fmt"
67
"log"
8+
"log/slog"
9+
"net"
710
"net/http"
811
"os"
912
"path/filepath"
13+
"strings"
1014
"sync"
1115
"text/template"
1216
"time"
@@ -222,7 +226,14 @@ func serveTemplate(w http.ResponseWriter, r *http.Request) {
222226
}
223227
}
224228

225-
func Listen(webPort int, webHttpsPort int, wg *sync.WaitGroup, webSocketHandler func(*websocket.Conn)) {
229+
func Listen(wg *sync.WaitGroup, webSocketHandler func(*websocket.Conn)) {
230+
231+
networkConfig := configs.GetNetworkConfig()
232+
233+
if networkConfig.HttpPort == 0 && networkConfig.HttpsPort == 0 {
234+
slog.Error(`Web`, "error", "No ports defined. No web server will be started.")
235+
return
236+
}
226237

227238
// Routing
228239
// Basic homepage
@@ -294,45 +305,97 @@ func Listen(webPort int, webHttpsPort int, wg *sync.WaitGroup, webSocketHandler
294305
doBasicAuth(roomData),
295306
))
296307

297-
// HTTP Server
298-
wg.Add(1)
299-
httpServer = &http.Server{Addr: fmt.Sprintf(`:%d`, webPort)}
300-
go func() {
308+
//
309+
// Http server start up
310+
//
301311

302-
mudlog.Info("Starting http server", "webport", webPort)
312+
if networkConfig.HttpPort > 0 {
303313

304-
defer wg.Done()
305-
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
306-
mudlog.Error("Error starting web server", "error", err)
314+
httpServer = &http.Server{
315+
Addr: fmt.Sprintf(`:%d`, networkConfig.HttpPort),
307316
}
308-
}()
309317

310-
if webHttpsPort > 0 {
318+
if networkConfig.HttpsRedirect && networkConfig.HttpsPort != 0 {
311319

312-
filePaths := configs.GetFilePathsConfig()
320+
var redirectHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
313321

314-
certFile := ``
315-
keyFile := ``
322+
host := r.Host
323+
324+
// If the host header includes a port (e.g. "example.com:80"), strip it out.
325+
if strings.Contains(host, ":") {
326+
host, _, _ = net.SplitHostPort(host)
327+
}
328+
329+
// Build the target URL with your known HTTPS port (443 in this case).
330+
target := fmt.Sprintf("https://%s:%d%s", host, networkConfig.HttpsPort, r.RequestURI)
331+
332+
http.Redirect(w, r, target, http.StatusMovedPermanently)
333+
334+
}
335+
336+
httpServer.Handler = redirectHandler
316337

317-
if _, err := os.Stat(string(filePaths.HttpsCertFile)); err == nil {
318-
certFile = string(filePaths.HttpsCertFile)
319-
}
320-
if _, err := os.Stat(string(filePaths.HttpsKeyFile)); err == nil {
321-
keyFile = string(filePaths.HttpsKeyFile)
322338
}
323339

324-
if certFile != `` && keyFile != `` {
325-
wg.Add(1)
326-
httpsServer = &http.Server{Addr: fmt.Sprintf(`:%d`, webHttpsPort)}
327-
go func() {
340+
// HTTP Server
341+
wg.Add(1)
342+
343+
mudlog.Info("HTTP", "stage", "Starting http server", "port", networkConfig.HttpPort)
344+
go func() {
345+
defer wg.Done()
346+
347+
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
348+
mudlog.Error("HTTP", "error", fmt.Errorf("Error starting web server: %w", err))
349+
}
350+
}()
351+
}
352+
353+
//
354+
// Https server start up
355+
//
328356

329-
mudlog.Info("Starting https server", "webHttpsPort", webHttpsPort)
357+
if networkConfig.HttpsPort > 0 {
330358

331-
defer wg.Done()
332-
if err := httpsServer.ListenAndServeTLS(certFile, keyFile); err != nil && err != http.ErrServerClosed {
333-
mudlog.Error("Error starting HTTPS web server", "error", err)
359+
filePaths := configs.GetFilePathsConfig()
360+
361+
if len(filePaths.HttpsCertFile) == 0 || len(filePaths.HttpsKeyFile) == 0 {
362+
363+
mudlog.Info("HTTPS", "stage", "skipping", "error", "Undefined key file", "Public Cert", filePaths.HttpsCertFile, "Private Key", filePaths.HttpsKeyFile)
364+
365+
} else {
366+
367+
if filePaths.HttpsCertFile != `` && filePaths.HttpsKeyFile != `` {
368+
369+
mudlog.Info("HTTPS", "stage", "Validating public/private key pair", "Public Cert", filePaths.HttpsCertFile, "Private Key", filePaths.HttpsKeyFile)
370+
371+
cert, err := tls.LoadX509KeyPair(string(filePaths.HttpsCertFile), string(filePaths.HttpsKeyFile))
372+
373+
if err != nil {
374+
375+
mudlog.Error("HTTPS", "error", fmt.Errorf("Error loading certificate and key: %w", err))
376+
377+
} else {
378+
379+
tlsConfig := &tls.Config{
380+
Certificates: []tls.Certificate{cert},
381+
}
382+
383+
wg.Add(1)
384+
385+
httpsServer = &http.Server{
386+
Addr: fmt.Sprintf(`:%d`, networkConfig.HttpsPort),
387+
TLSConfig: tlsConfig,
388+
}
389+
390+
mudlog.Info("HTTPS", "stage", "Starting https server", "port", networkConfig.HttpsPort)
391+
go func() {
392+
defer wg.Done()
393+
if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
394+
mudlog.Error("HTTPS", "error", fmt.Errorf("Error starting HTTPS web server: %w", err))
395+
}
396+
}()
334397
}
335-
}()
398+
}
336399
}
337400
}
338401

@@ -357,13 +420,17 @@ func Shutdown() {
357420

358421
if httpServer != nil {
359422
if err := httpServer.Shutdown(ctx); err != nil {
360-
log.Printf("HTTP server shutdown failed:%+v", err)
423+
mudlog.Error("HTTP", "error", fmt.Errorf("HTTP server shutdown failed: %w", err))
424+
} else {
425+
mudlog.Info("HTTPS", "stage", "stopped")
361426
}
362427
}
363428

364429
if httpsServer != nil {
365430
if err := httpsServer.Shutdown(ctx); err != nil {
366-
log.Printf("HTTPS server shutdown failed:%+v", err)
431+
mudlog.Error("HTTPS", "error", fmt.Errorf("HTTP server shutdown failed: %w", err))
432+
} else {
433+
mudlog.Info("HTTPS", "stage", "stopped")
367434
}
368435
}
369436
}

main.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,20 @@ func main() {
168168
"name", string(c.Server.MudName),
169169
)
170170

171+
mudlog.Info(`========================`)
172+
171173
// Load all the data files up front.
172174
loadAllDataFiles(false)
173175

176+
mudlog.Info(`========================`)
177+
174178
mudlog.Info("Mapper", "status", "precaching")
175179
timeStart := time.Now()
176180
mapper.PreCacheMaps()
177181
mudlog.Info("Mapper", "status", "done", "time taken", time.Since(timeStart))
178182

183+
mudlog.Info(`========================`)
184+
179185
// Create the user index
180186
idx := users.NewUserIndex()
181187
if !idx.Exists() {
@@ -195,7 +201,6 @@ func main() {
195201

196202
scripting.Setup(int(c.Scripting.LoadTimeoutMs), int(c.Scripting.RoomTimeoutMs))
197203

198-
//
199204
mudlog.Info(`========================`)
200205

201206
// Trigger the load plugins event
@@ -219,7 +224,8 @@ func main() {
219224
// Set the server to be alive
220225
serverAlive.Store(true)
221226

222-
web.Listen(int(c.Network.WebPort), int(c.Network.HttpsPort), &wg, HandleWebSocketConnection)
227+
mudlog.Info(`========================`)
228+
web.Listen(&wg, HandleWebSocketConnection)
223229

224230
allServerListeners := make([]net.Listener, 0, len(c.Network.TelnetPort))
225231
for _, port := range c.Network.TelnetPort {

world.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ func (w *World) UpdateStats() {
10181018
}
10191019
}
10201020

1021-
s.WebSocketPort = int(c.WebPort)
1021+
s.WebSocketPort = int(c.HttpPort)
10221022

10231023
web.UpdateStats(s)
10241024
}

0 commit comments

Comments
 (0)