1
0
Fork 0
mirror of https://github.com/Luzifer/badge-gen.git synced 2024-11-08 13:20:02 +00:00

Add caching results of API calls

This commit is contained in:
Knut Ahlers 2016-06-29 15:13:26 +02:00
parent d2c7c6693e
commit 54c47998d7
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
5 changed files with 140 additions and 29 deletions

10
app.go
View file

@ -5,6 +5,7 @@ import (
"crypto/sha1" "crypto/sha1"
"errors" "errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"net/url" "net/url"
"sort" "sort"
@ -14,6 +15,7 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/Luzifer/badge-gen/cache"
"github.com/Luzifer/rconfig" "github.com/Luzifer/rconfig"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/tdewolff/minify" "github.com/tdewolff/minify"
@ -29,9 +31,11 @@ var (
cfg = struct { cfg = struct {
Port int64 `env:"PORT"` Port int64 `env:"PORT"`
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"` Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
Cache string `flag:"cache" default:"mem://" description:"Where to cache query results from thirdparty APIs"`
}{} }{}
serviceHandlers = map[string]serviceHandler{} serviceHandlers = map[string]serviceHandler{}
version = "dev" version = "dev"
cacheStore cache.Cache
) )
type serviceHandlerDocumentation struct { type serviceHandlerDocumentation struct {
@ -72,6 +76,12 @@ func main() {
cfg.Listen = fmt.Sprintf(":%d", cfg.Port) cfg.Listen = fmt.Sprintf(":%d", cfg.Port)
} }
var err error
cacheStore, err = cache.GetCacheByURI(cfg.Cache)
if err != nil {
log.Fatalf("Unable to open cache: %s", err)
}
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/v1/badge", generateBadge).Methods("GET") r.HandleFunc("/v1/badge", generateBadge).Methods("GET")
r.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET") r.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET")

33
cache/cache.go vendored Normal file
View file

@ -0,0 +1,33 @@
package cache
import (
"errors"
"net/url"
"time"
)
type KeyNotFoundError struct{}
func (k KeyNotFoundError) Error() string {
return "Requested key was not found in database"
}
type Cache interface {
Get(namespace, key string) (value string, err error)
Set(namespace, key, value string, ttl time.Duration) (err error)
Delete(namespace, key string) (err error)
}
func GetCacheByURI(uri string) (Cache, error) {
url, err := url.Parse(uri)
if err != nil {
return nil, err
}
switch url.Scheme {
case "mem":
return NewInMemCache(), nil
default:
return nil, errors.New("Invalid cache scheme: " + url.Scheme)
}
}

53
cache/inMemCache.go vendored Normal file
View file

@ -0,0 +1,53 @@
package cache
import (
"sync"
"time"
)
type inMemCacheEntry struct {
Value string
Expires time.Time
}
type InMemCache struct {
cache map[string]inMemCacheEntry
lock sync.RWMutex
}
func NewInMemCache() *InMemCache {
return &InMemCache{
cache: map[string]inMemCacheEntry{},
}
}
func (i InMemCache) Get(namespace, key string) (value string, err error) {
i.lock.RLock()
defer i.lock.RUnlock()
e, ok := i.cache[namespace+"::"+key]
if !ok || e.Expires.Before(time.Now()) {
return "", KeyNotFoundError{}
}
return e.Value, nil
}
func (i InMemCache) Set(namespace, key, value string, ttl time.Duration) (err error) {
i.lock.Lock()
defer i.lock.Unlock()
i.cache[namespace+"::"+key] = inMemCacheEntry{
Value: value,
Expires: time.Now().Add(ttl),
}
return nil
}
func (i InMemCache) Delete(namespace, key string) (err error) {
i.lock.Lock()
defer i.lock.Unlock()
delete(i.cache, namespace+"::"+key)
return nil
}

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"strings" "strings"
"time"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp" "golang.org/x/net/context/ctxhttp"
@ -45,27 +46,33 @@ func (g githubServiceHandler) Handle(ctx context.Context, params []string) (titl
func (g githubServiceHandler) handleLicense(ctx context.Context, params []string) (title, text, color string, err error) { func (g githubServiceHandler) handleLicense(ctx context.Context, params []string) (title, text, color string, err error) {
path := strings.Join([]string{"repos", params[0], params[1], "license"}, "/") path := strings.Join([]string{"repos", params[0], params[1], "license"}, "/")
req, _ := http.NewRequest("GET", "https://api.github.com/"+path, nil) text, err = cacheStore.Get("github_license", path)
req.Header.Set("Accept", "application/vnd.github.drax-preview+json")
var resp *http.Response
resp, err = ctxhttp.Do(ctx, http.DefaultClient, req)
if err != nil { if err != nil {
return req, _ := http.NewRequest("GET", "https://api.github.com/"+path, nil)
} req.Header.Set("Accept", "application/vnd.github.drax-preview+json")
defer resp.Body.Close()
r := struct { var resp *http.Response
License struct { resp, err = ctxhttp.Do(ctx, http.DefaultClient, req)
Name string `json:"name"` if err != nil {
} `json:"license"` return
}{} }
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil { defer resp.Body.Close()
return
r := struct {
License struct {
Name string `json:"name"`
} `json:"license"`
}{}
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
return
}
text = r.License.Name
cacheStore.Set("github_license", path, text, 10*time.Minute)
} }
title = "license" title = "license"
text = r.License.Name
color = "007ec6" color = "007ec6"
if text == "" { if text == "" {

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"strings" "strings"
"time"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp" "golang.org/x/net/context/ctxhttp"
@ -36,26 +37,33 @@ func (t travisServiceHandler) Handle(ctx context.Context, params []string) (titl
path := strings.Join([]string{"repos", params[0], params[1], "branches", params[2]}, "/") path := strings.Join([]string{"repos", params[0], params[1], "branches", params[2]}, "/")
var resp *http.Response var state string
resp, err = ctxhttp.Get(ctx, http.DefaultClient, "https://api.travis-ci.org/"+path) state, err = cacheStore.Get("travis", path)
if err != nil { if err != nil {
return var resp *http.Response
} resp, err = ctxhttp.Get(ctx, http.DefaultClient, "https://api.travis-ci.org/"+path)
defer resp.Body.Close() if err != nil {
return
}
defer resp.Body.Close()
r := struct { r := struct {
File string `json:"file"` File string `json:"file"`
Branch struct { Branch struct {
State string `json:"state"` State string `json:"state"`
} `json:"branch"` } `json:"branch"`
}{} }{}
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil { if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
return return
}
state = r.Branch.State
cacheStore.Set("travis", path, state, 5*time.Minute)
} }
title = "travis" title = "travis"
text = r.Branch.State text = state
if text == "" { if text == "" {
text = "unknown" text = "unknown"
} }