github.com/gofiber/fiber/v2@v2.47.0/middleware/cache/cache_test.go (about) 1 // Special thanks to @codemicro for moving this to fiber core 2 // Original middleware: github.com/codemicro/fiber-cache 3 package cache 4 5 import ( 6 "bytes" 7 "fmt" 8 "io" 9 "math" 10 "net/http/httptest" 11 "os" 12 "strconv" 13 "testing" 14 "time" 15 16 "github.com/gofiber/fiber/v2" 17 "github.com/gofiber/fiber/v2/internal/storage/memory" 18 "github.com/gofiber/fiber/v2/middleware/etag" 19 "github.com/gofiber/fiber/v2/utils" 20 21 "github.com/valyala/fasthttp" 22 ) 23 24 func Test_Cache_CacheControl(t *testing.T) { 25 t.Parallel() 26 27 app := fiber.New() 28 29 app.Use(New(Config{ 30 CacheControl: true, 31 Expiration: 10 * time.Second, 32 })) 33 34 app.Get("/", func(c *fiber.Ctx) error { 35 return c.SendString("Hello, World!") 36 }) 37 38 _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 39 utils.AssertEqual(t, nil, err) 40 41 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 42 utils.AssertEqual(t, nil, err) 43 utils.AssertEqual(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl)) 44 } 45 46 func Test_Cache_Expired(t *testing.T) { 47 t.Parallel() 48 49 app := fiber.New() 50 app.Use(New(Config{Expiration: 2 * time.Second})) 51 52 app.Get("/", func(c *fiber.Ctx) error { 53 return c.SendString(fmt.Sprintf("%d", time.Now().UnixNano())) 54 }) 55 56 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 57 utils.AssertEqual(t, nil, err) 58 body, err := io.ReadAll(resp.Body) 59 utils.AssertEqual(t, nil, err) 60 61 // Sleep until the cache is expired 62 time.Sleep(3 * time.Second) 63 64 respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 65 utils.AssertEqual(t, nil, err) 66 bodyCached, err := io.ReadAll(respCached.Body) 67 utils.AssertEqual(t, nil, err) 68 69 if bytes.Equal(body, bodyCached) { 70 t.Errorf("Cache should have expired: %s, %s", body, bodyCached) 71 } 72 73 // Next response should be also cached 74 respCachedNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 75 utils.AssertEqual(t, nil, err) 76 bodyCachedNextRound, err := io.ReadAll(respCachedNextRound.Body) 77 utils.AssertEqual(t, nil, err) 78 79 if !bytes.Equal(bodyCachedNextRound, bodyCached) { 80 t.Errorf("Cache should not have expired: %s, %s", bodyCached, bodyCachedNextRound) 81 } 82 } 83 84 func Test_Cache(t *testing.T) { 85 t.Parallel() 86 87 app := fiber.New() 88 app.Use(New()) 89 90 app.Get("/", func(c *fiber.Ctx) error { 91 now := fmt.Sprintf("%d", time.Now().UnixNano()) 92 return c.SendString(now) 93 }) 94 95 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 96 resp, err := app.Test(req) 97 utils.AssertEqual(t, nil, err) 98 99 cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil) 100 cachedResp, err := app.Test(cachedReq) 101 utils.AssertEqual(t, nil, err) 102 103 body, err := io.ReadAll(resp.Body) 104 utils.AssertEqual(t, nil, err) 105 cachedBody, err := io.ReadAll(cachedResp.Body) 106 utils.AssertEqual(t, nil, err) 107 108 utils.AssertEqual(t, cachedBody, body) 109 } 110 111 // go test -run Test_Cache_WithNoCacheRequestDirective 112 func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { 113 t.Parallel() 114 115 app := fiber.New() 116 app.Use(New()) 117 118 app.Get("/", func(c *fiber.Ctx) error { 119 return c.SendString(c.Query("id", "1")) 120 }) 121 122 // Request id = 1 123 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 124 resp, err := app.Test(req) 125 utils.AssertEqual(t, nil, err) 126 body, err := io.ReadAll(resp.Body) 127 utils.AssertEqual(t, nil, err) 128 utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) 129 utils.AssertEqual(t, []byte("1"), body) 130 // Response cached, entry id = 1 131 132 // Request id = 2 without Cache-Control: no-cache 133 cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 134 cachedResp, err := app.Test(cachedReq) 135 utils.AssertEqual(t, nil, err) 136 cachedBody, err := io.ReadAll(cachedResp.Body) 137 utils.AssertEqual(t, nil, err) 138 utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache")) 139 utils.AssertEqual(t, []byte("1"), cachedBody) 140 // Response not cached, returns cached response, entry id = 1 141 142 // Request id = 2 with Cache-Control: no-cache 143 noCacheReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 144 noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache) 145 noCacheResp, err := app.Test(noCacheReq) 146 utils.AssertEqual(t, nil, err) 147 noCacheBody, err := io.ReadAll(noCacheResp.Body) 148 utils.AssertEqual(t, nil, err) 149 utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) 150 utils.AssertEqual(t, []byte("2"), noCacheBody) 151 // Response cached, returns updated response, entry = 2 152 153 /* Check Test_Cache_WithETagAndNoCacheRequestDirective */ 154 // Request id = 2 with Cache-Control: no-cache again 155 noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 156 noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache) 157 noCacheResp1, err := app.Test(noCacheReq1) 158 utils.AssertEqual(t, nil, err) 159 noCacheBody1, err := io.ReadAll(noCacheResp1.Body) 160 utils.AssertEqual(t, nil, err) 161 utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache")) 162 utils.AssertEqual(t, []byte("2"), noCacheBody1) 163 // Response cached, returns updated response, entry = 2 164 165 // Request id = 1 without Cache-Control: no-cache 166 cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil) 167 cachedResp1, err := app.Test(cachedReq1) 168 utils.AssertEqual(t, nil, err) 169 cachedBody1, err := io.ReadAll(cachedResp1.Body) 170 utils.AssertEqual(t, nil, err) 171 utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache")) 172 utils.AssertEqual(t, []byte("2"), cachedBody1) 173 // Response not cached, returns cached response, entry id = 2 174 } 175 176 // go test -run Test_Cache_WithETagAndNoCacheRequestDirective 177 func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { 178 t.Parallel() 179 180 app := fiber.New() 181 app.Use( 182 etag.New(), 183 New(), 184 ) 185 186 app.Get("/", func(c *fiber.Ctx) error { 187 return c.SendString(c.Query("id", "1")) 188 }) 189 190 // Request id = 1 191 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 192 resp, err := app.Test(req) 193 utils.AssertEqual(t, nil, err) 194 utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) 195 utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) 196 // Response cached, entry id = 1 197 198 // If response status 200 199 etagToken := resp.Header.Get("Etag") 200 201 // Request id = 2 with ETag but without Cache-Control: no-cache 202 cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 203 cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) 204 cachedResp, err := app.Test(cachedReq) 205 utils.AssertEqual(t, nil, err) 206 utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache")) 207 utils.AssertEqual(t, fiber.StatusNotModified, cachedResp.StatusCode) 208 // Response not cached, returns cached response, entry id = 1, status not modified 209 210 // Request id = 2 with ETag and Cache-Control: no-cache 211 noCacheReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 212 noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache) 213 noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) 214 noCacheResp, err := app.Test(noCacheReq) 215 utils.AssertEqual(t, nil, err) 216 utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) 217 utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode) 218 // Response cached, returns updated response, entry id = 2 219 220 // If response status 200 221 etagToken = noCacheResp.Header.Get("Etag") 222 223 // Request id = 2 with ETag and Cache-Control: no-cache again 224 noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 225 noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache) 226 noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) 227 noCacheResp1, err := app.Test(noCacheReq1) 228 utils.AssertEqual(t, nil, err) 229 utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache")) 230 utils.AssertEqual(t, fiber.StatusNotModified, noCacheResp1.StatusCode) 231 // Response cached, returns updated response, entry id = 2, status not modified 232 233 // Request id = 1 without ETag and Cache-Control: no-cache 234 cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil) 235 cachedResp1, err := app.Test(cachedReq1) 236 utils.AssertEqual(t, nil, err) 237 utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache")) 238 utils.AssertEqual(t, fiber.StatusOK, cachedResp1.StatusCode) 239 // Response not cached, returns cached response, entry id = 2 240 } 241 242 // go test -run Test_Cache_WithNoStoreRequestDirective 243 func Test_Cache_WithNoStoreRequestDirective(t *testing.T) { 244 t.Parallel() 245 246 app := fiber.New() 247 app.Use(New()) 248 249 app.Get("/", func(c *fiber.Ctx) error { 250 return c.SendString(c.Query("id", "1")) 251 }) 252 253 // Request id = 2 254 noStoreReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil) 255 noStoreReq.Header.Set(fiber.HeaderCacheControl, noStore) 256 noStoreResp, err := app.Test(noStoreReq) 257 utils.AssertEqual(t, nil, err) 258 noStoreBody, err := io.ReadAll(noStoreResp.Body) 259 utils.AssertEqual(t, nil, err) 260 utils.AssertEqual(t, []byte("2"), noStoreBody) 261 // Response not cached, returns updated response 262 } 263 264 func Test_Cache_WithSeveralRequests(t *testing.T) { 265 t.Parallel() 266 267 app := fiber.New() 268 269 app.Use(New(Config{ 270 CacheControl: true, 271 Expiration: 10 * time.Second, 272 })) 273 274 app.Get("/:id", func(c *fiber.Ctx) error { 275 return c.SendString(c.Params("id")) 276 }) 277 278 for runs := 0; runs < 10; runs++ { 279 for i := 0; i < 10; i++ { 280 func(id int) { 281 rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, fmt.Sprintf("/%d", id), nil)) 282 utils.AssertEqual(t, nil, err) 283 284 defer func(body io.ReadCloser) { 285 err := body.Close() 286 utils.AssertEqual(t, nil, err) 287 }(rsp.Body) 288 289 idFromServ, err := io.ReadAll(rsp.Body) 290 utils.AssertEqual(t, nil, err) 291 292 a, err := strconv.Atoi(string(idFromServ)) 293 utils.AssertEqual(t, nil, err) 294 295 // SomeTimes,The id is not equal with a 296 utils.AssertEqual(t, id, a) 297 }(i) 298 } 299 } 300 } 301 302 func Test_Cache_Invalid_Expiration(t *testing.T) { 303 t.Parallel() 304 305 app := fiber.New() 306 cache := New(Config{Expiration: 0 * time.Second}) 307 app.Use(cache) 308 309 app.Get("/", func(c *fiber.Ctx) error { 310 now := fmt.Sprintf("%d", time.Now().UnixNano()) 311 return c.SendString(now) 312 }) 313 314 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 315 resp, err := app.Test(req) 316 utils.AssertEqual(t, nil, err) 317 318 cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil) 319 cachedResp, err := app.Test(cachedReq) 320 utils.AssertEqual(t, nil, err) 321 322 body, err := io.ReadAll(resp.Body) 323 utils.AssertEqual(t, nil, err) 324 cachedBody, err := io.ReadAll(cachedResp.Body) 325 utils.AssertEqual(t, nil, err) 326 327 utils.AssertEqual(t, cachedBody, body) 328 } 329 330 func Test_Cache_Get(t *testing.T) { 331 t.Parallel() 332 333 app := fiber.New() 334 335 app.Use(New()) 336 337 app.Post("/", func(c *fiber.Ctx) error { 338 return c.SendString(c.Query("cache")) 339 }) 340 341 app.Get("/get", func(c *fiber.Ctx) error { 342 return c.SendString(c.Query("cache")) 343 }) 344 345 resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=123", nil)) 346 utils.AssertEqual(t, nil, err) 347 body, err := io.ReadAll(resp.Body) 348 utils.AssertEqual(t, nil, err) 349 utils.AssertEqual(t, "123", string(body)) 350 351 resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil)) 352 utils.AssertEqual(t, nil, err) 353 body, err = io.ReadAll(resp.Body) 354 utils.AssertEqual(t, nil, err) 355 utils.AssertEqual(t, "12345", string(body)) 356 357 resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=123", nil)) 358 utils.AssertEqual(t, nil, err) 359 body, err = io.ReadAll(resp.Body) 360 utils.AssertEqual(t, nil, err) 361 utils.AssertEqual(t, "123", string(body)) 362 363 resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil)) 364 utils.AssertEqual(t, nil, err) 365 body, err = io.ReadAll(resp.Body) 366 utils.AssertEqual(t, nil, err) 367 utils.AssertEqual(t, "123", string(body)) 368 } 369 370 func Test_Cache_Post(t *testing.T) { 371 t.Parallel() 372 373 app := fiber.New() 374 375 app.Use(New(Config{ 376 Methods: []string{fiber.MethodPost}, 377 })) 378 379 app.Post("/", func(c *fiber.Ctx) error { 380 return c.SendString(c.Query("cache")) 381 }) 382 383 app.Get("/get", func(c *fiber.Ctx) error { 384 return c.SendString(c.Query("cache")) 385 }) 386 387 resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=123", nil)) 388 utils.AssertEqual(t, nil, err) 389 body, err := io.ReadAll(resp.Body) 390 utils.AssertEqual(t, nil, err) 391 utils.AssertEqual(t, "123", string(body)) 392 393 resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil)) 394 utils.AssertEqual(t, nil, err) 395 body, err = io.ReadAll(resp.Body) 396 utils.AssertEqual(t, nil, err) 397 utils.AssertEqual(t, "123", string(body)) 398 399 resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=123", nil)) 400 utils.AssertEqual(t, nil, err) 401 body, err = io.ReadAll(resp.Body) 402 utils.AssertEqual(t, nil, err) 403 utils.AssertEqual(t, "123", string(body)) 404 405 resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil)) 406 utils.AssertEqual(t, nil, err) 407 body, err = io.ReadAll(resp.Body) 408 utils.AssertEqual(t, nil, err) 409 utils.AssertEqual(t, "12345", string(body)) 410 } 411 412 func Test_Cache_NothingToCache(t *testing.T) { 413 t.Parallel() 414 415 app := fiber.New() 416 417 app.Use(New(Config{Expiration: -(time.Second * 1)})) 418 419 app.Get("/", func(c *fiber.Ctx) error { 420 return c.SendString(time.Now().String()) 421 }) 422 423 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 424 utils.AssertEqual(t, nil, err) 425 body, err := io.ReadAll(resp.Body) 426 utils.AssertEqual(t, nil, err) 427 428 time.Sleep(500 * time.Millisecond) 429 430 respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 431 utils.AssertEqual(t, nil, err) 432 bodyCached, err := io.ReadAll(respCached.Body) 433 utils.AssertEqual(t, nil, err) 434 435 if bytes.Equal(body, bodyCached) { 436 t.Errorf("Cache should have expired: %s, %s", body, bodyCached) 437 } 438 } 439 440 func Test_Cache_CustomNext(t *testing.T) { 441 t.Parallel() 442 443 app := fiber.New() 444 445 app.Use(New(Config{ 446 Next: func(c *fiber.Ctx) bool { 447 return c.Response().StatusCode() != fiber.StatusOK 448 }, 449 CacheControl: true, 450 })) 451 452 app.Get("/", func(c *fiber.Ctx) error { 453 return c.SendString(time.Now().String()) 454 }) 455 456 app.Get("/error", func(c *fiber.Ctx) error { 457 return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String()) 458 }) 459 460 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 461 utils.AssertEqual(t, nil, err) 462 body, err := io.ReadAll(resp.Body) 463 utils.AssertEqual(t, nil, err) 464 465 respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 466 utils.AssertEqual(t, nil, err) 467 bodyCached, err := io.ReadAll(respCached.Body) 468 utils.AssertEqual(t, nil, err) 469 utils.AssertEqual(t, true, bytes.Equal(body, bodyCached)) 470 utils.AssertEqual(t, true, respCached.Header.Get(fiber.HeaderCacheControl) != "") 471 472 _, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) 473 utils.AssertEqual(t, nil, err) 474 475 errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) 476 utils.AssertEqual(t, nil, err) 477 utils.AssertEqual(t, true, errRespCached.Header.Get(fiber.HeaderCacheControl) == "") 478 } 479 480 func Test_CustomKey(t *testing.T) { 481 t.Parallel() 482 483 app := fiber.New() 484 var called bool 485 app.Use(New(Config{KeyGenerator: func(c *fiber.Ctx) string { 486 called = true 487 return utils.CopyString(c.Path()) 488 }})) 489 490 app.Get("/", func(c *fiber.Ctx) error { 491 return c.SendString("hi") 492 }) 493 494 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 495 _, err := app.Test(req) 496 utils.AssertEqual(t, nil, err) 497 utils.AssertEqual(t, true, called) 498 } 499 500 func Test_CustomExpiration(t *testing.T) { 501 t.Parallel() 502 503 app := fiber.New() 504 var called bool 505 var newCacheTime int 506 app.Use(New(Config{ExpirationGenerator: func(c *fiber.Ctx, cfg *Config) time.Duration { 507 called = true 508 var err error 509 newCacheTime, err = strconv.Atoi(c.GetRespHeader("Cache-Time", "600")) 510 utils.AssertEqual(t, nil, err) 511 return time.Second * time.Duration(newCacheTime) 512 }})) 513 514 app.Get("/", func(c *fiber.Ctx) error { 515 c.Response().Header.Add("Cache-Time", "1") 516 now := fmt.Sprintf("%d", time.Now().UnixNano()) 517 return c.SendString(now) 518 }) 519 520 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 521 utils.AssertEqual(t, nil, err) 522 utils.AssertEqual(t, true, called) 523 utils.AssertEqual(t, 1, newCacheTime) 524 525 // Sleep until the cache is expired 526 time.Sleep(1 * time.Second) 527 528 cachedResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 529 utils.AssertEqual(t, nil, err) 530 531 body, err := io.ReadAll(resp.Body) 532 utils.AssertEqual(t, nil, err) 533 cachedBody, err := io.ReadAll(cachedResp.Body) 534 utils.AssertEqual(t, nil, err) 535 536 if bytes.Equal(body, cachedBody) { 537 t.Errorf("Cache should have expired: %s, %s", body, cachedBody) 538 } 539 540 // Next response should be cached 541 cachedRespNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 542 utils.AssertEqual(t, nil, err) 543 cachedBodyNextRound, err := io.ReadAll(cachedRespNextRound.Body) 544 utils.AssertEqual(t, nil, err) 545 546 if !bytes.Equal(cachedBodyNextRound, cachedBody) { 547 t.Errorf("Cache should not have expired: %s, %s", cachedBodyNextRound, cachedBody) 548 } 549 } 550 551 func Test_AdditionalE2EResponseHeaders(t *testing.T) { 552 t.Parallel() 553 554 app := fiber.New() 555 app.Use(New(Config{ 556 StoreResponseHeaders: true, 557 })) 558 559 app.Get("/", func(c *fiber.Ctx) error { 560 c.Response().Header.Add("X-Foobar", "foobar") 561 return c.SendString("hi") 562 }) 563 564 req := httptest.NewRequest(fiber.MethodGet, "/", nil) 565 resp, err := app.Test(req) 566 utils.AssertEqual(t, nil, err) 567 utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar")) 568 569 req = httptest.NewRequest(fiber.MethodGet, "/", nil) 570 resp, err = app.Test(req) 571 utils.AssertEqual(t, nil, err) 572 utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar")) 573 } 574 575 func Test_CacheHeader(t *testing.T) { 576 t.Parallel() 577 578 app := fiber.New() 579 580 app.Use(New(Config{ 581 Expiration: 10 * time.Second, 582 Next: func(c *fiber.Ctx) bool { 583 return c.Response().StatusCode() != fiber.StatusOK 584 }, 585 })) 586 587 app.Get("/", func(c *fiber.Ctx) error { 588 return c.SendString("Hello, World!") 589 }) 590 591 app.Post("/", func(c *fiber.Ctx) error { 592 return c.SendString(c.Query("cache")) 593 }) 594 595 app.Get("/error", func(c *fiber.Ctx) error { 596 return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String()) 597 }) 598 599 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 600 utils.AssertEqual(t, nil, err) 601 utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) 602 603 resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 604 utils.AssertEqual(t, nil, err) 605 utils.AssertEqual(t, cacheHit, resp.Header.Get("X-Cache")) 606 607 resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil)) 608 utils.AssertEqual(t, nil, err) 609 utils.AssertEqual(t, cacheUnreachable, resp.Header.Get("X-Cache")) 610 611 errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil)) 612 utils.AssertEqual(t, nil, err) 613 utils.AssertEqual(t, cacheUnreachable, errRespCached.Header.Get("X-Cache")) 614 } 615 616 func Test_Cache_WithHead(t *testing.T) { 617 t.Parallel() 618 619 app := fiber.New() 620 app.Use(New()) 621 622 app.Get("/", func(c *fiber.Ctx) error { 623 now := fmt.Sprintf("%d", time.Now().UnixNano()) 624 return c.SendString(now) 625 }) 626 627 req := httptest.NewRequest(fiber.MethodHead, "/", nil) 628 resp, err := app.Test(req) 629 utils.AssertEqual(t, nil, err) 630 utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) 631 632 cachedReq := httptest.NewRequest(fiber.MethodHead, "/", nil) 633 cachedResp, err := app.Test(cachedReq) 634 utils.AssertEqual(t, nil, err) 635 utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache")) 636 637 body, err := io.ReadAll(resp.Body) 638 utils.AssertEqual(t, nil, err) 639 cachedBody, err := io.ReadAll(cachedResp.Body) 640 utils.AssertEqual(t, nil, err) 641 642 utils.AssertEqual(t, cachedBody, body) 643 } 644 645 func Test_Cache_WithHeadThenGet(t *testing.T) { 646 t.Parallel() 647 648 app := fiber.New() 649 app.Use(New()) 650 app.Get("/", func(c *fiber.Ctx) error { 651 return c.SendString(c.Query("cache")) 652 }) 653 654 headResp, err := app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil)) 655 utils.AssertEqual(t, nil, err) 656 headBody, err := io.ReadAll(headResp.Body) 657 utils.AssertEqual(t, nil, err) 658 utils.AssertEqual(t, "", string(headBody)) 659 utils.AssertEqual(t, cacheMiss, headResp.Header.Get("X-Cache")) 660 661 headResp, err = app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil)) 662 utils.AssertEqual(t, nil, err) 663 headBody, err = io.ReadAll(headResp.Body) 664 utils.AssertEqual(t, nil, err) 665 utils.AssertEqual(t, "", string(headBody)) 666 utils.AssertEqual(t, cacheHit, headResp.Header.Get("X-Cache")) 667 668 getResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil)) 669 utils.AssertEqual(t, nil, err) 670 getBody, err := io.ReadAll(getResp.Body) 671 utils.AssertEqual(t, nil, err) 672 utils.AssertEqual(t, "123", string(getBody)) 673 utils.AssertEqual(t, cacheMiss, getResp.Header.Get("X-Cache")) 674 675 getResp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil)) 676 utils.AssertEqual(t, nil, err) 677 getBody, err = io.ReadAll(getResp.Body) 678 utils.AssertEqual(t, nil, err) 679 utils.AssertEqual(t, "123", string(getBody)) 680 utils.AssertEqual(t, cacheHit, getResp.Header.Get("X-Cache")) 681 } 682 683 func Test_CustomCacheHeader(t *testing.T) { 684 t.Parallel() 685 686 app := fiber.New() 687 688 app.Use(New(Config{ 689 CacheHeader: "Cache-Status", 690 })) 691 692 app.Get("/", func(c *fiber.Ctx) error { 693 return c.SendString("Hello, World!") 694 }) 695 696 resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) 697 utils.AssertEqual(t, nil, err) 698 utils.AssertEqual(t, cacheMiss, resp.Header.Get("Cache-Status")) 699 } 700 701 // Because time points are updated once every X milliseconds, entries in tests can often have 702 // equal expiration times and thus be in an random order. This closure hands out increasing 703 // time intervals to maintain strong ascending order of expiration 704 func stableAscendingExpiration() func(c1 *fiber.Ctx, c2 *Config) time.Duration { 705 i := 0 706 return func(c1 *fiber.Ctx, c2 *Config) time.Duration { 707 i++ 708 return time.Hour * time.Duration(i) 709 } 710 } 711 712 func Test_Cache_MaxBytesOrder(t *testing.T) { 713 t.Parallel() 714 715 app := fiber.New() 716 app.Use(New(Config{ 717 MaxBytes: 2, 718 ExpirationGenerator: stableAscendingExpiration(), 719 })) 720 721 app.Get("/*", func(c *fiber.Ctx) error { 722 return c.SendString("1") 723 }) 724 725 cases := [][]string{ 726 // Insert a, b into cache of size 2 bytes (responses are 1 byte) 727 {"/a", cacheMiss}, 728 {"/b", cacheMiss}, 729 {"/a", cacheHit}, 730 {"/b", cacheHit}, 731 // Add c -> a evicted 732 {"/c", cacheMiss}, 733 {"/b", cacheHit}, 734 // Add a again -> b evicted 735 {"/a", cacheMiss}, 736 {"/c", cacheHit}, 737 // Add b -> c evicted 738 {"/b", cacheMiss}, 739 {"/c", cacheMiss}, 740 } 741 742 for idx, tcase := range cases { 743 rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil)) 744 utils.AssertEqual(t, nil, err) 745 utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) 746 } 747 } 748 749 func Test_Cache_MaxBytesSizes(t *testing.T) { 750 t.Parallel() 751 752 app := fiber.New() 753 754 app.Use(New(Config{ 755 MaxBytes: 7, 756 ExpirationGenerator: stableAscendingExpiration(), 757 })) 758 759 app.Get("/*", func(c *fiber.Ctx) error { 760 path := c.Context().URI().LastPathSegment() 761 size, err := strconv.Atoi(string(path)) 762 utils.AssertEqual(t, nil, err) 763 return c.Send(make([]byte, size)) 764 }) 765 766 cases := [][]string{ 767 {"/1", cacheMiss}, 768 {"/2", cacheMiss}, 769 {"/3", cacheMiss}, 770 {"/4", cacheMiss}, // 1+2+3+4 > 7 => 1,2 are evicted now 771 {"/3", cacheHit}, 772 {"/1", cacheMiss}, 773 {"/2", cacheMiss}, 774 {"/8", cacheUnreachable}, // too big to cache -> unreachable 775 } 776 777 for idx, tcase := range cases { 778 rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil)) 779 utils.AssertEqual(t, nil, err) 780 utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx)) 781 } 782 } 783 784 // go test -v -run=^$ -bench=Benchmark_Cache -benchmem -count=4 785 func Benchmark_Cache(b *testing.B) { 786 app := fiber.New() 787 788 app.Use(New()) 789 790 app.Get("/demo", func(c *fiber.Ctx) error { 791 data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark 792 return c.Status(fiber.StatusTeapot).Send(data) 793 }) 794 795 h := app.Handler() 796 797 fctx := &fasthttp.RequestCtx{} 798 fctx.Request.Header.SetMethod(fiber.MethodGet) 799 fctx.Request.SetRequestURI("/demo") 800 801 b.ReportAllocs() 802 b.ResetTimer() 803 804 for n := 0; n < b.N; n++ { 805 h(fctx) 806 } 807 808 utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) 809 utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000) 810 } 811 812 // go test -v -run=^$ -bench=Benchmark_Cache_Storage -benchmem -count=4 813 func Benchmark_Cache_Storage(b *testing.B) { 814 app := fiber.New() 815 816 app.Use(New(Config{ 817 Storage: memory.New(), 818 })) 819 820 app.Get("/demo", func(c *fiber.Ctx) error { 821 data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark 822 return c.Status(fiber.StatusTeapot).Send(data) 823 }) 824 825 h := app.Handler() 826 827 fctx := &fasthttp.RequestCtx{} 828 fctx.Request.Header.SetMethod(fiber.MethodGet) 829 fctx.Request.SetRequestURI("/demo") 830 831 b.ReportAllocs() 832 b.ResetTimer() 833 834 for n := 0; n < b.N; n++ { 835 h(fctx) 836 } 837 838 utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) 839 utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000) 840 } 841 842 func Benchmark_Cache_AdditionalHeaders(b *testing.B) { 843 app := fiber.New() 844 app.Use(New(Config{ 845 StoreResponseHeaders: true, 846 })) 847 848 app.Get("/demo", func(c *fiber.Ctx) error { 849 c.Response().Header.Add("X-Foobar", "foobar") 850 return c.SendStatus(418) 851 }) 852 853 h := app.Handler() 854 855 fctx := &fasthttp.RequestCtx{} 856 fctx.Request.Header.SetMethod(fiber.MethodGet) 857 fctx.Request.SetRequestURI("/demo") 858 859 b.ReportAllocs() 860 b.ResetTimer() 861 862 for n := 0; n < b.N; n++ { 863 h(fctx) 864 } 865 866 utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) 867 utils.AssertEqual(b, []byte("foobar"), fctx.Response.Header.Peek("X-Foobar")) 868 } 869 870 func Benchmark_Cache_MaxSize(b *testing.B) { 871 // The benchmark is run with three different MaxSize parameters 872 // 1) 0: Tracking is disabled = no overhead 873 // 2) MaxInt32: Enough to store all entries = no removals 874 // 3) 100: Small size = constant insertions and removals 875 cases := []uint{0, math.MaxUint32, 100} 876 names := []string{"Disabled", "Unlim", "LowBounded"} 877 for i, size := range cases { 878 b.Run(names[i], func(b *testing.B) { 879 app := fiber.New() 880 app.Use(New(Config{MaxBytes: size})) 881 882 app.Get("/*", func(c *fiber.Ctx) error { 883 return c.Status(fiber.StatusTeapot).SendString("1") 884 }) 885 886 h := app.Handler() 887 fctx := &fasthttp.RequestCtx{} 888 fctx.Request.Header.SetMethod(fiber.MethodGet) 889 890 b.ReportAllocs() 891 b.ResetTimer() 892 893 for n := 0; n < b.N; n++ { 894 fctx.Request.SetRequestURI(fmt.Sprintf("/%v", n)) 895 h(fctx) 896 } 897 898 utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode()) 899 }) 900 } 901 }