Add hot config reload

This commit is contained in:
simon 2019-06-01 15:00:50 -04:00
parent cdc3017948
commit 4f7b5b7d98
6 changed files with 65 additions and 19 deletions

View File

@ -19,13 +19,22 @@ and error handling. Built for automated web scraping.
### Usage ### Usage
```bash ```bash
wget https://simon987.net/data/architeuthis/9_architeuthis.tar.gz wget https://simon987.net/data/architeuthis/11_architeuthis.tar.gz
tar -xzf 9_architeuthis.tar.gz tar -xzf 11_architeuthis.tar.gz
vim config.json # Configure settings here vim config.json # Configure settings here
./architeuthis ./architeuthis
``` ```
### Hot config reload
```bash
# Note: this will reset current rate limiters, if there are many active
# connections, this might cause a small request spike and go over
# the rate limits.
./reload.sh
```
### Sample configuration ### Sample configuration
```json ```json

View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/sirupsen/logrus"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"io/ioutil" "io/ioutil"
"os" "os"
@ -98,6 +99,32 @@ func applyConfig(proxy *Proxy) {
} }
} }
func (b *Balancer) reloadConfig() {
b.proxyMutex.Lock()
loadConfig()
if b.proxies != nil {
b.proxies = b.proxies[:0]
}
for _, proxyConf := range config.Proxies {
proxy, err := NewProxy(proxyConf.Name, proxyConf.Url)
handleErr(err)
b.proxies = append(b.proxies, proxy)
applyConfig(proxy)
logrus.WithFields(logrus.Fields{
"name": proxy.Name,
"url": proxy.Url,
}).Info("Proxy")
}
b.proxyMutex.Unlock()
logrus.Info("Reloaded config")
}
func handleErr(err error) { func handleErr(err error) {
if err != nil { if err != nil {
panic(err) panic(err)

3
gc.go
View File

@ -24,11 +24,14 @@ func (b *Balancer) setupGarbageCollector() {
func (b *Balancer) cleanAllExpiredLimits() { func (b *Balancer) cleanAllExpiredLimits() {
before := 0 before := 0
after := 0 after := 0
b.proxyMutex.RLock()
for _, p := range b.proxies { for _, p := range b.proxies {
before += len(p.Limiters) before += len(p.Limiters)
cleanExpiredLimits(p) cleanExpiredLimits(p)
after += len(p.Limiters) after += len(p.Limiters)
} }
b.proxyMutex.RUnlock()
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"removed": before - after, "removed": before - after,

2
jenkins/Jenkinsfile vendored
View File

@ -26,7 +26,7 @@ pipeline {
sh 'cp *.go "/go/src/github.com/simon987/Architeuthis"' sh 'cp *.go "/go/src/github.com/simon987/Architeuthis"'
sh 'cd /go/src/github.com/simon987/Architeuthis && go get ./...' sh 'cd /go/src/github.com/simon987/Architeuthis && go get ./...'
sh 'cd /go/src/github.com/simon987/Architeuthis && go build -a -installsuffix cgo -o "${WORKSPACE}/architeuthis" .' sh 'cd /go/src/github.com/simon987/Architeuthis && go build -a -installsuffix cgo -o "${WORKSPACE}/architeuthis" .'
sh 'tar -czf ${BUILD_NUMBER}_architeuthis.tar.gz config.json architeuthis' sh 'tar -czf ${BUILD_NUMBER}_architeuthis.tar.gz config.json architeuthis reload.sh'
sshPut remote: remote, from: env.BUILD_NUMBER + '_architeuthis.tar.gz', into: 'architeuthis/webroot/' sshPut remote: remote, from: env.BUILD_NUMBER + '_architeuthis.tar.gz', into: 'architeuthis/webroot/'
} }
} }

36
main.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"github.com/elazarl/goproxy" "github.com/elazarl/goproxy"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/ryanuber/go-glob" "github.com/ryanuber/go-glob"
@ -11,12 +12,14 @@ import (
"net/url" "net/url"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
) )
type Balancer struct { type Balancer struct {
server *goproxy.ProxyHttpServer server *goproxy.ProxyHttpServer
proxies []*Proxy proxies []*Proxy
proxyMutex *sync.RWMutex
} }
type ExpiringLimiter struct { type ExpiringLimiter struct {
@ -113,6 +116,7 @@ func New() *Balancer {
balancer := new(Balancer) balancer := new(Balancer)
balancer.proxyMutex = &sync.RWMutex{}
balancer.server = goproxy.NewProxyHttpServer() balancer.server = goproxy.NewProxyHttpServer()
balancer.server.OnRequest().HandleConnect(goproxy.AlwaysMitm) balancer.server.OnRequest().HandleConnect(goproxy.AlwaysMitm)
@ -120,6 +124,7 @@ func New() *Balancer {
balancer.server.OnRequest().DoFunc( balancer.server.OnRequest().DoFunc(
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
balancer.proxyMutex.RLock()
p := balancer.chooseProxy() p := balancer.chooseProxy()
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
@ -129,6 +134,7 @@ func New() *Balancer {
}).Trace("Routing request") }).Trace("Routing request")
resp, err := p.processRequest(r) resp, err := p.processRequest(r)
balancer.proxyMutex.RUnlock()
if err != nil { if err != nil {
logrus.WithError(err).Trace("Could not complete request") logrus.WithError(err).Trace("Could not complete request")
@ -137,6 +143,17 @@ func New() *Balancer {
return nil, resp return nil, resp
}) })
balancer.server.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/reload" {
balancer.reloadConfig()
_, _ = fmt.Fprint(w, "Reloaded\n")
} else {
w.Header().Set("Content-Type", "application/json")
_, _ = fmt.Fprint(w, "{\"name\":\"Architeuthis\",\"version\":1.0}")
}
})
return balancer return balancer
} }
@ -275,21 +292,8 @@ func NewProxy(name, stringUrl string) (*Proxy, error) {
func main() { func main() {
logrus.SetLevel(logrus.TraceLevel) logrus.SetLevel(logrus.TraceLevel)
loadConfig()
balancer := New() balancer := New()
balancer.reloadConfig()
for _, proxyConf := range config.Proxies {
proxy, err := NewProxy(proxyConf.Name, proxyConf.Url)
handleErr(err)
balancer.proxies = append(balancer.proxies, proxy)
applyConfig(proxy)
logrus.WithFields(logrus.Fields{
"name": proxy.Name,
"url": proxy.Url,
}).Info("Proxy")
}
balancer.setupGarbageCollector() balancer.setupGarbageCollector()
balancer.Run() balancer.Run()

3
reload.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
curl -X GET localhost:5050/reload