diff options
-rw-r--r-- | cache.go | 80 | ||||
-rw-r--r-- | http.go | 29 | ||||
-rw-r--r-- | main.go | 2 | ||||
-rw-r--r-- | osversion.go | 27 |
4 files changed, 101 insertions, 37 deletions
diff --git a/cache.go b/cache.go index b15bb50..24744f6 100644 --- a/cache.go +++ b/cache.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io/ioutil" "log" "strings" @@ -8,6 +9,8 @@ import ( "time" ) +const cacheDuration = 1 * time.Minute + // Holds the cached responses type page struct { raw []byte @@ -25,6 +28,31 @@ var cache = &cacheWrapper{ pages: make(map[string]*page), } +// Checks if a page exists in the cache already. +// If it doesn't, creates an empty entry. +func unNullPage(path string) { + cache.RLock() + pageBlob := cache.pages[path] + cache.RUnlock() + + if pageBlob == nil { + cache.Lock() + cache.pages[path] = &page{ + raw: []byte{}, + expires: time.Time{}, + } + cache.Unlock() + } +} + +// Returns true if the cached page is good. +// False if it's stale. +func (cache *cacheWrapper) isFresh(path string) bool { + cache.RLock() + defer cache.RUnlock() + return time.Now().Before(cache.pages[path].expires) +} + // Wraps the two cache-checking functions. // One for /, the other for various requests. func (cache *cacheWrapper) bap(requestPath string) { @@ -48,24 +76,29 @@ func (cache *cacheWrapper) bap(requestPath string) { } } +// yoinks the raw data to send to the requester +func (cache *cacheWrapper) yoink(path string) []byte { + cache.RLock() + defer cache.RUnlock() + + return cache.pages[path].raw +} + +// yoinks the expiration for the cache +func (cache *cacheWrapper) expiresWhen(path string) string { + cache.RLock() + defer cache.RUnlock() + + return cache.pages[path].expires.Format(time.RFC1123) +} + // Checks if cache either has expired or has nil copy of // the index. If so, it yoinks the page from disk and // sets the expiration time. func bapIndex() { - if cache.pages["/"] == nil { - cache.Lock() - cache.pages["/"] = &page{ - raw: []byte{}, - expires: time.Time{}, - } - cache.Unlock() - } + unNullPage("/") - cache.RLock() - expires := cache.pages["/"].expires - cache.RUnlock() - - if time.Now().Before(expires) { + if cache.isFresh("/") { return } @@ -80,12 +113,31 @@ func bapIndex() { cache.pages["/"] = &page{ raw: bytes, - expires: time.Now().Add(1 * time.Minute), + expires: time.Now().Add(cacheDuration), } } func bapOSVersion(format string) { + path := fmt.Sprintf("/%s/osversion", format) + unNullPage(path) + if cache.isFresh(path) { + return + } + + bytes, err := osVersionQuery(format) + if err != nil { + log.Printf("Could not query OS version: %s", err.Error()) + bytes = []byte("Internal Error") + } + + cache.Lock() + defer cache.Unlock() + + cache.pages[path] = &page{ + raw: bytes, + expires: time.Now().Add(cacheDuration), + } } func bapPkgs(format string) {} diff --git a/http.go b/http.go index 0e3af0c..358efce 100644 --- a/http.go +++ b/http.go @@ -14,7 +14,7 @@ const mimeJSON = "application/json; charset=utf-8" // Eventually. // I chose this monolithic handler that calls validation functions to // determine what to do next because this will make it easier to test. -func validateRequest(w http.ResponseWriter, r *http.Request) { +func mainHandler(w http.ResponseWriter, r *http.Request) { if !methodHop(r) { errHTTP(w, r, errors.New("405 Method Not Allowed"), http.StatusMethodNotAllowed) return @@ -31,25 +31,18 @@ func validateRequest(w http.ResponseWriter, r *http.Request) { return } - var err error - switch routingHop(r) { - case "pkgs": - err = Pkgs(w, r, format) - case "query": - err = Query(w, r, format) - case "uptime": - err = Uptime(w, r, format) - case "usercount": - err = UserCount(w, r, format) - case "users": - err = Users(w, r, format) - case "osversion": - err = OSVersion(w, r, format) - default: - errHTTP(w, r, errors.New("Unknown endpoint"), http.StatusNotFound) - return + cache.bap(r.URL.Path) + out := cache.yoink(r.URL.Path) + + if format == "json" { + w.Header().Set("Content-Type", mimeJSON) + } else { + w.Header().Set("Content-Type", mimePlain) } + w.Header().Set("Expires", cache.expiresWhen(r.URL.Path)) + + _, err := w.Write(out) if err != nil { errHTTP(w, r, err, http.StatusBadRequest) return diff --git a/main.go b/main.go index c71cb3b..e94bb02 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ func main() { }(logChan) mux := http.NewServeMux() - mux.HandleFunc("/", validateRequest) + mux.HandleFunc("/", mainHandler) mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "404 Not Found", http.StatusNotFound) }) diff --git a/osversion.go b/osversion.go index 24ac2c5..6ecac9a 100644 --- a/osversion.go +++ b/osversion.go @@ -1,10 +1,29 @@ package main -import "net/http" +import ( + "errors" + "fmt" + "os/exec" + "strings" +) -// OSVersion handles the /<format>/osversion endpoint. +// osVersionQuery handles the /<format>/osversion endpoint. // Responds with the OpenBSD version. -func OSVersion(w http.ResponseWriter, r *http.Request, format string) error { +func osVersionQuery(format string) ([]byte, error) { + out, err := exec.Command("/usr/bin/uname", "-a").Output() + if err != nil { + return nil, errors.New("Couldn't exec `uname -a`") + } - return nil + split := strings.Split(string(out), " ") + + if format == "json" { + return []byte(fmt.Sprintf(` +{ + "os": "%s", + "version": "%s" +}`, split[0], split[2])), nil + } + + return []byte(fmt.Sprintf("%s %s", split[0], split[2])), nil } |