-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmain.go
180 lines (149 loc) · 4.53 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"
"gopkg.in/yaml.v2"
)
// Config struct for webapp config
type Config struct {
Server struct {
// Host is the local machine IP Address to bind the HTTP Server to
Host string `yaml:"host"`
// Port is the local machine TCP Port to bind the HTTP Server to
Port string `yaml:"port"`
Timeout struct {
// Server is the general server timeout to use
// for graceful shutdowns
Server time.Duration `yaml:"server"`
// Write is the amount of time to wait until an HTTP server
// write opperation is cancelled
Write time.Duration `yaml:"write"`
// Read is the amount of time to wait until an HTTP server
// read operation is cancelled
Read time.Duration `yaml:"read"`
// Read is the amount of time to wait
// until an IDLE HTTP session is closed
Idle time.Duration `yaml:"idle"`
} `yaml:"timeout"`
} `yaml:"server"`
}
// NewConfig returns a new decoded Config struct
func NewConfig(configPath string) (*Config, error) {
// Create config structure
config := &Config{}
// Open config file
file, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&config); err != nil {
return nil, err
}
return config, nil
}
// ValidateConfigPath just makes sure, that the path provided is a file,
// that can be read
func ValidateConfigPath(path string) error {
s, err := os.Stat(path)
if err != nil {
return err
}
if s.IsDir() {
return fmt.Errorf("'%s' is a directory, not a normal file", path)
}
return nil
}
// ParseFlags will create and parse the CLI flags
// and return the path to be used elsewhere
func ParseFlags() (string, error) {
// String that contains the configured configuration path
var configPath string
// Set up a CLI flag called "-config" to allow users
// to supply the configuration file
flag.StringVar(&configPath, "config", "./config.yml", "path to config file")
// Actually parse the flags
flag.Parse()
// Validate the path first
if err := ValidateConfigPath(configPath); err != nil {
return "", err
}
// Return the configuration path
return configPath, nil
}
// NewRouter generates the router used in the HTTP Server
func NewRouter() *http.ServeMux {
// Create router and define routes and return that router
router := http.NewServeMux()
router.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
})
return router
}
// Run will run the HTTP Server
func (config Config) Run() {
// Set up a channel to listen to for interrupt signals
var runChan = make(chan os.Signal, 1)
// Define server options
server := &http.Server{
Addr: config.Server.Host + ":" + config.Server.Port,
Handler: NewRouter(),
ReadTimeout: config.Server.Timeout.Read * time.Second,
WriteTimeout: config.Server.Timeout.Write * time.Second,
IdleTimeout: config.Server.Timeout.Idle * time.Second,
}
// Handle ctrl+c/ctrl+x interrupt
signal.Notify(runChan, os.Interrupt)
// Alert the user that the server is starting
log.Printf("Server is starting on %s\n", server.Addr)
// Run the server on a new goroutine
go func() {
if err := server.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
// Normal interrupt operation, ignore
} else {
log.Fatalf("Server failed to start due to err: %v", err)
}
}
}()
// Block on this channel listeninf for those previously defined syscalls assign
// to variable so we can let the user know why the server is shutting down
interrupt := <-runChan
// Set up a context to allow for graceful server shutdowns in the event
// of an OS interrupt (defers the cancel just in case)
ctx, cancel := context.WithTimeout(
context.Background(),
config.Server.Timeout.Server,
)
defer cancel()
// If we get one of the pre-prescribed syscalls, gracefully terminate the server
// while alerting the user
log.Printf("Server is shutting down due to %+v\n", interrupt)
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server was unable to gracefully shutdown due to err: %+v", err)
}
}
// Func main should be as small as possible and do as little as possible by convention
func main() {
// Generate our config based on the config supplied
// by the user in the flags
cfgPath, err := ParseFlags()
if err != nil {
log.Fatal(err)
}
cfg, err := NewConfig(cfgPath)
if err != nil {
log.Fatal(err)
}
// Run the server
cfg.Run()
}