123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- package cache
- import (
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- "go-common/library/cache/memcache"
- "go-common/library/container/pool"
- "go-common/library/ecode"
- "go-common/library/log"
- bm "go-common/library/net/http/blademaster"
- "go-common/library/net/http/blademaster/middleware/cache/store"
- xtime "go-common/library/time"
- "github.com/stretchr/testify/assert"
- )
- const (
- SockAddr = "127.0.0.1:18080"
- McSockAddr = "172.16.33.54:11211"
- )
- func uri(base, path string) string {
- return fmt.Sprintf("%s://%s%s", "http", base, path)
- }
- func init() {
- log.Init(nil)
- }
- func newMemcache() (*Cache, func()) {
- s := store.NewMemcache(&memcache.Config{
- Config: &pool.Config{
- Active: 10,
- Idle: 2,
- IdleTimeout: xtime.Duration(time.Second),
- },
- Name: "test",
- Proto: "tcp",
- Addr: McSockAddr,
- DialTimeout: xtime.Duration(time.Second),
- ReadTimeout: xtime.Duration(time.Second),
- WriteTimeout: xtime.Duration(time.Second),
- })
- return New(s), func() {}
- }
- func newFile() (*Cache, func()) {
- path, err := ioutil.TempDir("", "cache-test")
- if err != nil {
- panic("Failed to create cache directory")
- }
- s := store.NewFile(&store.FileConfig{
- RootDir: path,
- })
- remove := func() {
- os.RemoveAll(path)
- }
- return New(s), remove
- }
- func TestPage(t *testing.T) {
- memcache, remove1 := newMemcache()
- filestore, remove2 := newFile()
- defer func() {
- remove1()
- remove2()
- }()
- t.Run("Memcache Store", pageCase(memcache, true))
- t.Run("File Store", pageCase(filestore, false))
- }
- func TestControl(t *testing.T) {
- memcache, remove1 := newMemcache()
- filestore, remove2 := newFile()
- defer func() {
- remove1()
- remove2()
- }()
- t.Run("Memcache Store", controlCase(memcache, true))
- t.Run("File Store", controlCase(filestore, false))
- }
- func TestPageCacheMultiWrite(t *testing.T) {
- memcache, remove1 := newMemcache()
- filestore, remove2 := newFile()
- defer func() {
- remove1()
- remove2()
- }()
- t.Run("Memcache Store", pageMultiWriteCase(memcache))
- t.Run("File Store", pageMultiWriteCase(filestore))
- }
- func TestDegrade(t *testing.T) {
- memcache, remove1 := newMemcache()
- filestore, remove2 := newFile()
- defer func() {
- remove1()
- remove2()
- }()
- t.Run("Memcache Store", degradeCase(memcache))
- t.Run("File Store", degradeCase(filestore))
- }
- func pageCase(cache *Cache, testExpire bool) func(t *testing.T) {
- return func(t *testing.T) {
- expire := int32(3)
- pc := NewPage(expire)
- engine := bm.Default()
- engine.GET("/page-cache", cache.Cache(pc, nil), func(ctx *bm.Context) {
- ctx.Writer.Header().Set("X-Hello", "World")
- ctx.String(203, "%s\n", time.Now().String())
- })
- go engine.Run(SockAddr)
- defer func() {
- engine.Server().Shutdown(context.Background())
- }()
- time.Sleep(time.Second)
- code1, content1, headers1, err1 := httpGet(uri(SockAddr, "/page-cache"))
- code2, content2, headers2, err2 := httpGet(uri(SockAddr, "/page-cache"))
- assert.Nil(t, err1)
- assert.Nil(t, err2)
- assert.Equal(t, code1, 203)
- assert.Equal(t, code2, 203)
- assert.NotNil(t, content1)
- assert.NotNil(t, content2)
- assert.Equal(t, headers1["X-Hello"], []string{"World"})
- assert.Equal(t, headers2["X-Hello"], []string{"World"})
- assert.Equal(t, string(content1), string(content2))
- if !testExpire {
- return
- }
- // test if the last caching is expired
- t.Logf("Waiting %d seconds for caching expire test", expire+1)
- time.Sleep(time.Second * time.Duration(expire+1))
- _, content3, _, err3 := httpGet(uri(SockAddr, "/page-cache"))
- _, content4, _, err4 := httpGet(uri(SockAddr, "/page-cache"))
- assert.Nil(t, err3)
- assert.Nil(t, err4)
- assert.NotNil(t, content1)
- assert.NotNil(t, content2)
- assert.NotEqual(t, string(content1), string(content3))
- assert.Equal(t, string(content3), string(content4))
- }
- }
- func pageMultiWriteCase(cache *Cache) func(t *testing.T) {
- return func(t *testing.T) {
- chunks := []string{
- "Hello",
- "World",
- "Hello",
- "World",
- "Hello",
- "World",
- "Hello",
- "World",
- }
- pc := NewPage(3)
- engine := bm.Default()
- engine.GET("/page-cache-write", cache.Cache(pc, nil), func(ctx *bm.Context) {
- ctx.Writer.Header().Set("X-Hello", "World")
- ctx.Writer.WriteHeader(203)
- for _, chunk := range chunks {
- ctx.Writer.Write([]byte(chunk))
- }
- })
- go engine.Run(SockAddr)
- defer func() {
- engine.Server().Shutdown(context.Background())
- }()
- time.Sleep(time.Second)
- code1, content1, headers1, err1 := httpGet(uri(SockAddr, "/page-cache-write"))
- code2, content2, headers2, err2 := httpGet(uri(SockAddr, "/page-cache-write"))
- assert.Nil(t, err1)
- assert.Nil(t, err2)
- assert.Equal(t, code1, 203)
- assert.Equal(t, code2, 203)
- assert.NotNil(t, content1)
- assert.NotNil(t, content2)
- assert.Equal(t, headers1["X-Hello"], []string{"World"})
- assert.Equal(t, headers2["X-Hello"], []string{"World"})
- assert.Equal(t, strings.Join(chunks, ""), string(content1))
- assert.Equal(t, strings.Join(chunks, ""), string(content2))
- assert.Equal(t, string(content1), string(content2))
- }
- }
- func degradeCase(cache *Cache) func(t *testing.T) {
- return func(t *testing.T) {
- wg := sync.WaitGroup{}
- i := int32(0)
- degrade := NewDegrader(10)
- engine := bm.Default()
- engine.GET("/scheduled/error", cache.Cache(degrade.Args("name", "age"), nil), func(c *bm.Context) {
- code := atomic.AddInt32(&i, 1)
- if code == 5 {
- c.JSON("succeed", nil)
- return
- }
- if code%2 == 0 {
- c.JSON("", ecode.Degrade)
- return
- }
- c.JSON(fmt.Sprintf("Code: %d", code), ecode.Int(int(code)))
- })
- wg.Add(1)
- go func() {
- engine.Run(":18080")
- wg.Done()
- }()
- defer func() {
- engine.Server().Shutdown(context.TODO())
- wg.Wait()
- }()
- time.Sleep(time.Second)
- for index := 1; index < 10; index++ {
- _, content, _, _ := httpGet(uri(SockAddr, "/scheduled/error?name=degrader&age=26"))
- t.Log(index, string(content))
- var res struct {
- Data string `json:"data"`
- }
- err := json.Unmarshal(content, &res)
- assert.Nil(t, err)
- if index == 5 {
- // ensure response is write to cache
- time.Sleep(time.Second)
- }
- if index > 5 && index%2 == 0 {
- if res.Data != "succeed" {
- t.Fatalf("Failed to degrade at index: %d", index)
- } else {
- t.Logf("This request is degraded at index: %d", index)
- }
- }
- }
- }
- }
- func controlCase(cache *Cache, testExpire bool) func(t *testing.T) {
- return func(t *testing.T) {
- wg := sync.WaitGroup{}
- i := int32(0)
- expire := int32(30)
- control := NewControl(expire)
- filter := func(ctx *bm.Context) bool {
- if ctx.Request.Form.Get("cache") == "false" {
- return false
- }
- return true
- }
- engine := bm.Default()
- engine.GET("/large/response", cache.Cache(control, filter), func(c *bm.Context) {
- c.JSON(map[string]interface{}{
- "index": atomic.AddInt32(&i, 1),
- "Hello0": "World",
- "Hello1": "World",
- "Hello2": "World",
- "Hello3": "World",
- "Hello4": "World",
- "Hello5": "World",
- "Hello6": "World",
- "Hello7": "World",
- "Hello8": "World",
- }, nil)
- })
- engine.GET("/large/response/error", cache.Cache(control, filter), func(c *bm.Context) {
- c.JSON(nil, ecode.RequestErr)
- })
- wg.Add(1)
- go func() {
- engine.Run(":18080")
- wg.Done()
- }()
- defer func() {
- engine.Server().Shutdown(context.TODO())
- wg.Wait()
- }()
- time.Sleep(time.Second)
- code, content, headers, err := httpGet(uri(SockAddr, "/large/response?name=hello&age=1"))
- assert.NoError(t, err)
- assert.Equal(t, 200, code)
- assert.NotEmpty(t, content)
- assert.Equal(t, "max-age=30", headers.Get("Cache-Control"))
- exp, err := http.ParseTime(headers.Get("Expires"))
- assert.NoError(t, err)
- assert.InDelta(t, 30, exp.Unix()-time.Now().Unix(), 5)
- code, content, headers, err = httpGet(uri(SockAddr, "/large/response/error?name=hello&age=1&cache=false"))
- assert.NoError(t, err)
- assert.Equal(t, 200, code)
- assert.NotEmpty(t, content)
- assert.Empty(t, headers.Get("Expires"))
- assert.Empty(t, headers.Get("Cache-Control"))
- code, content, headers, err = httpGet(uri(SockAddr, "/large/response/error?name=hello&age=1"))
- assert.NoError(t, err)
- assert.Equal(t, 200, code)
- assert.NotEmpty(t, content)
- assert.Empty(t, headers.Get("Expires"))
- assert.Empty(t, headers.Get("Cache-Control"))
- }
- }
- func httpGet(url string) (code int, content []byte, headers http.Header, err error) {
- resp, err := http.Get(url)
- if err != nil {
- return
- }
- defer resp.Body.Close()
- if content, err = ioutil.ReadAll(resp.Body); err != nil {
- return
- }
- code = resp.StatusCode
- headers = resp.Header
- return
- }
|