cache_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package cache
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "sync"
  11. "sync/atomic"
  12. "testing"
  13. "time"
  14. "go-common/library/cache/memcache"
  15. "go-common/library/container/pool"
  16. "go-common/library/ecode"
  17. "go-common/library/log"
  18. bm "go-common/library/net/http/blademaster"
  19. "go-common/library/net/http/blademaster/middleware/cache/store"
  20. xtime "go-common/library/time"
  21. "github.com/stretchr/testify/assert"
  22. )
  23. const (
  24. SockAddr = "127.0.0.1:18080"
  25. McSockAddr = "172.16.33.54:11211"
  26. )
  27. func uri(base, path string) string {
  28. return fmt.Sprintf("%s://%s%s", "http", base, path)
  29. }
  30. func init() {
  31. log.Init(nil)
  32. }
  33. func newMemcache() (*Cache, func()) {
  34. s := store.NewMemcache(&memcache.Config{
  35. Config: &pool.Config{
  36. Active: 10,
  37. Idle: 2,
  38. IdleTimeout: xtime.Duration(time.Second),
  39. },
  40. Name: "test",
  41. Proto: "tcp",
  42. Addr: McSockAddr,
  43. DialTimeout: xtime.Duration(time.Second),
  44. ReadTimeout: xtime.Duration(time.Second),
  45. WriteTimeout: xtime.Duration(time.Second),
  46. })
  47. return New(s), func() {}
  48. }
  49. func newFile() (*Cache, func()) {
  50. path, err := ioutil.TempDir("", "cache-test")
  51. if err != nil {
  52. panic("Failed to create cache directory")
  53. }
  54. s := store.NewFile(&store.FileConfig{
  55. RootDir: path,
  56. })
  57. remove := func() {
  58. os.RemoveAll(path)
  59. }
  60. return New(s), remove
  61. }
  62. func TestPage(t *testing.T) {
  63. memcache, remove1 := newMemcache()
  64. filestore, remove2 := newFile()
  65. defer func() {
  66. remove1()
  67. remove2()
  68. }()
  69. t.Run("Memcache Store", pageCase(memcache, true))
  70. t.Run("File Store", pageCase(filestore, false))
  71. }
  72. func TestControl(t *testing.T) {
  73. memcache, remove1 := newMemcache()
  74. filestore, remove2 := newFile()
  75. defer func() {
  76. remove1()
  77. remove2()
  78. }()
  79. t.Run("Memcache Store", controlCase(memcache, true))
  80. t.Run("File Store", controlCase(filestore, false))
  81. }
  82. func TestPageCacheMultiWrite(t *testing.T) {
  83. memcache, remove1 := newMemcache()
  84. filestore, remove2 := newFile()
  85. defer func() {
  86. remove1()
  87. remove2()
  88. }()
  89. t.Run("Memcache Store", pageMultiWriteCase(memcache))
  90. t.Run("File Store", pageMultiWriteCase(filestore))
  91. }
  92. func TestDegrade(t *testing.T) {
  93. memcache, remove1 := newMemcache()
  94. filestore, remove2 := newFile()
  95. defer func() {
  96. remove1()
  97. remove2()
  98. }()
  99. t.Run("Memcache Store", degradeCase(memcache))
  100. t.Run("File Store", degradeCase(filestore))
  101. }
  102. func pageCase(cache *Cache, testExpire bool) func(t *testing.T) {
  103. return func(t *testing.T) {
  104. expire := int32(3)
  105. pc := NewPage(expire)
  106. engine := bm.Default()
  107. engine.GET("/page-cache", cache.Cache(pc, nil), func(ctx *bm.Context) {
  108. ctx.Writer.Header().Set("X-Hello", "World")
  109. ctx.String(203, "%s\n", time.Now().String())
  110. })
  111. go engine.Run(SockAddr)
  112. defer func() {
  113. engine.Server().Shutdown(context.Background())
  114. }()
  115. time.Sleep(time.Second)
  116. code1, content1, headers1, err1 := httpGet(uri(SockAddr, "/page-cache"))
  117. code2, content2, headers2, err2 := httpGet(uri(SockAddr, "/page-cache"))
  118. assert.Nil(t, err1)
  119. assert.Nil(t, err2)
  120. assert.Equal(t, code1, 203)
  121. assert.Equal(t, code2, 203)
  122. assert.NotNil(t, content1)
  123. assert.NotNil(t, content2)
  124. assert.Equal(t, headers1["X-Hello"], []string{"World"})
  125. assert.Equal(t, headers2["X-Hello"], []string{"World"})
  126. assert.Equal(t, string(content1), string(content2))
  127. if !testExpire {
  128. return
  129. }
  130. // test if the last caching is expired
  131. t.Logf("Waiting %d seconds for caching expire test", expire+1)
  132. time.Sleep(time.Second * time.Duration(expire+1))
  133. _, content3, _, err3 := httpGet(uri(SockAddr, "/page-cache"))
  134. _, content4, _, err4 := httpGet(uri(SockAddr, "/page-cache"))
  135. assert.Nil(t, err3)
  136. assert.Nil(t, err4)
  137. assert.NotNil(t, content1)
  138. assert.NotNil(t, content2)
  139. assert.NotEqual(t, string(content1), string(content3))
  140. assert.Equal(t, string(content3), string(content4))
  141. }
  142. }
  143. func pageMultiWriteCase(cache *Cache) func(t *testing.T) {
  144. return func(t *testing.T) {
  145. chunks := []string{
  146. "Hello",
  147. "World",
  148. "Hello",
  149. "World",
  150. "Hello",
  151. "World",
  152. "Hello",
  153. "World",
  154. }
  155. pc := NewPage(3)
  156. engine := bm.Default()
  157. engine.GET("/page-cache-write", cache.Cache(pc, nil), func(ctx *bm.Context) {
  158. ctx.Writer.Header().Set("X-Hello", "World")
  159. ctx.Writer.WriteHeader(203)
  160. for _, chunk := range chunks {
  161. ctx.Writer.Write([]byte(chunk))
  162. }
  163. })
  164. go engine.Run(SockAddr)
  165. defer func() {
  166. engine.Server().Shutdown(context.Background())
  167. }()
  168. time.Sleep(time.Second)
  169. code1, content1, headers1, err1 := httpGet(uri(SockAddr, "/page-cache-write"))
  170. code2, content2, headers2, err2 := httpGet(uri(SockAddr, "/page-cache-write"))
  171. assert.Nil(t, err1)
  172. assert.Nil(t, err2)
  173. assert.Equal(t, code1, 203)
  174. assert.Equal(t, code2, 203)
  175. assert.NotNil(t, content1)
  176. assert.NotNil(t, content2)
  177. assert.Equal(t, headers1["X-Hello"], []string{"World"})
  178. assert.Equal(t, headers2["X-Hello"], []string{"World"})
  179. assert.Equal(t, strings.Join(chunks, ""), string(content1))
  180. assert.Equal(t, strings.Join(chunks, ""), string(content2))
  181. assert.Equal(t, string(content1), string(content2))
  182. }
  183. }
  184. func degradeCase(cache *Cache) func(t *testing.T) {
  185. return func(t *testing.T) {
  186. wg := sync.WaitGroup{}
  187. i := int32(0)
  188. degrade := NewDegrader(10)
  189. engine := bm.Default()
  190. engine.GET("/scheduled/error", cache.Cache(degrade.Args("name", "age"), nil), func(c *bm.Context) {
  191. code := atomic.AddInt32(&i, 1)
  192. if code == 5 {
  193. c.JSON("succeed", nil)
  194. return
  195. }
  196. if code%2 == 0 {
  197. c.JSON("", ecode.Degrade)
  198. return
  199. }
  200. c.JSON(fmt.Sprintf("Code: %d", code), ecode.Int(int(code)))
  201. })
  202. wg.Add(1)
  203. go func() {
  204. engine.Run(":18080")
  205. wg.Done()
  206. }()
  207. defer func() {
  208. engine.Server().Shutdown(context.TODO())
  209. wg.Wait()
  210. }()
  211. time.Sleep(time.Second)
  212. for index := 1; index < 10; index++ {
  213. _, content, _, _ := httpGet(uri(SockAddr, "/scheduled/error?name=degrader&age=26"))
  214. t.Log(index, string(content))
  215. var res struct {
  216. Data string `json:"data"`
  217. }
  218. err := json.Unmarshal(content, &res)
  219. assert.Nil(t, err)
  220. if index == 5 {
  221. // ensure response is write to cache
  222. time.Sleep(time.Second)
  223. }
  224. if index > 5 && index%2 == 0 {
  225. if res.Data != "succeed" {
  226. t.Fatalf("Failed to degrade at index: %d", index)
  227. } else {
  228. t.Logf("This request is degraded at index: %d", index)
  229. }
  230. }
  231. }
  232. }
  233. }
  234. func controlCase(cache *Cache, testExpire bool) func(t *testing.T) {
  235. return func(t *testing.T) {
  236. wg := sync.WaitGroup{}
  237. i := int32(0)
  238. expire := int32(30)
  239. control := NewControl(expire)
  240. filter := func(ctx *bm.Context) bool {
  241. if ctx.Request.Form.Get("cache") == "false" {
  242. return false
  243. }
  244. return true
  245. }
  246. engine := bm.Default()
  247. engine.GET("/large/response", cache.Cache(control, filter), func(c *bm.Context) {
  248. c.JSON(map[string]interface{}{
  249. "index": atomic.AddInt32(&i, 1),
  250. "Hello0": "World",
  251. "Hello1": "World",
  252. "Hello2": "World",
  253. "Hello3": "World",
  254. "Hello4": "World",
  255. "Hello5": "World",
  256. "Hello6": "World",
  257. "Hello7": "World",
  258. "Hello8": "World",
  259. }, nil)
  260. })
  261. engine.GET("/large/response/error", cache.Cache(control, filter), func(c *bm.Context) {
  262. c.JSON(nil, ecode.RequestErr)
  263. })
  264. wg.Add(1)
  265. go func() {
  266. engine.Run(":18080")
  267. wg.Done()
  268. }()
  269. defer func() {
  270. engine.Server().Shutdown(context.TODO())
  271. wg.Wait()
  272. }()
  273. time.Sleep(time.Second)
  274. code, content, headers, err := httpGet(uri(SockAddr, "/large/response?name=hello&age=1"))
  275. assert.NoError(t, err)
  276. assert.Equal(t, 200, code)
  277. assert.NotEmpty(t, content)
  278. assert.Equal(t, "max-age=30", headers.Get("Cache-Control"))
  279. exp, err := http.ParseTime(headers.Get("Expires"))
  280. assert.NoError(t, err)
  281. assert.InDelta(t, 30, exp.Unix()-time.Now().Unix(), 5)
  282. code, content, headers, err = httpGet(uri(SockAddr, "/large/response/error?name=hello&age=1&cache=false"))
  283. assert.NoError(t, err)
  284. assert.Equal(t, 200, code)
  285. assert.NotEmpty(t, content)
  286. assert.Empty(t, headers.Get("Expires"))
  287. assert.Empty(t, headers.Get("Cache-Control"))
  288. code, content, headers, err = httpGet(uri(SockAddr, "/large/response/error?name=hello&age=1"))
  289. assert.NoError(t, err)
  290. assert.Equal(t, 200, code)
  291. assert.NotEmpty(t, content)
  292. assert.Empty(t, headers.Get("Expires"))
  293. assert.Empty(t, headers.Get("Cache-Control"))
  294. }
  295. }
  296. func httpGet(url string) (code int, content []byte, headers http.Header, err error) {
  297. resp, err := http.Get(url)
  298. if err != nil {
  299. return
  300. }
  301. defer resp.Body.Close()
  302. if content, err = ioutil.ReadAll(resp.Body); err != nil {
  303. return
  304. }
  305. code = resp.StatusCode
  306. headers = resp.Header
  307. return
  308. }