Browse Source

Add per-item ttl in store

master
Tyler Sommer 3 years ago
parent
commit
63b1a00e12
Signed by: tyler-sommer
GPG Key ID: C09C010500DBD008
  1. 49
      cmd/eokvin/http.go
  2. 23
      cmd/eokvin/store.go

49
cmd/eokvin/http.go

@ -8,6 +8,7 @@ import (
"log"
"net/http"
"strings"
"time"
"golang.org/x/crypto/acme/autocert"
)
@ -135,26 +136,56 @@ func redirectToCanonicalHost(w http.ResponseWriter, r *http.Request) {
// newHandler is an http.Handler that creates a new item in the urlStore store.
var newHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
var serverError = func(err error) {
w.WriteHeader(http.StatusInternalServerError)
log.Println("error creating short url:", err.Error())
if _, err = w.Write([]byte(`{"error":"internal error"}`)); err != nil {
log.Println("error writing response:", err.Error())
}
}
var badRequest = func(message string) {
b, err := json.Marshal(map[string]string{"error": message})
if err != nil {
serverError(err)
return
}
w.WriteHeader(http.StatusBadRequest)
if _, err = w.Write(b); err != nil {
log.Println("error writing response:", err.Error())
}
}
link := r.PostFormValue("url")
if len(link) == 0 {
w.WriteHeader(http.StatusBadRequest)
badRequest("url cannot be blank")
return
}
ttl := urlStore.ttl
s := r.PostFormValue("ttl")
if len(s) > 0 {
var err error
if ttl, err = time.ParseDuration(s); err != nil {
badRequest("invalid value given for ttl")
return
}
}
k, err := urlStore.newItemID()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
serverError(err)
return
}
urlStore.mu.Lock()
urlStore.entries[k] = newItem(link)
urlStore.entries[k] = newItem(link, ttl)
urlStore.mu.Unlock()
b, err := json.Marshal(map[string]string{"short-url": canonicalHost + "/" + k.String()})
b, err := json.Marshal(
map[string]string{
"short-url": canonicalHost + "/" + k.String(),
"expires": time.Now().Add(ttl).Format("2006-01-02T15:04:05-0700"),
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, err = fmt.Fprintf(w, `{"error": "%s"}\n`, strings.Replace(err.Error(), `"`, `'`, -1))
if err != nil {
log.Println("error writing response:", err.Error())
}
serverError(err)
return
}
w.WriteHeader(http.StatusCreated)

23
cmd/eokvin/store.go

@ -22,11 +22,17 @@ func (c itemID) String() string {
type item struct {
value string
insertedAt time.Time
ttl time.Duration
}
func (c item) String() string {
return c.value
}
// newItem initializes a new url store item with a TTL.
func newItem(s string, ttl time.Duration) item {
return item{value: s, insertedAt: time.Now(), ttl: ttl}
}
// A store is an in-memory data store with expiring items.
type store struct {
mu sync.RWMutex
@ -57,7 +63,12 @@ func (cch *store) newItemID() (itemID, error) {
// isExpired returns true if the given item is expired.
func (cch *store) isExpired(c item) bool {
return c.insertedAt.Before(time.Now().Add(-1 * cch.ttl))
ttl := c.ttl
if ttl == 0 {
// fallback to the stores configured ttl
ttl = cch.ttl
}
return c.insertedAt.Before(time.Now().Add(-1 * ttl))
}
// expiredItemReaper deletes expired entries from the store at regular
@ -66,6 +77,8 @@ func (cch *store) expiredItemReaper() error {
for {
select {
case <-time.After(30 * time.Second):
// make a list of items that need to be deleted, but avoid
// fully locking the entries map.
var del []itemID
cch.mu.RLock()
for k, v := range cch.entries {
@ -77,6 +90,8 @@ func (cch *store) expiredItemReaper() error {
if len(del) == 0 {
continue
}
// with at least one entry to delete, obtain a full write lock
// and make the modifications in a loop.
cch.mu.Lock()
for _, k := range del {
delete(cch.entries, k)
@ -85,9 +100,3 @@ func (cch *store) expiredItemReaper() error {
}
}
}
// newItem initializes a new item.
func newItem(s string) item {
return item{value: s, insertedAt: time.Now()}
}

Loading…
Cancel
Save