Browse Source

feat: Auto reconfiguration on server side

master
parent
commit
a250c86304
  1. 10
      README.md
  2. 2
      client/main.go
  3. 22
      server/conf.go
  4. 38
      server/main.go
  5. 2
      server/pot.go

10
README.md

@ -30,12 +30,22 @@ Default configuration file structure:
`brew-time-sec` is the approximate time it takes to brew coffee in seconds. After this amount is passed, status of the pot changes to `Pouring` and will stay it until `max-pour-time-sec` seconds are passed or an incoming request with `WHEN` method is presented. `brew-time-sec` is the approximate time it takes to brew coffee in seconds. After this amount is passed, status of the pot changes to `Pouring` and will stay it until `max-pour-time-sec` seconds are passed or an incoming request with `WHEN` method is presented.
Configuration is initialized on program start and is responsive to change of the configuration file. If the configuration file is changed - the changes will take place in server program as well. Configuration file is reopened and applied once on each incoming request. This behaviour can be turned off via `-no-autoreconf` flag. If flag is present - initial configuration will stay constant.
By default port 80 is used, but can be changed with a `-port` flag: By default port 80 is used, but can be changed with a `-port` flag:
`HTCPCP-server -port 8000` `HTCPCP-server -port 8000`
Server will listen and handle incoming requests. Server will listen and handle incoming requests.
Examples:
- `HTCPCP-server -port 8000` - starts a server on port 8000
- `HTCPCP-server` - starts a server on port 80
- `HTCPCP-server -port 8000 -no-autoreconf` - starts a server on port 8000, configuration is constant
- `HTCPCP-server -version` - prints a version information
### Client ### Client
Client is used to interact with the server and has such syntax: Client is used to interact with the server and has such syntax:

2
client/main.go

@ -71,7 +71,7 @@ func main() {
flag.Parse() flag.Parse()
if *version { if *version {
fmt.Printf("HTCPCP-client %s\nKasianov Nikolai Alekseevich (Unbewohnte)\n", Version) fmt.Printf("HTCPCP-client %s\n(C) 2024 Kasianov Nikolai Alekseevich (Unbewohnte)\n", Version)
return return
} }

22
server/conf.go

@ -43,44 +43,44 @@ func DefaultConf() Conf {
} }
// Tries to retrieve configuration structure from given json file // Tries to retrieve configuration structure from given json file
func ConfFromFile(path string) (Conf, error) { func ConfFromFile(path string) (*Conf, error) {
confFile, err := os.Open(path) confFile, err := os.Open(path)
if err != nil { if err != nil {
return DefaultConf(), err return nil, err
} }
defer confFile.Close() defer confFile.Close()
confBytes, err := io.ReadAll(confFile) confBytes, err := io.ReadAll(confFile)
if err != nil { if err != nil {
return DefaultConf(), err return nil, err
} }
var conf Conf var conf *Conf
err = json.Unmarshal(confBytes, &conf) err = json.Unmarshal(confBytes, &conf)
if err != nil { if err != nil {
return DefaultConf(), err return nil, err
} }
return conf, nil return conf, nil
} }
// Create a new configuration file // Create a new configuration file
func CreateConf(path string, conf Conf) (Conf, error) { func CreateConf(path string, conf Conf) (*Conf, error) {
confFile, err := os.Create(path) confFile, err := os.Create(path)
if err != nil { if err != nil {
return DefaultConf(), err return nil, err
} }
defer confFile.Close() defer confFile.Close()
confJsonBytes, err := json.MarshalIndent(conf, "", " ") confJsonBytes, err := json.MarshalIndent(&conf, "", " ")
if err != nil { if err != nil {
return conf, err return nil, err
} }
_, err = confFile.Write(confJsonBytes) _, err = confFile.Write(confJsonBytes)
if err != nil { if err != nil {
return conf, nil return nil, nil
} }
return conf, nil return &conf, nil
} }

38
server/main.go

@ -24,11 +24,12 @@ import (
const ConfName string = "conf.json" const ConfName string = "conf.json"
const Version string = "0.1-server" const Version string = "0.2-server"
var ( var (
port *uint = flag.Uint("port", 80, "Set server port") port *uint = flag.Uint("port", 80, "Set server port")
version *bool = flag.Bool("version", false, "Print version information") version *bool = flag.Bool("version", false, "Print version information")
noAutoReconf *bool = flag.Bool("no-autoreconf", false, "Do NOT reopen configuration file and reload stored configuration on each new request")
) )
func main() { func main() {
@ -36,7 +37,7 @@ func main() {
flag.Parse() flag.Parse()
if *version { if *version {
fmt.Printf("HTCPCP-server %s\nKasianov Nikolai Alekseevich (Unbewohnte)\n", Version) fmt.Printf("HTCPCP-server %s\n(C) 2024 Kasianov Nikolai Alekseevich (Unbewohnte)\n", Version)
return return
} }
@ -47,14 +48,15 @@ func main() {
} }
wDir := filepath.Dir(exePath) wDir := filepath.Dir(exePath)
// Open commands file, create if does not exist confPath := filepath.Join(wDir, ConfName)
conf, err := ConfFromFile(filepath.Join(wDir, ConfName)) // Open configuration file, create if does not exist
conf, err := ConfFromFile(confPath)
if err != nil { if err != nil {
_, err = CreateConf(filepath.Join(wDir, ConfName), DefaultConf()) _, err = CreateConf(confPath, DefaultConf())
if err != nil { if err != nil {
log.Fatalf("Failed to create a new commands file: %s", err) log.Fatalf("Failed to create a new configuration file: %s", err)
} }
log.Printf("Created a new commands file") log.Printf("Created a new configuration file")
os.Exit(0) os.Exit(0)
} }
@ -62,6 +64,22 @@ func main() {
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Reload configuration options in case something's changed
if !*noAutoReconf {
conf, err = ConfFromFile(confPath)
if err != nil {
log.Fatalf("Could not reopen configuration file: %s", err)
}
// If not ready - wait for this iteration to end, not now
if pot.State == PotStatusReady {
pot.commands = conf.Commands
pot.CoffeeType = conf.CoffeeType
pot.BrewTimeSec = conf.BrewTimeSec
pot.MaxPourTimeSec = conf.MaxPourTimeSec
}
}
if r.Method == "BREW" || r.Method == "POST" { if r.Method == "BREW" || r.Method == "POST" {
// Brew some coffee // Brew some coffee
if r.Header.Get("Content-Type") != "application/coffee-pot-command" { if r.Header.Get("Content-Type") != "application/coffee-pot-command" {
@ -102,6 +120,8 @@ func main() {
err := pot.StopPouring() err := pot.StopPouring()
if err != nil { if err != nil {
log.Printf("Failed to stop pouring milk: %s\n", err) log.Printf("Failed to stop pouring milk: %s\n", err)
http.Error(w, "Coffee is not brewed yet", http.StatusBadRequest)
return
} }
} else { } else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)

2
server/pot.go

@ -34,7 +34,7 @@ type Pot struct {
commands Commands commands Commands
} }
func NewPot(conf Conf) *Pot { func NewPot(conf *Conf) *Pot {
return &Pot{ return &Pot{
State: PotStatusReady, State: PotStatusReady,
CoffeeType: conf.CoffeeType, CoffeeType: conf.CoffeeType,

Loading…
Cancel
Save