From 325846b47be2f71caf010aa6c936b12f4307d8ba Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 18 Mar 2024 22:49:11 -0400 Subject: [PATCH] Implemented expiry & max origins. refactored constants to utils/constants. --- docs/benchmarks.md | 22 ++++++++++++++++++++++ routes.go | 9 ++++++--- tests/avg.py | 13 +++++++++++++ utils/constants.go | 8 ++++++++ utils/utils.go | 47 ++++++++++++++++++++++++++-------------------- 5 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 docs/benchmarks.md create mode 100644 tests/avg.py create mode 100644 utils/constants.go diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 0000000..8368bf7 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,22 @@ +All Benchmarks are conducted with go run locally, and the redis being ran on the same host as prod. + +These numbers were taken using tests/avg.py + +## /hit with different TTL implementations (in relation to the data return) +### With expire before + +AVG: 66.62284ms, MIN: 53.67ms, MAX: 311.721ms, COUNT: 100 + +### With expire after + +AVG: 64.50046999999999ms, MIN: 53.162ms, MAX: 193.546ms, COUNT: 100 + +### With expire as a go routine + +AVG: 43.408190000000005ms, MIN: 28.913999999999998ms, MAX: 167.731ms, COUNT: 100 + +### With no expire + +AVG: 38.95288ms, MIN: 22.008ms, MAX: 165.397ms, COUNT: 100 + + diff --git a/routes.go b/routes.go index 3511220..c602f40 100644 --- a/routes.go +++ b/routes.go @@ -48,6 +48,9 @@ func HitView(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get data. Try again later."}) return } + go func() { + Client.Expire(context.Background(), dbKey, utils.BaseTTLPeriod) + }() c.JSON(http.StatusOK, gin.H{"value": val}) } @@ -78,7 +81,7 @@ func CreateView(c *gin.Context) { return } // Get data from Redis - created := Client.SetNX(context.Background(), dbKey, initialValue, 0) + created := Client.SetNX(context.Background(), dbKey, initialValue, utils.BaseTTLPeriod) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to set data. Try again later."}) return @@ -87,8 +90,8 @@ func CreateView(c *gin.Context) { c.JSON(http.StatusConflict, gin.H{"error": "Key already exists, please use a different key."}) return } - AdminKey := uuid.New().String() // Create a new admin key used for deletion and control - Client.Set(context.Background(), utils.CreateAdminKey(dbKey), AdminKey, 0) + AdminKey := uuid.New().String() // Create a new admin key used for deletion and control + Client.Set(context.Background(), utils.CreateAdminKey(dbKey), AdminKey, 0) // todo: figure out how to handle admin keys (handle alongside admin orrrrrrr separately as in a routine once a month that deletes all admin keys with no corresponding key) c.JSON(http.StatusCreated, gin.H{"key": key, "namespace": namespace, "admin_key": AdminKey, "value": initialValue}) } diff --git a/tests/avg.py b/tests/avg.py new file mode 100644 index 0000000..6456f67 --- /dev/null +++ b/tests/avg.py @@ -0,0 +1,13 @@ +import requests + +COUNT = 100 +TIMEOUT = 0.5 #(500ms) +url = 'http://localhost:8080/hit/jasoncameron.dev/' +times = [] + +for _ in range(COUNT): + + response = requests.get(url, timeout=TIMEOUT) + times.append(response.elapsed.total_seconds() * 1000) + +print(f"AVG: {sum(times)/COUNT}ms, MIN: {min(times)}ms, MAX: {max(times)}ms, COUNT: {len(times)}") diff --git a/utils/constants.go b/utils/constants.go new file mode 100644 index 0000000..f6ebc74 --- /dev/null +++ b/utils/constants.go @@ -0,0 +1,8 @@ +package utils + +import "time" + +const BaseTTLPeriod = time.Hour * 24 * 7 * 4 * 6 // 6 months + +const MinLength = 3 +const MaxLength = 64 diff --git a/utils/utils.go b/utils/utils.go index 8f038bd..65c91a4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -15,39 +15,46 @@ import ( "github.com/gin-gonic/gin" ) -func truncateString(s string, length int) string { - if len(s) <= length { - return s +// truncateString truncates the string to a maximum of 64 characters. If the string is less than 3 characters, it will be padded with dots. +func truncateString(s string) string { + + strLen := len(s) + if strLen < MinLength { + return strings.Repeat(".", MinLength-strLen) + s } - return s[:length] -} -func getHostPath(c *gin.Context) (string, string) { - path := truncateString(strings.ReplaceAll(c.Request.URL.Path, "/", ""), 64) - // Extract domain and path // todo fix path logic - return "", path + if strLen > MaxLength { + return s[:MaxLength] + } + return s + } -func convertReserved(c *gin.Context, input string) (string, bool) { +func convertReserved(c *gin.Context, input string) string { input = strings.Trim(input, "/") if input == ":HOST:" { origin := c.Request.Header.Get("Origin") if origin == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Origin header is required if :HOST: is used"}) - return "", false + return "" } - return origin, true + return truncateString(origin) } else if input == ":PATH:" { - _, path := getHostPath(c) - return path, true + path := c.Request.Header.Get("Referer") + if path == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Referer header is required if :PATH: is used"}) + return "" + } + + return truncateString(path) + } - host, path := getHostPath(c) - fmt.Println(host, path) - return input, true + + return input } func CreateKey(c *gin.Context, namespace, key string, skipValidation bool) string { - namespace, continueOn := convertReserved(c, namespace) - key, continueOn2 := convertReserved(c, key) - if !(continueOn && continueOn2) { + namespace = convertReserved(c, namespace) + key = convertReserved(c, key) + if key == "" || namespace == "" { return "" } if skipValidation == false {