Skip to content

Commit

Permalink
Restructure application with configuration and dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
presbrey committed Jan 27, 2025
1 parent 2716593 commit e54fd45
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 54 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ go 1.23.1

require github.com/fsnotify/fsnotify v1.8.0

require golang.org/x/sys v0.13.0 // indirect
require (
github.com/caarlos0/env/v11 v11.3.1
golang.org/x/sys v0.13.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
Expand Down
161 changes: 108 additions & 53 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
"sync"
"time"

"os/signal"

"github.com/caarlos0/env/v11"
"github.com/fsnotify/fsnotify"
)

Expand All @@ -25,14 +28,19 @@ type ProxyHandler struct {
configLock sync.RWMutex
}

// Options holds the application configuration
type Options struct {
HTTPAddr string `env:"HTTP_ADDR" envDefault:""`
Verbose bool `env:"VERBOSE" envDefault:"false"`
LogRequests bool `env:"LOG_REQUESTS" envDefault:"false"`
LogResponses bool `env:"LOG_RESPONSES" envDefault:"false"`
LogErrors bool `env:"LOG_ERRORS" envDefault:"true"`
LogFile string `env:"LOG_FILE" envDefault:""`
}

// Logging configuration
var (
verbose bool
logRequests bool
logResponses bool
logErrors bool
logConfigFile string
logger *log.Logger
logger *log.Logger
)

func isWebScheme(s string) bool {
Expand Down Expand Up @@ -122,7 +130,7 @@ func singleJoiningSlash(a, b string) string {
}

func modifyResponse(res *http.Response) error {
if logResponses {
if opts.LogResponses {
body, err := io.ReadAll(res.Body)
if err != nil {
return err
Expand All @@ -137,12 +145,12 @@ func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ph.configLock.RLock()
defer ph.configLock.RUnlock()

if logRequests {
if opts.LogRequests {
logger.Printf("Request: %s %s", r.Method, r.RequestURI)
}
start := time.Now()
ph.mux.ServeHTTP(w, r)
if verbose {
if opts.Verbose {
logger.Printf("%s %s %s", r.Method, r.RequestURI, time.Since(start))
}
}
Expand Down Expand Up @@ -182,74 +190,121 @@ func (ph *ProxyHandler) watchConfig(filename string) error {
}
}

func main() {
var httpAddr string
flag.StringVar(&httpAddr, "http", "", "HTTP listen address (e.g., :8080)")
flag.BoolVar(&verbose, "verbose", false, "Enable verbose logging")
flag.BoolVar(&logRequests, "log-requests", false, "Log incoming requests")
flag.BoolVar(&logResponses, "log-responses", false, "Log outgoing responses")
flag.BoolVar(&logErrors, "log-errors", true, "Log proxy errors")
flag.StringVar(&logConfigFile, "log-file", "", "Log to file instead of stderr")
flag.Parse()
var opts Options

if flag.NArg() != 1 {
fmt.Println("Usage: ./lightymux [flags] <config_file>")
flag.PrintDefaults()
os.Exit(1)
}
// LightyMux is the main application struct that coordinates all components
type LightyMux struct {
Options *Options
handler *ProxyHandler
server *http.Server
logger *log.Logger
}

// NewLightyMux creates a new LightyMux instance with the given options
func NewLightyMux(opts *Options) (*LightyMux, error) {
// Setup logging
if logConfigFile != "" {
logFile, err := os.OpenFile(logConfigFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
var l *log.Logger
if opts.LogFile != "" {
logFile, err := os.OpenFile(opts.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error opening log file: %v", err)
return nil, fmt.Errorf("error opening log file: %v", err)
}
defer logFile.Close()
logger = log.New(logFile, "", log.LstdFlags)
l = log.New(logFile, "", log.LstdFlags)
} else {
logger = log.New(os.Stderr, "", log.LstdFlags)
l = log.New(os.Stderr, "", log.LstdFlags)
}

configFile := flag.Arg(0)

// Check for PORT environment variable if -http flag is not set
if httpAddr == "" {
// Check for PORT environment variable if HTTPAddr is not set
if opts.HTTPAddr == "" {
if port := os.Getenv("PORT"); port != "" {
httpAddr = ":" + port
opts.HTTPAddr = ":" + port
} else {
httpAddr = ":8080" // Default if neither flag nor env var is set
opts.HTTPAddr = ":8080" // Default if neither flag nor env var is set
}
}

proxyHandler := &ProxyHandler{}
if err := proxyHandler.loadConfig(configFile); err != nil {
logger.Fatalf("Error loading config: %v", err)
return &LightyMux{
Options: opts,
handler: &ProxyHandler{},
logger: l,
}, nil
}

// Run starts the LightyMux server with the given configuration file
func (lm *LightyMux) Run(configFile string) error {
// Set the global logger (temporary until we refactor to remove global state)
logger = lm.logger

// Initialize proxy handler with config
if err := lm.handler.loadConfig(configFile); err != nil {
return fmt.Errorf("error loading config: %v", err)
}

// Start config file watcher
go func() {
if err := proxyHandler.watchConfig(configFile); err != nil {
logger.Fatalf("Error setting up config watcher: %v", err)
if err := lm.handler.watchConfig(configFile); err != nil {
lm.logger.Printf("Error watching config: %v", err)
}
}()

server := &http.Server{
Addr: httpAddr,
Handler: proxyHandler,
// Initialize HTTP server
lm.server = &http.Server{
Addr: lm.Options.HTTPAddr,
Handler: lm.handler,
}

// Setup graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
sigchan := make(chan os.Signal, 1)
<-sigchan
logger.Println("Shutting down the server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Printf("Error during server shutdown: %v", err)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
<-sigChan
lm.logger.Println("Shutting down server...")
if err := lm.server.Shutdown(ctx); err != nil {
lm.logger.Printf("Error during server shutdown: %v", err)
}
cancel()
}()

logger.Printf("Starting reverse proxy on %s", httpAddr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
logger.Fatalf("Error starting server: %v", err)
lm.logger.Printf("Starting reverse proxy on %s", lm.Options.HTTPAddr)
if err := lm.server.ListenAndServe(); err != http.ErrServerClosed {
return fmt.Errorf("error starting server: %v", err)
}

return nil
}

func main() {
if err := env.Parse(&opts); err != nil {
fmt.Printf("Error parsing env vars: %v\n", err)
os.Exit(1)
}

// Allow command line flags to override env vars
flag.StringVar(&opts.HTTPAddr, "http", opts.HTTPAddr, "HTTP listen address (e.g., :8080)")
flag.BoolVar(&opts.Verbose, "verbose", opts.Verbose, "Enable verbose logging")
flag.BoolVar(&opts.LogRequests, "log-requests", opts.LogRequests, "Log incoming requests")
flag.BoolVar(&opts.LogResponses, "log-responses", opts.LogResponses, "Log outgoing responses")
flag.BoolVar(&opts.LogErrors, "log-errors", opts.LogErrors, "Log proxy errors")
flag.StringVar(&opts.LogFile, "log-file", opts.LogFile, "Log to file instead of stderr")
flag.Parse()

if flag.NArg() != 1 {
fmt.Println("Usage: ./lightymux [flags] <config_file>")
flag.PrintDefaults()
os.Exit(1)
}

lm, err := NewLightyMux(&opts)
if err != nil {
fmt.Printf("Error initializing LightyMux: %v\n", err)
os.Exit(1)
}

if err := lm.Run(flag.Arg(0)); err != nil {
fmt.Printf("Error running LightyMux: %v\n", err)
os.Exit(1)
}
}

0 comments on commit e54fd45

Please sign in to comment.