http caching - designs

pull/94/head
Hunter Long 2018-10-21 12:36:11 -07:00
parent 3e4234f2e3
commit e9354e8e6c
7 changed files with 102 additions and 8 deletions

87
handlers/cache.go Normal file
View File

@ -0,0 +1,87 @@
package handlers
import (
"net/http"
"net/http/httptest"
"sync"
"time"
)
var storage Cacher
type Cacher interface {
Get(key string) []byte
Set(key string, content []byte, duration time.Duration)
}
// Item is a cached reference
type Item struct {
Content []byte
Expiration int64
}
// Expired returns true if the item has expired.
func (item Item) Expired() bool {
if item.Expiration == 0 {
return false
}
return time.Now().UnixNano() > item.Expiration
}
//Storage mecanism for caching strings in memory
type Storage struct {
items map[string]Item
mu *sync.RWMutex
}
//NewStorage creates a new in memory storage
func NewStorage() *Storage {
return &Storage{
items: make(map[string]Item),
mu: &sync.RWMutex{},
}
}
//Get a cached content by key
func (s Storage) Get(key string) []byte {
s.mu.RLock()
defer s.mu.RUnlock()
item := s.items[key]
if item.Expired() {
delete(s.items, key)
return nil
}
return item.Content
}
//Set a cached content by key
func (s Storage) Set(key string, content []byte, duration time.Duration) {
s.mu.Lock()
defer s.mu.Unlock()
s.items[key] = Item{
Content: content,
Expiration: time.Now().Add(duration).UnixNano(),
}
}
func cached(duration string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
content := storage.Get(r.RequestURI)
if content != nil {
w.Write(content)
} else {
c := httptest.NewRecorder()
handler(c, r)
for k, v := range c.HeaderMap {
w.Header()[k] = v
}
w.WriteHeader(c.Code)
content := c.Body.Bytes()
if d, err := time.ParseDuration(duration); err == nil {
storage.Set(r.RequestURI, content, d)
}
w.Write(content)
}
})
}

View File

@ -33,6 +33,7 @@ var (
// Router returns all of the routes used in Statup
func Router() *mux.Router {
dir := utils.Directory
storage = NewStorage()
r := mux.NewRouter()
r.Handle("/", http.HandlerFunc(indexHandler))
if source.UsingAssets(dir) {
@ -96,7 +97,7 @@ func Router() *mux.Router {
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
r.Handle("/api/services", http.HandlerFunc(apiCreateServiceHandler)).Methods("POST")
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceHandler)).Methods("GET")
r.Handle("/api/services/{id}/data", http.HandlerFunc(apiServiceDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/data", cached("10s", http.HandlerFunc(apiServiceDataHandler))).Methods("GET")
r.Handle("/api/services/{id}/ping", http.HandlerFunc(apiServicePingDataHandler)).Methods("GET")
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceUpdateHandler)).Methods("POST")
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceDeleteHandler)).Methods("DELETE")

View File

@ -198,8 +198,8 @@ HTML, BODY {
.nav-pills A {
color: #424242; }
.nav-pills > i {
margin-right: 5px; }
.nav-pills I {
margin-right: 10px; }
.CodeMirror {
/* Bootstrap Settings */

View File

@ -30,7 +30,10 @@ $('.test_notifier').on('click', function(e) {
var notifier = form.find('input[name=notifier]').val();
var success = $('#'+notifier+'-success');
var error = $('#'+notifier+'-error');
var spinner = '<i class="fa fa-spinner fa-spin"></i>';
var btnHtml = btn.html();
btn.prop("disabled", true);
btn.html(spinner);
$.ajax({
url: form.attr("action")+"/test",
type: 'POST',
@ -49,13 +52,16 @@ $('.test_notifier').on('click', function(e) {
}, 8000)
}
btn.prop("disabled", false);
btn.html(btnHtml);
}
});
e.preventDefault();
});
$('form').submit(function() {
var spinner = '<i class="fa fa-spinner fa-spin"></i>';
$(this).find('button[type=submit]').prop('disabled', true);
$(this).find('button[type=submit]').html(spinner);
});
$('select#service_type').on('change', function() {

View File

@ -232,8 +232,8 @@ HTML,BODY {
color: #424242;
}
.nav-pills > i {
margin-right: 5px;
.nav-pills I {
margin-right: 10px;
}
.CodeMirror {

View File

@ -41,12 +41,12 @@
<input type="hidden" name="notifier" value="{{underscore $n.Method }}">
<div class="col-12 col-sm-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
<button type="submit" class="btn btn-primary btn-block text-capitalize"><i class="fa fa-check"></i> Save</button>
<button type="submit" class="btn btn-primary btn-block text-capitalize"><i class="fa fa-check-circle"></i> Save</button>
</div>
{{if $n.CanTest}}
<div class="col-12 col-sm-12">
<button class="test_notifier btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-times"></i> Test</button>
<button class="test_notifier btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-vial"></i> Test Notifier</button>
</div>
<div class="col-12 col-sm-12 mt-2">

View File

@ -155,7 +155,7 @@
{{ range .Notifications }}
{{$n := .Select}}
<div class="tab-pane" id="v-pills-{{underscore $n.Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore $n.Method }}-tab">
<div class="tab-pane fade" id="v-pills-{{underscore $n.Method}}" role="tabpanel" aria-labelledby="v-pills-{{underscore $n.Method }}-tab">
{{template "form_notifier" .}}