about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBen Morrison <ben@gbmor.dev>2020-05-09 01:26:15 -0400
committerBen Morrison <ben@gbmor.dev>2020-05-09 01:26:15 -0400
commit460684d77834d65fa47f3534056baa72dfba17ec (patch)
tree15dc1197adcc540c52efb21b5f6d6dc767466cbf
parent01b6a93f2cfc8b6dd54045c726d87dedcbf9ccc7 (diff)
downloadapi-460684d77834d65fa47f3534056baa72dfba17ec.tar.gz
consolidating functionality to a single handler
also, making cache interaction more generic.
-rw-r--r--cache.go80
-rw-r--r--http.go29
-rw-r--r--main.go2
-rw-r--r--osversion.go27
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
 }