Add "-watch" flag

git-svn-id: file:///srv/svn/repo/toyohime/trunk@106 922d331f-388e-da47-97a9-ad700dc0b8b9
This commit is contained in:
jonbetti 2018-12-23 01:29:56 +00:00
parent 59be7edc65
commit 0d6a16a8d8
4 changed files with 150 additions and 17 deletions

View File

@ -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)
}

View File

@ -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())
}

5
go.mod
View File

@ -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
)

4
go.sum Normal file
View File

@ -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=