123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- package cache
- import (
- "bytes"
- "crypto/sha1"
- "io"
- "net/http"
- "net/url"
- "sync"
- "go-common/library/log"
- bm "go-common/library/net/http/blademaster"
- "go-common/library/net/http/blademaster/middleware/cache/store"
- proto "github.com/gogo/protobuf/proto"
- )
- // consts for blademaster cache
- const (
- _pagePrefix = "bm.page"
- )
- // Page is used to cache common response
- type Page struct {
- Expire int32
- pool sync.Pool
- }
- type cachedWriter struct {
- ctx *bm.Context
- response http.ResponseWriter
- store store.Store
- status int
- expire int32
- key string
- }
- var _ http.ResponseWriter = &cachedWriter{}
- // NewPage will create a new page cache struct
- func NewPage(expire int32) *Page {
- pc := &Page{
- Expire: expire,
- }
- pc.pool.New = func() interface{} {
- return &cachedWriter{}
- }
- return pc
- }
- // Key is used to identify response cache key in most key-value store
- func (p *Page) Key(ctx *bm.Context) string {
- url := ctx.Request.URL
- key := urlEscape(_pagePrefix, url.RequestURI())
- return key
- }
- // Handler is used to execute cache service
- func (p *Page) Handler(store store.Store) bm.HandlerFunc {
- return func(ctx *bm.Context) {
- var (
- resp *ResponseCache
- cached []byte
- err error
- )
- key := p.Key(ctx)
- cached, err = store.Get(ctx, key)
- // if we did got the previous cache,
- // try to unmarshal it
- if err == nil && len(cached) > 0 {
- resp = new(ResponseCache)
- err = proto.Unmarshal(cached, resp)
- }
- // if we failed to fetch the cache or failed to parse cached data,
- // then consider try to cache this response
- if err != nil || resp == nil {
- writer := p.pool.Get().(*cachedWriter)
- writer.ctx = ctx
- writer.response = ctx.Writer
- writer.key = key
- writer.expire = p.Expire
- writer.store = store
- ctx.Writer = writer
- ctx.Next()
- p.pool.Put(writer)
- return
- }
- // write cached response
- headers := ctx.Writer.Header()
- for key, value := range resp.Header {
- headers[key] = value.Value
- }
- ctx.Writer.WriteHeader(int(resp.Status))
- ctx.Writer.Write(resp.Data)
- ctx.Abort()
- }
- }
- func (w *cachedWriter) Header() http.Header {
- return w.response.Header()
- }
- func (w *cachedWriter) WriteHeader(code int) {
- w.status = int(code)
- w.response.WriteHeader(code)
- }
- func (w *cachedWriter) Write(data []byte) (size int, err error) {
- var (
- origin []byte
- pdata []byte
- )
- if size, err = w.response.Write(data); err != nil {
- return
- }
- store := w.store
- origin, err = store.Get(w.ctx, w.key)
- resp := new(ResponseCache)
- if err == nil || len(origin) > 0 {
- err1 := proto.Unmarshal(origin, resp)
- if err1 == nil {
- data = append(resp.Data, data...)
- }
- }
- resp.Status = int32(w.status)
- resp.Header = headerValues(w.Header())
- resp.Data = data
- if pdata, err = proto.Marshal(resp); err != nil {
- // cannot happen
- log.Error("Failed to marshal response to protobuf: %v", err)
- return
- }
- if err = store.Set(w.ctx, w.key, pdata, w.expire); err != nil {
- log.Error("Failed to set response cache: %v", err)
- return
- }
- return
- }
- func headerValues(headers http.Header) map[string]*HeaderValue {
- result := make(map[string]*HeaderValue, len(headers))
- for key, values := range headers {
- result[key] = &HeaderValue{
- Value: values,
- }
- }
- return result
- }
- func urlEscape(prefix string, u string) string {
- key := url.QueryEscape(u)
- if len(key) > 200 {
- h := sha1.New()
- io.WriteString(h, u)
- key = string(h.Sum(nil))
- }
- var buffer bytes.Buffer
- buffer.WriteString(prefix)
- buffer.WriteString(":")
- buffer.WriteString(key)
- return buffer.String()
- }
|