-
Notifications
You must be signed in to change notification settings - Fork 5
/
config.go
161 lines (136 loc) · 3.95 KB
/
config.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
package main
import (
"log"
"net/http"
"net/http/httputil"
"os"
"strings"
"github.com/BurntSushi/toml"
)
// Config Struct represents the load balancer's configuration
type Config struct {
// the backend services to balance
Backends []string `toml:"backends"`
// The port the load balancer is bound to
Bind string `toml:"bind"`
// Secure or unsecure http protocol
Mode string `toml:"mode"`
// Path to certificate file
Certfile string `toml:certFile`
// Path to private key file related to certificate
Keyfile string `toml:keyFile`
// Path to access logs
AccessLog string `toml:accessLog`
// Revserse Proxies to forward requests to
Proxies []*httputil.ReverseProxy
// Number of proxies available
HostCount int
// The index of the next proxy to forward a request to
NextHost int
}
// singleton Config instance initially set to nil
var config *Config = nil
// GetConfig implements a singleton pattern to access the Config singleton
func GetConfig(configFile string) *Config {
if config == nil {
config = new(Config)
config.init(configFile)
}
return config
}
// init initializes a new Config instance by reading from the config file
// It will unmarshal the toml file into the Config struct
func (c *Config) init(configFile string) {
_, err := os.Stat(configFile)
if err != nil {
log.Fatal("Config file not found: ", configFile)
}
if _, err := toml.DecodeFile(configFile, c); err != nil {
log.Fatal(err)
}
c.handleUserInput()
c.printConfigInfo()
c.makeProxies()
c.HostCount = len(c.Backends)
c.NextHost = 0
if err := c.configureAccessLog(); err != nil {
log.Fatal(err)
}
}
// handleUserInput checks command line input and overrides config file settings
// Backends is parsed from a raw string to a slice of strings
// TODO: Better input validation
func (c *Config) handleUserInput() {
if *backendStr != "" {
// Remove whitespace from backends
*backendStr = strings.Replace(*backendStr, " ", "", -1)
// Throwing backends into an array
c.Backends = strings.Split(*backendStr, ",")
}
if *bind != "" {
c.Bind = *bind
}
if *mode != "" {
c.Mode = *mode
}
if *certFile != "" {
c.Certfile = *certFile
}
if *keyFile != "" {
c.Keyfile = *keyFile
}
if *accessLog != "" {
c.AccessLog = *accessLog
}
}
// printConfigInfo prints to stderr host and port settings applied to current process
func (c *Config) printConfigInfo() {
// tell the user what info the load balancer is using
for _, v := range c.Backends {
log.Println("using " + v + " as a backend")
}
log.Println("listening on port " + c.Bind)
}
// makeProxies creates slice of ReverseProxies based on the Config's backend hosts
// It returns a slice of httputil.ReverseProxy
func (c *Config) makeProxies() {
// Create a proxy for each backend
c.Proxies = make([]*httputil.ReverseProxy, len(c.Backends))
for i := range c.Backends {
// host must be defined here, and not within the anonymous function.
// Otherwise, you'll run into scoping issues
host := c.Backends[i]
director := func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = host
}
c.Proxies[i] = &httputil.ReverseProxy{Director: director}
}
}
// configureAccessLog checks if the log path exists and if not, sets it up so
// logging can successfully take place
func (c *Config) configureAccessLog() error {
if _, err := os.Stat(config.AccessLog); os.IsNotExist(err) {
file, err := os.OpenFile(config.AccessLog, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return err
}
file.Close()
}
return nil
}
// Function for handling the http requests
func (c *Config) handle(w http.ResponseWriter, r *http.Request) {
c.pickHost()
c.Proxies[c.NextHost].ServeHTTP(w, r)
}
// pickHost determines the next backend host to forward the request to - according to round-robin
// It returns an integer, which represents the host's index in config.Backends
func (c *Config) pickHost() {
nextHost := c.NextHost + 1
if nextHost >= c.HostCount {
c.NextHost = 0
} else {
c.NextHost = nextHost
}
}