diff --git a/main.go b/main.go index ff0491a..fcc17c8 100644 --- a/main.go +++ b/main.go @@ -1,38 +1,24 @@ package main import ( - "encoding/json" "flag" "fmt" "html/template" - "io/ioutil" "log" "math/rand" "net" - "net/http" - "net/url" "os" - "regexp" "strings" "time" + "github.com/valyala/fasthttp" + "github.com/wolviecb/short/shortie" + "github.com/asaskevich/govalidator" - "github.com/gorilla/handlers" - "github.com/gorilla/mux" + "github.com/fasthttp/router" "github.com/patrickmn/go-cache" ) -type body struct { - FullHeader bool - IsGhost bool - HasForm bool - IsLink bool - H1 string - H3 string - Line1 string - Line2 string -} - const ( letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // Base strings for randStringBytesMaskImprSrc letterIdxBits = 6 // 6 bits to represent a letter index @@ -44,146 +30,48 @@ const ( var ( // tiny entropy pool src = rand.NewSource(time.Now().UnixNano()) - // KV memory DB - pool *cache.Cache - // Error codes - errBadRequest = fmt.Errorf("Bad Request") - errNotFound = fmt.Errorf("Not Found") + out = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) ) -// get executes the GET command -func get(key string) (string, bool) { - value, status := pool.Get(key) - if !status { - return "", status - } - return value.(string), status +func logger(r fasthttp.RequestHandler) fasthttp.RequestHandler { + return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) { + b := time.Now() + r(ctx) + e := time.Now() + out.Printf("[%v] %v | %s | %s %s - %v - %v | %s", + e.Format("2006/01/02 - 15:04:05"), + ctx.RemoteAddr(), + getHTTP(ctx), + ctx.Method(), + ctx.RequestURI(), + ctx.Response.Header.StatusCode(), + e.Sub(b), + ctx.UserAgent(), + ) + }) } -// set executes the redis SET command -func set(key, suffix string) { - pool.Set(suffix, key, 0) -} - -// redirect receives a key searches the kv database for it and if -// found returns the value, or a error if not found -func redirect(k string) (string, error) { - rgx, _ := regexp.Compile("[a-zA-Z0-9]+") - key := rgx.FindString(k) - key, status := get(key) - if !status { - return "", errNotFound +func getHTTP(ctx *fasthttp.RequestCtx) string { + if ctx.Response.Header.IsHTTP11() { + return "HTTP/1.1" } - u, _ := url.Parse(key) - if u.Scheme == "" { - u.Scheme = "https" - } - return u.String(), nil -} - -// shortener receive a url, validates the url, generate a random suffix string -// of urlSize size, checks if the suffix string is ensure on the kv database -// and then writes the kv pair (suffix, url) to the database, returning the suffix -func shortener(u string, s int) (string, error) { - var su string - if !govalidator.IsURL(u) { - return su, errBadRequest - } - pu, _ := url.Parse(u) - - for { - su = randStringBytesMaskImprSrc(s) - _, status := get(su) - if !status { - break - } - } - - set(pu.String(), su) - return su, nil -} - -// randStringBytesMaskImprSrc Generate random string of n size -func randStringBytesMaskImprSrc(n int) string { - b := make([]byte, n) - // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! - for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { - if remain == 0 { - cache, remain = src.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - return string(b) -} - -func internalError(msg string, err error) body { - log.Println(err) - return body{ - FullHeader: true, - IsGhost: true, - HasForm: true, - H1: "500", - H3: msg, - Line1: "Boo, the ghost is broken :(", - Line2: "His last words where: " + err.Error(), - } -} - -// loadFromFile loads kv pairs from the dumpFile json to the in memory database -func loadFromFile(file string, e, c int) (int, error) { - dumpObj := make(map[string]cache.Item) - jsonFile, err := ioutil.ReadFile(file) - if err != nil { - return 0, err - } - - err = json.Unmarshal([]byte(jsonFile), &dumpObj) - if err != nil { - return 0, err - } - - pool = cache.NewFrom(time.Duration(e)*time.Hour, time.Duration(c)*time.Hour, dumpObj) - return len(dumpObj), err -} - -// itemsFromPost loads kv pairs from a json POST to the in memory database -func loadFromJSON(j []byte, e, c int) (int, error) { - dumpObj := make(map[string]cache.Item) - err := json.Unmarshal(j, &dumpObj) - if err != nil { - return 0, err - } - - pool = cache.NewFrom(time.Duration(e)*time.Hour, time.Duration(c)*time.Hour, dumpObj) - return len(dumpObj), nil -} - -// dumpDbToFile dumps the kv pairs from the in memory database to file -func dumpDbTOFile(file string) (int, error) { - i := pool.Items() - dumpObj, _ := json.Marshal(i) - return len(i), ioutil.WriteFile(file, dumpObj, 0644) + return "HTTP/1.0" } func main() { var ( addr = flag.String("addr", "localhost", "Address to listen for connections") domain = flag.String("domain", "localhost", "Domain to write to the URLs") - dumpFile = flag.String("dump", "urls.json", "Path to the file to dump the kv db") path = flag.String("path", "", "Path to the base URL (https://localhost/PATH/...") - proto = flag.String("proto", "https", "proto to the base URL (HTTPS://localhost/path/... no real https here just to set the url (for like a proxy offloading https") + http = flag.Bool("http", true, "proto to the base URL (HTTPS://localhost/path/... no real https here just to set the url (for like a proxy offloading https") port = flag.Int("port", 8080, "Port to listen for connections") - urlSize = flag.Int("urlsize", 10, "Define the size of the shortened String, default 10") exp = flag.Int("exp", 240, "Default expiration time in hours, default 240") cleanup = flag.Int("cleanup", 1, "Cleanup interval in hours, default 1") version = flag.Bool("v", false, "prints current version") listenAddr string ) + flag.StringVar(&shortie.DumpFile, "urls.json", "Path to the file to dump the kv db", "urls.json") + flag.IntVar(&shortie.URLSize, "Define the size of the shortened String, default 10", 10, "10") flag.Parse() @@ -195,9 +83,11 @@ func main() { if *port > 65535 || *port < 1 { log.Fatalln("Invalid port number") } + shortie.Port = *port if *path != "" && !strings.HasSuffix(*path, "/") { *path = *path + "/" } + shortie.Path = *path ip := net.ParseIP(*addr) if ip != nil { @@ -214,118 +104,26 @@ func main() { log.Fatalln("Invalid domain address") } - pool = cache.New(time.Duration(*exp)*time.Hour, time.Duration(*cleanup)*time.Hour) + if *http { + shortie.Proto = "http" + } + shortie.Domain = *domain + shortie.Exp = time.Duration(*exp) * time.Hour + shortie.Cleanup = time.Duration(*cleanup) * time.Hour + + shortie.Pool = cache.New(shortie.Exp, shortie.Cleanup) t := template.Must(template.ParseFiles("templates/response.html")) - r := mux.NewRouter() + r := router.New() - // Index - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - t.Execute(w, body{ - HasForm: true, - Line1: "Welcome to Short, the simple URL shortener,", - Line2: "Type an URL below to shorten it", - }) - }).Methods("GET") + r.GET("/", shortie.IndexHandler(t)) + r.POST("/", shortie.Short(t)) + r.GET("/{key}", shortie.Redir(t)) + r.GET("/v1/toFile", shortie.ToFile(t)) + r.GET("/v1/fromFile", shortie.FromFile(t)) + r.GET("/v1/count", func(ctx *fasthttp.RequestCtx) { fmt.Fprintf(ctx, "%v", shortie.Pool.ItemCount()) }) + r.GET("/v1/dump", shortie.Dump(t)) + r.POST("/v1/fromPost", shortie.FromPost(t)) - // URL Shortener - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - suf, err := shortener(r.FormValue("url"), *urlSize) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - t.Execute(w, body{ - FullHeader: true, - IsGhost: true, - HasForm: true, - H1: "400", - H3: err.Error(), - Line1: "Boo, looks like this ghost stole this page!", - Line2: "But you can type an URL below to shorten it", - }) - return - } - ru, _ := url.Parse(fmt.Sprintf("%s://%s:%v/%s%s", *proto, *domain, *port, *path, suf)) - t.Execute(w, body{ - IsLink: true, - Line1: ru.String(), - }) - }).Methods("POST") - - // URL Redirect - r.HandleFunc("/{key}", func(w http.ResponseWriter, r *http.Request) { - vals := mux.Vars(r) - key := vals["key"] - if *path != "" { - key = strings.Replace(key, *path, "", 1) - } - u, err := redirect(key) - if err != nil { - w.WriteHeader(http.StatusNotFound) - t.Execute(w, body{ - FullHeader: true, - IsGhost: true, - HasForm: true, - H1: "404", - H3: err.Error(), - Line1: "Boo, looks like this ghost stole this page!", - Line2: "But you can type an URL below to shorten it", - }) - return - } - http.Redirect(w, r, u, http.StatusFound) - }).Methods("GET") - - // Dump DB to file - r.HandleFunc("/v1/toFile", func(w http.ResponseWriter, r *http.Request) { - i, err := dumpDbTOFile(*dumpFile) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - t.Execute(w, internalError("Failed to dump kv DB to file", err)) - return - } - t.Execute(w, body{Line1: fmt.Sprintf("Exported %v items to %v", i, dumpFile)}) - }).Methods("GET") - - // Read DB from file - r.HandleFunc("/v1/fromFile", func(w http.ResponseWriter, r *http.Request) { - i, err := loadFromFile(*dumpFile, *exp, *cleanup) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - t.Execute(w, internalError("Error loading DB from file", err)) - return - } - t.Execute(w, body{Line1: fmt.Sprintf("Imported %v items to the DB", i)}) - }).Methods("GET") - - // Count items on DB - r.HandleFunc("/v1/count", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%v", pool.ItemCount()) - }).Methods("GET") - - r.HandleFunc("/v1/dump", func(w http.ResponseWriter, r *http.Request) { - dumpObj, err := json.Marshal(pool.Items()) - if err != nil { - t.Execute(w, internalError("Unable to dump key value db: ", err)) - return - } - fmt.Fprintf(w, "%s", dumpObj) - }).Methods("GET") - - // Loads DB from json POST - r.HandleFunc("/v1/fromPost", func(w http.ResponseWriter, r *http.Request) { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Execute(w, internalError("Unable to dump key value db: ", err)) - return - } - i, err := loadFromJSON(b, *exp, *cleanup) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - t.Execute(w, internalError("Error loading DB", err)) - return - } - t.Execute(w, body{Line1: fmt.Sprintf("Imported %v items to the DB", i)}) - }).Methods("POST") - - log.Printf("Domain: %s, URL Proto: %s, Listen Address: %s\n", *domain, *proto, listenAddr) - log.Fatal(http.ListenAndServe(listenAddr, handlers.CombinedLoggingHandler(os.Stdout, r))) + log.Printf("Domain: %s, URL Proto: %s, Listen Address: %s\n", shortie.Domain, shortie.Proto, listenAddr) + log.Fatal(fasthttp.ListenAndServe(":8080", logger(r.Handler))) } diff --git a/shortie/go.mod b/shortie/go.mod new file mode 100644 index 0000000..ca30784 --- /dev/null +++ b/shortie/go.mod @@ -0,0 +1,11 @@ +module github.com/wolviecb/short/shortie + +go 1.15 + +require ( + github.com/andybalholm/brotli v1.0.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef + github.com/klauspost/compress v1.11.6 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/valyala/fasthttp v1.19.0 +) diff --git a/shortie/go.sum b/shortie/go.sum new file mode 100644 index 0000000..ade383c --- /dev/null +++ b/shortie/go.sum @@ -0,0 +1,27 @@ +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.6 h1:EgWPCW6O3n1D5n99Zq3xXBt9uCwRGvpwGOusOLNBRSQ= +github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.19.0 h1:PfTS4PeH3xDr3WomrDS2ID8lU2GskK1xS3YG6gIpibU= +github.com/valyala/fasthttp v1.19.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/shortie/shortie.go b/shortie/shortie.go new file mode 100644 index 0000000..03779a4 --- /dev/null +++ b/shortie/shortie.go @@ -0,0 +1,307 @@ +package shortie + +import ( + "encoding/json" + "fmt" + "html/template" + "io/ioutil" + "log" + "math/rand" + "net/http" + "net/url" + "regexp" + "strings" + "time" + + "github.com/asaskevich/govalidator" + "github.com/patrickmn/go-cache" + "github.com/valyala/fasthttp" +) + +type body struct { + FullHeader bool + IsGhost bool + HasForm bool + IsLink bool + H1 string + H3 string + Line1 string + Line2 string +} + +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // Base strings for randStringBytesMaskImprSrc + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = Src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} + +// redirect receives a key searches the kv database for it and if +// found returns the value, or a error if not found +func redirect(k string) (string, error) { + rgx, _ := regexp.Compile("[a-zA-Z0-9]+") + key := rgx.FindString(k) + key, status := get(key) + if !status { + return "", fmt.Errorf("Not Found") + } + u, _ := url.Parse(key) + if u.Scheme == "" { + u.Scheme = "https" + } + return u.String(), nil +} + +// shortener receive a url, validates the url, generate a random suffix string +// of urlSize size, checks if the suffix string is ensure on the kv database +// and then writes the kv pair (suffix, url) to the database, returning the suffix +func shortener(u []byte, s int) (string, error) { + var su string + us := string(u) + if !govalidator.IsURL(string(us)) { + return su, fmt.Errorf("Bad Request") + } + pu, _ := url.Parse(us) + + for { + su = randStringBytesMaskImprSrc(s) + _, status := get(su) + if !status { + break + } + } + + set(pu.String(), su) + return su, nil +} + +// dumpDbToFile dumps the kv pairs from the in memory database to file +func dumpDbTOFile() (int, error) { + i := Pool.Items() + dumpObj, _ := json.Marshal(i) + return len(i), ioutil.WriteFile(DumpFile, dumpObj, 0644) +} + +// loadFromFile loads kv pairs from the dumpFile json to the in memory database +func loadFromFile() (int, error) { + dumpObj := make(map[string]cache.Item) + jsonFile, err := ioutil.ReadFile(DumpFile) + if err != nil { + return 0, err + } + + err = json.Unmarshal([]byte(jsonFile), &dumpObj) + if err != nil { + return 0, err + } + + Pool = cache.NewFrom(Exp, Cleanup, dumpObj) + return len(dumpObj), err +} + +// itemsFromPost loads kv pairs from a json POST to the in memory database +func loadFromJSON(j []byte) (int, error) { + dumpObj := make(map[string]cache.Item) + err := json.Unmarshal(j, &dumpObj) + if err != nil { + return 0, err + } + + Pool = cache.NewFrom(Exp, Cleanup, dumpObj) + return len(dumpObj), nil +} + +func internalError(msg string, err error) body { + log.Println(err) + return body{ + FullHeader: true, + IsGhost: true, + HasForm: true, + H1: "500", + H3: msg, + Line1: "Boo, the ghost is broken :(", + Line2: "His last words where: " + err.Error(), + } +} + +// IndexHandler return a fasthttp.RequestHandler function that genetares the index page +func IndexHandler(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, body{ + HasForm: true, + Line1: "Welcome to Short, the simple URL shortener,", + Line2: "Type an URL below to shorten it", + }) + } +} + +// Short return a fasthttp.RequestHandler function that genetares the shortener page +func Short(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + suf, err := shortener(ctx.FormValue("url"), URLSize) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + t.Execute(ctx, body{ + FullHeader: true, + IsGhost: true, + HasForm: true, + H1: "400", + H3: err.Error(), + Line1: "Boo, looks like this ghost stole this page!", + Line2: "But you can type an URL below to shorten it", + }) + return + } + ru, _ := url.Parse(fmt.Sprintf("%s://%s:%v/%s%s", Proto, Domain, Port, Path, suf)) + t.Execute(ctx, body{ + IsLink: true, + Line1: ru.String(), + }) + } +} + +// Redir return a fasthttp.RequestHandler function that genetares the shortener redirect page +func Redir(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + key := ctx.UserValue("key") + if Path != "" { + key = strings.Replace(key.(string), Path, "", 1) + } + u, err := redirect(key.(string)) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusNotFound) + t.Execute(ctx, body{ + FullHeader: true, + IsGhost: true, + HasForm: true, + H1: "404", + H3: err.Error(), + Line1: "Boo, looks like this ghost stole this page!", + Line2: "But you can type an URL below to shorten it", + }) + return + } + ctx.Redirect(u, fasthttp.StatusFound) + } +} + +// ToFile return a fasthttp.RequestHandler function that dumps the contents of the +// KV db to the DumpFile file +func ToFile(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + i, err := dumpDbTOFile() + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, internalError("Failed to dump kv DB to file", err)) + return + } + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, body{Line1: fmt.Sprintf("Exported %v items to %v", i, DumpFile)}) + } +} + +// FromFile return a fasthttp.RequestHandler function that loads the content of the DumpFile into the +// KV db +func FromFile(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + i, err := loadFromFile() + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, internalError("Error loading DB from file", err)) + return + } + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, body{Line1: fmt.Sprintf("Imported %v items to the DB", i)}) + } +} + +// Dump return a fasthttp.RequestHandler function that dumps the contents of the +// KV db to the ctx handler +func Dump(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + dumpObj, err := json.Marshal(Pool.Items()) + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, internalError("Unable to dump key value db: ", err)) + return + } + fmt.Fprintf(ctx, "%s", dumpObj) + } +} + +// FromPost return a fasthttp.RequestHandler function that loads the content of the JSON POST into the +// KV db +func FromPost(t *template.Template) func(ctx *fasthttp.RequestCtx) { + return func(ctx *fasthttp.RequestCtx) { + i, err := loadFromJSON(ctx.PostBody()) + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, internalError("Error loading DB", err)) + return + } + ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html")) + t.Execute(ctx, body{Line1: fmt.Sprintf("Imported %v items to the DB", i)}) + } +}