From 6a0bab6d2256b572c2d49432370604f1cec16e2b Mon Sep 17 00:00:00 2001 From: zjj Date: Thu, 16 Aug 2018 16:49:21 +0800 Subject: [PATCH 1/2] add file change watch --- registry/file/backend.go | 127 +++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 12 deletions(-) diff --git a/registry/file/backend.go b/registry/file/backend.go index 9c81d4312..fed168b57 100644 --- a/registry/file/backend.go +++ b/registry/file/backend.go @@ -1,30 +1,133 @@ // Package file implements a simple file based registry -// backend which reads the routes from a file once. +// backend which reads the routes from a file. +//file content like registry.static.routes = route add web-svc /test http://127.0.0.1:8082 +//registry.file.path = /home/zjj/fabio.txt +//registry.file.noroutehtmlpath = /home/zjj/404.html package file import ( "io/ioutil" "log" + "os" + "time" "github.com/fabiolb/fabio/config" "github.com/fabiolb/fabio/registry" - "github.com/fabiolb/fabio/registry/static" +) + +type be struct { + cfg *config.File + routeMtime time.Time + norouteMtime time.Time + Routes string + NoRouteHTML string + Interval time.Duration +} + +var ( + zero time.Time ) func NewBackend(cfg *config.File) (registry.Backend, error) { - routes, err := ioutil.ReadFile(cfg.RoutesPath) - if err != nil { - log.Println("[ERROR] Cannot read routes from ", cfg.RoutesPath) + b := &be{cfg: cfg, Interval: 2 * time.Second} + if err := b.readRoute(); err != nil { return nil, err } - noroutehtml, err := ioutil.ReadFile(cfg.NoRouteHTMLPath) - if err != nil { - log.Println("[ERROR] Cannot read no route HTML from ", cfg.NoRouteHTMLPath) + if err := b.readNoRouteHtml(); err != nil { return nil, err } - staticCfg := &config.Static{ - NoRouteHTML: string(noroutehtml), - Routes: string(routes), + return b, nil +} + +func (b *be) readRoute() error { + finfo, err := os.Stat(b.cfg.RoutesPath) + if err != nil { + log.Println("[ERROR] Cannot read routes stat from ", b.cfg.RoutesPath) + return err + } + + if b.routeMtime == zero || b.routeMtime != finfo.ModTime() { + b.routeMtime = finfo.ModTime() + routes, err := ioutil.ReadFile(b.cfg.RoutesPath) + if err != nil { + log.Println("[ERROR] Cannot read routes from ", b.cfg.RoutesPath) + return err + } + b.Routes = string(routes) + } + return nil +} + +func (b *be) readNoRouteHtml() error { + if b.cfg.NoRouteHTMLPath != "" { + finfo, err := os.Stat(b.cfg.NoRouteHTMLPath) + if err != nil { + log.Println("[ERROR] Cannot read no route HTML stat from ", b.cfg.NoRouteHTMLPath) + return err + } + if b.norouteMtime == zero || b.norouteMtime != finfo.ModTime() { + b.norouteMtime = finfo.ModTime() + noroutehtml, err := ioutil.ReadFile(b.cfg.NoRouteHTMLPath) + if err != nil { + log.Println("[ERROR] Cannot read no route HTML from ", b.cfg.NoRouteHTMLPath) + return err + } + b.NoRouteHTML = string(noroutehtml) + } } - return static.NewBackend(staticCfg) + return nil +} + +func (b *be) Register(services []string) error { + return nil +} + +func (b *be) Deregister(serviceName string) error { + return nil +} + +func (b *be) DeregisterAll() error { + return nil +} + +func (b *be) ManualPaths() ([]string, error) { + return nil, nil +} + +func (b *be) ReadManual(string) (value string, version uint64, err error) { + return "", 0, nil +} + +func (b *be) WriteManual(path string, value string, version uint64) (ok bool, err error) { + return false, nil +} + +func (b *be) WatchServices() chan string { + ch := make(chan string, 1) + ch <- b.Routes + go func() { + for { + b.readRoute() + ch <- b.Routes + time.Sleep(b.Interval) + } + }() + return ch +} + +func (b *be) WatchManual() chan string { + return make(chan string) +} + +func (b *be) WatchNoRouteHTML() chan string { + ch := make(chan string, 1) + ch <- b.NoRouteHTML + go func() { + for { + b.readNoRouteHtml() + ch <- b.NoRouteHTML + time.Sleep(b.Interval) + } + }() + return ch } From 72318317921c9b1d6c851546c14290ee43b3241b Mon Sep 17 00:00:00 2001 From: zjj Date: Fri, 17 Aug 2018 11:18:24 +0800 Subject: [PATCH 2/2] =?UTF-8?q?update=20file=20watch=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 1 + config/default.go | 3 + config/load.go | 1 + docs/content/ref/registry.file.interval.md | 10 +++ docs/content/ref/registry.file.path.md | 7 ++ fabio.properties | 5 ++ registry/file/backend.go | 90 +++++----------------- registry/file/file.go | 38 +++++++++ 8 files changed, 85 insertions(+), 70 deletions(-) create mode 100644 docs/content/ref/registry.file.interval.md create mode 100644 registry/file/file.go diff --git a/config/config.go b/config/config.go index f8ad7e4c2..84a8af1a7 100644 --- a/config/config.go +++ b/config/config.go @@ -123,6 +123,7 @@ type Static struct { type File struct { NoRouteHTMLPath string RoutesPath string + Interval time.Duration } type Consul struct { diff --git a/config/default.go b/config/default.go index 45f68424d..4b5623f61 100644 --- a/config/default.go +++ b/config/default.go @@ -64,6 +64,9 @@ var defaultConfig = &Config{ }, Timeout: 10 * time.Second, Retry: 500 * time.Millisecond, + File: File{ + Interval: 2 * time.Second, + }, }, Runtime: Runtime{ GOGC: 800, diff --git a/config/load.go b/config/load.go index d75188471..448253195 100644 --- a/config/load.go +++ b/config/load.go @@ -161,6 +161,7 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.DurationVar(&cfg.Registry.Retry, "registry.retry", defaultConfig.Registry.Retry, "retry interval during startup") f.StringVar(&cfg.Registry.File.RoutesPath, "registry.file.path", defaultConfig.Registry.File.RoutesPath, "path to file based routing table") f.StringVar(&cfg.Registry.File.NoRouteHTMLPath, "registry.file.noroutehtmlpath", defaultConfig.Registry.File.NoRouteHTMLPath, "path to file for HTML returned when no route is found") + f.DurationVar(&cfg.Registry.File.Interval, "registry.file.interval", defaultConfig.Registry.File.Interval, "check file changed interval") f.StringVar(&cfg.Registry.Static.Routes, "registry.static.routes", defaultConfig.Registry.Static.Routes, "static routes") f.StringVar(&cfg.Registry.Static.NoRouteHTML, "registry.static.noroutehtml", defaultConfig.Registry.Static.NoRouteHTML, "HTML which is returned when no route is found") f.StringVar(&cfg.Registry.Consul.Addr, "registry.consul.addr", defaultConfig.Registry.Consul.Addr, "address of the consul agent") diff --git a/docs/content/ref/registry.file.interval.md b/docs/content/ref/registry.file.interval.md new file mode 100644 index 000000000..23894a49c --- /dev/null +++ b/docs/content/ref/registry.file.interval.md @@ -0,0 +1,10 @@ +--- +title: "registry.file.interval" +--- + +`registry.file.interval` configures check file changed interval time. + + +The default is + + registry.file.interval = 2s diff --git a/docs/content/ref/registry.file.path.md b/docs/content/ref/registry.file.path.md index 095ce7e76..269c5ccf0 100644 --- a/docs/content/ref/registry.file.path.md +++ b/docs/content/ref/registry.file.path.md @@ -5,6 +5,13 @@ title: "registry.file.path" `registry.file.path` configures a file based routing table. The value configures the path to the file with the routing table. +#### Example + registry.file.path = /home/zjj/route.txt +file content like is +``` +route add svc / http://1.2.3.4:5000/ +route add svc /test http://1.2.3.4:5001/ +``` The default is registry.file.path = diff --git a/fabio.properties b/fabio.properties index dae2caab0..4a0179acd 100644 --- a/fabio.properties +++ b/fabio.properties @@ -590,6 +590,11 @@ # # registry.file.noroutehtmlpath = +# registry.file.interval configures check file changed interval time. +# +# The default is +# +# registry.file.interval = 2s # registry.consul.addr configures the address of the consul agent to connect to. # diff --git a/registry/file/backend.go b/registry/file/backend.go index fed168b57..50796ae55 100644 --- a/registry/file/backend.go +++ b/registry/file/backend.go @@ -6,8 +6,6 @@ package file import ( - "io/ioutil" - "log" "os" "time" @@ -16,66 +14,18 @@ import ( ) type be struct { - cfg *config.File - routeMtime time.Time - norouteMtime time.Time - Routes string - NoRouteHTML string - Interval time.Duration + cfg *config.File + routesData *filedata + noRouteHTMLData *filedata } -var ( - zero time.Time -) - func NewBackend(cfg *config.File) (registry.Backend, error) { - b := &be{cfg: cfg, Interval: 2 * time.Second} - if err := b.readRoute(); err != nil { - return nil, err - } - if err := b.readNoRouteHtml(); err != nil { + if _, err := os.Stat(cfg.RoutesPath); err != nil { return nil, err } - return b, nil -} - -func (b *be) readRoute() error { - finfo, err := os.Stat(b.cfg.RoutesPath) - if err != nil { - log.Println("[ERROR] Cannot read routes stat from ", b.cfg.RoutesPath) - return err - } - - if b.routeMtime == zero || b.routeMtime != finfo.ModTime() { - b.routeMtime = finfo.ModTime() - routes, err := ioutil.ReadFile(b.cfg.RoutesPath) - if err != nil { - log.Println("[ERROR] Cannot read routes from ", b.cfg.RoutesPath) - return err - } - b.Routes = string(routes) - } - return nil -} -func (b *be) readNoRouteHtml() error { - if b.cfg.NoRouteHTMLPath != "" { - finfo, err := os.Stat(b.cfg.NoRouteHTMLPath) - if err != nil { - log.Println("[ERROR] Cannot read no route HTML stat from ", b.cfg.NoRouteHTMLPath) - return err - } - if b.norouteMtime == zero || b.norouteMtime != finfo.ModTime() { - b.norouteMtime = finfo.ModTime() - noroutehtml, err := ioutil.ReadFile(b.cfg.NoRouteHTMLPath) - if err != nil { - log.Println("[ERROR] Cannot read no route HTML from ", b.cfg.NoRouteHTMLPath) - return err - } - b.NoRouteHTML = string(noroutehtml) - } - } - return nil + b := &be{cfg: cfg, routesData: &filedata{path: cfg.RoutesPath}, noRouteHTMLData: &filedata{path: cfg.NoRouteHTMLPath}} + return b, nil } func (b *be) Register(services []string) error { @@ -103,13 +53,12 @@ func (b *be) WriteManual(path string, value string, version uint64) (ok bool, er } func (b *be) WatchServices() chan string { - ch := make(chan string, 1) - ch <- b.Routes + ch := make(chan string) go func() { for { - b.readRoute() - ch <- b.Routes - time.Sleep(b.Interval) + readFile(b.routesData) + ch <- b.routesData.content + time.Sleep(b.cfg.Interval) } }() return ch @@ -120,14 +69,15 @@ func (b *be) WatchManual() chan string { } func (b *be) WatchNoRouteHTML() chan string { - ch := make(chan string, 1) - ch <- b.NoRouteHTML - go func() { - for { - b.readNoRouteHtml() - ch <- b.NoRouteHTML - time.Sleep(b.Interval) - } - }() + ch := make(chan string) + if b.noRouteHTMLData.path != "" { + go func() { + for { + readFile(b.noRouteHTMLData) + ch <- b.noRouteHTMLData.content + time.Sleep(b.cfg.Interval) + } + }() + } return ch } diff --git a/registry/file/file.go b/registry/file/file.go new file mode 100644 index 000000000..da96b2cf5 --- /dev/null +++ b/registry/file/file.go @@ -0,0 +1,38 @@ +package file + +import ( + "io/ioutil" + "log" + "os" + "time" +) + +//file data +type filedata struct { + path string + content string + mtime time.Time +} + +// Example +// file := &filedata{path:"/home/zjj/routes.txt"} +// err := readFile(file) +func readFile(file *filedata) error { + finfo, err := os.Stat(file.path) + if err != nil { + log.Println("[ERROR] Cannot read file stats() from ", file.path) + return err + } + + lastmtime := finfo.ModTime() + if file.mtime != lastmtime { + data, err := ioutil.ReadFile(file.path) + if err != nil { + log.Println("[ERROR] Cannot read file data from ", file.path) + return err + } + file.content = string(data) + file.mtime = lastmtime + } + return nil +}