mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-16 10:14:04 +02:00
v0.5-rc2: added reload cooldown, fixed auto reload, updated API
This commit is contained in:
parent
996b418ea9
commit
c0ebd9f8c0
7 changed files with 76 additions and 45 deletions
5
Makefile
5
Makefile
|
@ -37,3 +37,8 @@ repush:
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "repush"
|
git commit -m "repush"
|
||||||
git push gitlab dev --force
|
git push gitlab dev --force
|
||||||
|
|
||||||
|
rapid-crash:
|
||||||
|
sudo docker run --restart=always --name test_crash debian:bookworm-slim /bin/cat &&\
|
||||||
|
sleep 3 &&\
|
||||||
|
sudo docker rm -f test_crash
|
||||||
|
|
13
README.md
13
README.md
|
@ -24,7 +24,6 @@ A [lightweight](docs/benchmark_result.md), easy-to-use, and efficient reverse pr
|
||||||
- Auto configuration for docker contaienrs
|
- Auto configuration for docker contaienrs
|
||||||
- Auto hot-reload on container state / config file changes
|
- Auto hot-reload on container state / config file changes
|
||||||
- Support HTTP(s), TCP and UDP
|
- Support HTTP(s), TCP and UDP
|
||||||
- Support HTTP(s) round robin load balancing
|
|
||||||
- Web UI for configuration and monitoring (See [screenshots](screeenshots))
|
- Web UI for configuration and monitoring (See [screenshots](screeenshots))
|
||||||
- Written in **[Go](https://go.dev)**
|
- Written in **[Go](https://go.dev)**
|
||||||
|
|
||||||
|
@ -110,14 +109,16 @@ See [providers.example.yml](providers.example.yml) for examples
|
||||||
|
|
||||||
## Build it yourself
|
## Build it yourself
|
||||||
|
|
||||||
1. Install / Upgrade [go (>=1.22)](https://go.dev/doc/install) and `make` if not already
|
1. Clone the repository `git clone https://github.com/yusing/go-proxy --depth=1`
|
||||||
|
|
||||||
2. Clear cache if you have built this before (go < 1.22) with `go clean -cache`
|
2. Install / Upgrade [go (>=1.22)](https://go.dev/doc/install) and `make` if not already
|
||||||
|
|
||||||
3. get dependencies with `make get`
|
3. Clear cache if you have built this before (go < 1.22) with `go clean -cache`
|
||||||
|
|
||||||
4. build binary with `make build`
|
4. get dependencies with `make get`
|
||||||
|
|
||||||
5. start your container with `make up` (docker) or `bin/go-proxy` (binary)
|
5. build binary with `make build`
|
||||||
|
|
||||||
|
6. start your container with `make up` (docker) or `bin/go-proxy` (binary)
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
|
@ -30,6 +31,8 @@ type Provider struct {
|
||||||
watcherCancel context.CancelFunc
|
watcherCancel context.CancelFunc
|
||||||
|
|
||||||
l *logrus.Entry
|
l *logrus.Entry
|
||||||
|
|
||||||
|
cooldownCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderType string
|
type ProviderType string
|
||||||
|
@ -45,9 +48,10 @@ func newProvider(name string, t ProviderType) *Provider {
|
||||||
t: t,
|
t: t,
|
||||||
routes: R.NewRoutes(),
|
routes: R.NewRoutes(),
|
||||||
reloadReqCh: make(chan struct{}, 1),
|
reloadReqCh: make(chan struct{}, 1),
|
||||||
|
cooldownCh: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
p.l = logrus.WithField("provider", p)
|
p.l = logrus.WithField("provider", p)
|
||||||
|
go p.processReloadRequests()
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
func NewFileProvider(filename string) *Provider {
|
func NewFileProvider(filename string) *Provider {
|
||||||
|
@ -100,7 +104,8 @@ func (p *Provider) StartAllRoutes() E.NestedError {
|
||||||
nStarted++
|
nStarted++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
p.l.Infof("%d routes started, %d failed", nStarted, nFailed)
|
|
||||||
|
p.l.Debugf("%d routes started, %d failed", nStarted, nFailed)
|
||||||
return errors.Build()
|
return errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,16 +125,17 @@ func (p *Provider) StopAllRoutes() E.NestedError {
|
||||||
nStopped++
|
nStopped++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
p.l.Infof("%d routes stopped, %d failed", nStopped, nFailed)
|
p.l.Debugf("%d routes stopped, %d failed", nStopped, nFailed)
|
||||||
return errors.Build()
|
return errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ReloadRoutes() {
|
func (p *Provider) ReloadRoutes() {
|
||||||
defer p.l.Info("routes reloaded")
|
select {
|
||||||
|
case p.reloadReqCh <- struct{}{}:
|
||||||
p.StopAllRoutes()
|
// Successfully sent reload request
|
||||||
p.loadRoutes()
|
default:
|
||||||
p.StartAllRoutes()
|
// Reload request already in progress, ignore this request
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) GetCurrentRoutes() *R.Routes {
|
func (p *Provider) GetCurrentRoutes() *R.Routes {
|
||||||
|
@ -142,15 +148,14 @@ func (p *Provider) watchEvents() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.reloadReqCh: // block until last reload is done
|
case <-p.watcherCtx.Done():
|
||||||
p.ReloadRoutes()
|
return
|
||||||
continue // ignore events once after reload
|
|
||||||
case event, ok := <-events:
|
case event, ok := <-events:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Info(event)
|
l.Info(event)
|
||||||
p.reloadReqCh <- struct{}{}
|
p.ReloadRoutes()
|
||||||
case err, ok := <-errs:
|
case err, ok := <-errs:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -163,6 +168,29 @@ func (p *Provider) watchEvents() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) processReloadRequests() {
|
||||||
|
for range p.reloadReqCh {
|
||||||
|
// prevent busy loop caused by a container
|
||||||
|
// repeating crashing and restarting
|
||||||
|
select {
|
||||||
|
case p.cooldownCh <- struct{}{}:
|
||||||
|
p.l.Info("Starting to reload routes")
|
||||||
|
|
||||||
|
p.StopAllRoutes()
|
||||||
|
p.loadRoutes()
|
||||||
|
p.StartAllRoutes()
|
||||||
|
|
||||||
|
p.l.Info("Routes reloaded")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(reloadCooldown)
|
||||||
|
<-p.cooldownCh
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) loadRoutes() E.NestedError {
|
func (p *Provider) loadRoutes() E.NestedError {
|
||||||
entries, err := p.GetProxyEntries()
|
entries, err := p.GetProxyEntries()
|
||||||
|
|
||||||
|
@ -183,3 +211,5 @@ func (p *Provider) loadRoutes() E.NestedError {
|
||||||
})
|
})
|
||||||
return errors.Build()
|
return errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reloadCooldown = 300 * time.Millisecond
|
||||||
|
|
|
@ -20,9 +20,8 @@ import (
|
||||||
type (
|
type (
|
||||||
HTTPRoute struct {
|
HTTPRoute struct {
|
||||||
Alias PT.Alias `json:"alias"`
|
Alias PT.Alias `json:"alias"`
|
||||||
|
TargetURL *URL `json:"target_url"`
|
||||||
TargetURL URL
|
PathPatterns PT.PathPatterns `json:"path_patterns"`
|
||||||
PathPatterns PT.PathPatterns
|
|
||||||
|
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
handler *P.ReverseProxy
|
handler *P.ReverseProxy
|
||||||
|
@ -53,7 +52,7 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
|
||||||
if !ok {
|
if !ok {
|
||||||
r = &HTTPRoute{
|
r = &HTTPRoute{
|
||||||
Alias: entry.Alias,
|
Alias: entry.Alias,
|
||||||
TargetURL: URL(*entry.URL),
|
TargetURL: (*URL)(entry.URL),
|
||||||
PathPatterns: entry.PathPatterns,
|
PathPatterns: entry.PathPatterns,
|
||||||
handler: rp,
|
handler: rp,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,4 +23,4 @@ func (f *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nested
|
||||||
return fwHelper.Add(ctx, f)
|
return fwHelper.Add(ctx, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fwHelper = newFileWatcherHelper()
|
var fwHelper = newFileWatcherHelper(common.ConfigBasePath)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,14 +25,12 @@ type fileWatcherStream struct {
|
||||||
errCh chan E.NestedError
|
errCh chan E.NestedError
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileWatcherHelper() *fileWatcherHelper {
|
func newFileWatcherHelper(dirPath string) *fileWatcherHelper {
|
||||||
w, err := fsnotify.NewWatcher()
|
w, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||||
}
|
}
|
||||||
// watch config path for all changes
|
if err = w.Add(dirPath); err != nil {
|
||||||
err = w.Add(common.ConfigBasePath)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||||
}
|
}
|
||||||
helper := &fileWatcherHelper{
|
helper := &fileWatcherHelper{
|
||||||
|
@ -60,26 +57,24 @@ func (h *fileWatcherHelper) Add(ctx context.Context, w *fileWatcher) (<-chan Eve
|
||||||
errCh: make(chan E.NestedError),
|
errCh: make(chan E.NestedError),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
h.Remove(w)
|
s.stopped <- struct{}{}
|
||||||
return
|
|
||||||
case <-s.stopped:
|
case <-s.stopped:
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
close(s.eventCh)
|
||||||
|
close(s.errCh)
|
||||||
|
delete(h.m, w.filename)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
h.m[w.filename] = s
|
h.m[w.filename] = s
|
||||||
return s.eventCh, s.errCh
|
return s.eventCh, s.errCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *fileWatcherHelper) Remove(w *fileWatcher) {
|
|
||||||
h.mu.Lock()
|
|
||||||
defer h.mu.Unlock()
|
|
||||||
|
|
||||||
h.m[w.filename].stopped <- struct{}{}
|
|
||||||
delete(h.m, w.filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *fileWatcherHelper) start() {
|
func (h *fileWatcherHelper) start() {
|
||||||
defer h.wg.Done()
|
defer h.wg.Done()
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.5.0-rc1
|
0.5.0-rc2
|
Loading…
Add table
Reference in a new issue