-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
131 lines (111 loc) · 2.78 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
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"sync"
"time"
)
// Load-balancer
type LoadBalancer struct {
Current int // current server index
Mutex sync.Mutex
}
// Server
type Server struct {
URL *url.URL
isHealthy bool
Mutex sync.Mutex
}
type Config struct {
Port string `json:"port"`
HealthCheckInterval string `json:"healthCheckInterval"`
Servers []string `json:"servers"`
}
func main() {
config, err := loadConfig("config.json")
if err != nil {
log.Fatal("Error loading configuration")
}
healthCheckInterval, err := time.ParseDuration(config.HealthCheckInterval)
if err != nil {
log.Fatal("Invalid health check interval")
}
var servers []*Server
for _, serverURL := range config.Servers {
u, _ := url.Parse(serverURL)
server := &Server{URL: u, isHealthy: true}
servers = append(servers, server)
go CheckHealth(server, healthCheckInterval)
}
lb := LoadBalancer{Current: 0}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
server := lb.getNextServer(servers)
if server == nil {
http.Error(w, "no healthy server available", http.StatusServiceUnavailable)
return
}
w.Header().Add("X-Forwarded Server", server.URL.String())
server.ReverseProxy().ServeHTTP(w, r)
})
log.Println("Starting load balancer on port", config.Port)
err = http.ListenAndServe(config.Port, nil)
if err != nil {
log.Fatalf("Error starting load balancer: %s\n", err.Error())
}
}
// load balancer algorithm
func (lb *LoadBalancer) getNextServer(servers []*Server) *Server {
lb.Mutex.Lock()
defer lb.Mutex.Unlock()
// loop to find healthy server
for i := 0; i < len(servers); i++ {
server := servers[lb.Current]
lb.Current = (lb.Current + 1) % len(servers)
// check if server is healthy
server.Mutex.Lock()
if server.isHealthy {
server.Mutex.Unlock()
return server
}
server.Mutex.Unlock()
}
return nil
}
func CheckHealth(s *Server, healthCheckInterval time.Duration) {
for range time.Tick(healthCheckInterval) {
// head request to server
res, err := http.Head(s.URL.String())
s.Mutex.Lock()
if err != nil || res.StatusCode != http.StatusOK {
fmt.Printf("%s is down\n", s.URL)
s.isHealthy = false
} else {
s.isHealthy = true
}
s.Mutex.Unlock()
// close the response body using condn [runtime error if res is nil as we cant access it]
if res != nil {
res.Body.Close()
}
}
}
func (s *Server) ReverseProxy() *httputil.ReverseProxy {
return httputil.NewSingleHostReverseProxy(s.URL)
}
func loadConfig(file string) (Config, error) {
var config Config
data, err := os.ReadFile(file)
if err != nil {
return config, err
}
err = json.Unmarshal(data, &config)
if err != nil {
return config, err
}
return config, nil
}