From 0d6a16a8d8642b54c7cf301edc3ff33923121065 Mon Sep 17 00:00:00 2001 From: jonbetti Date: Sun, 23 Dec 2018 01:29:56 +0000 Subject: [PATCH] Add "-watch" flag git-svn-id: file:///srv/svn/repo/toyohime/trunk@106 922d331f-388e-da47-97a9-ad700dc0b8b9 --- cmd/vanityserver/dynamic_handler.go | 96 +++++++++++++++++++++++++++++ cmd/vanityserver/main.go | 62 ++++++++++++++----- go.mod | 5 ++ go.sum | 4 ++ 4 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 cmd/vanityserver/dynamic_handler.go create mode 100644 go.sum diff --git a/cmd/vanityserver/dynamic_handler.go b/cmd/vanityserver/dynamic_handler.go new file mode 100644 index 0000000..9087230 --- /dev/null +++ b/cmd/vanityserver/dynamic_handler.go @@ -0,0 +1,96 @@ +package main + +import ( + "log" + "net/http" + "sync" + + "github.com/fsnotify/fsnotify" +) + +type dynamicHandler struct { + *fsnotify.Watcher + + h http.Handler + unhealthy bool + mu sync.RWMutex +} + +func newDynamicHandler(file string, generator func() (http.Handler, error)) *dynamicHandler { + w, err := fsnotify.NewWatcher() + if err != nil { + log.Fatalf("Failed to create fsnotify.Watcher: %v", err) + } + + if err := w.Add(file); err != nil { + log.Fatalf("Could not watch file %q: %v", file, err) + } + + h, err := generator() + if err != nil { + log.Fatalf("Failed generating initial handler: %v", err) + } + + dh := &dynamicHandler{Watcher: w, h: h} + updateHandler := func(h http.Handler, err error) error { + dh.mu.Lock() + defer dh.mu.Unlock() + + if err != nil { + dh.unhealthy = true + return err + } + dh.unhealthy = false + + if h == nil { + panic("nil handler returned from generator") + } + dh.h = h + return nil + } + + go func() { + log.Printf("Watching for changes to %q", file) + for { + select { + case evt, ok := <-w.Events: + if !ok { + return + } + if evt.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Rename) == 0 { + continue + } + if err := updateHandler(generator()); err != nil { + log.Printf("Error switching to new handler: %v", err) + } else { + log.Printf("Updated to new handler based on %q", file) + } + case err, ok := <-w.Errors: + if !ok { + return + } + log.Printf("Error in fsnotify.Watcher: %v", err) + } + } + }() + + return dh +} + +func (dh *dynamicHandler) IsHealthy() bool { + dh.mu.RLock() + defer dh.mu.RUnlock() + + return !dh.unhealthy +} + +func (dh *dynamicHandler) getHandler() http.Handler { + dh.mu.RLock() + defer dh.mu.RUnlock() + + return dh.h +} + +func (dh *dynamicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + dh.getHandler().ServeHTTP(w, r) +} diff --git a/cmd/vanityserver/main.go b/cmd/vanityserver/main.go index ce7638f..dcc1de8 100644 --- a/cmd/vanityserver/main.go +++ b/cmd/vanityserver/main.go @@ -11,6 +11,9 @@ this server. The "-nohealthz" flag disables the "/healthz" endpoint that returns a 200 OK when everything is OK. +The "-watch" flag watches the repo file for changes. When it is updated, the +updated version will be used for serving. + If repo file is not given, "./repos" is used. The file has the following format: pkgroot vcsScheme://vcsHost/user/repo @@ -70,7 +73,7 @@ func serveRepo(mux *http.ServeMux, root string, u *url.URL) { mux.Handle("/"+root+"/", h) } -func addRepoHandlers(mux *http.ServeMux, r io.Reader) { +func addRepoHandlers(mux *http.ServeMux, r io.Reader) error { indexMap := map[string]string{} sc := bufio.NewScanner(r) @@ -82,7 +85,7 @@ func addRepoHandlers(mux *http.ServeMux, r io.Reader) { case 2: // Pass default: - log.Fatalf("Expected line of form \"path vcsScheme://vcsHost/user/repo\" but got %q", sc.Text()) + return fmt.Errorf("expected line of form \"path vcsScheme://vcsHost/user/repo\" but got %q", sc.Text()) } if *showIndex { @@ -92,14 +95,14 @@ func addRepoHandlers(mux *http.ServeMux, r io.Reader) { path := fields[0] u, err := url.Parse(fields[1]) if err != nil { - log.Fatalf("Repo was not a valid URL: %q", fields[1]) + return fmt.Errorf("repo was not a valid URL: %q", fields[1]) } serveRepo(mux, path, u) } if !*showIndex { - return + return nil } var b bytes.Buffer @@ -123,7 +126,7 @@ Nothing here. Host: host, }) if err != nil { - log.Fatalf("Couldn't create index page: %v", err) + return fmt.Errorf("couldn't create index page: %v", err) } buf := b.Bytes() @@ -135,30 +138,41 @@ Nothing here. io.Copy(w, bytes.NewReader(buf)) })) + return nil } -func registerHealthz(mux *http.ServeMux) { +func registerHealthz(mux *http.ServeMux, isHealthy func() bool) { mux.Handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := io.WriteString(w, "OK\r\n") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if isHealthy() { + io.WriteString(w, "OK\r\n") + } else { + http.Error(w, "internal error\r\n", http.StatusInternalServerError) } })) } -func buildServer() *http.Server { +var healthcheck = func() bool { + return true +} + +func generateHandler() (http.Handler, error) { mux := http.NewServeMux() - if f, err := os.Open(reposPath); err != nil { - log.Fatalf("Error opening repos path: %v", err) - } else { - addRepoHandlers(mux, f) + f, err := os.Open(reposPath) + if err != nil { + return nil, fmt.Errorf("error opening %q: %v", reposPath, err) + } + if err := addRepoHandlers(mux, f); err != nil { + return nil, err } if !*noHealthz { - registerHealthz(mux) + registerHealthz(mux, healthcheck) } + return mux, nil +} +func buildServer(h http.Handler) *http.Server { return &http.Server{ // This should be sufficient. ReadTimeout: 5 * time.Second, @@ -166,7 +180,7 @@ func buildServer() *http.Server { IdleTimeout: 5 * time.Second, Addr: ":8080", - Handler: mux, + Handler: h, } } @@ -187,7 +201,21 @@ func main() { reposPath = override } - srv := buildServer() + var h http.Handler + if *watch { + dh := newDynamicHandler(reposPath, generateHandler) + healthcheck = dh.IsHealthy + defer dh.Close() + h = dh + } else { + var err error + h, err = generateHandler() + if err != nil { + log.Printf("Error generating handler: %v", err) + } + } + + srv := buildServer(h) log.Println(srv.ListenAndServe()) } diff --git a/go.mod b/go.mod index 8ab3ef8..318dbe0 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,6 @@ module go.jonnrb.io/vanity + +require ( + github.com/fsnotify/fsnotify v1.4.7 + golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e26a8e4 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo= +golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=