1 package httpcache
2
3 import (
4 "bytes"
5 "errors"
6 "flag"
7 "io"
8 "io/ioutil"
9 "net/http"
10 "net/http/httptest"
11 "os"
12 "strconv"
13 "testing"
14 "time"
15 )
16
17 var s struct {
18 server *httptest.Server
19 client http.Client
20 transport *Transport
21 done chan struct{}
22 }
23
24 type fakeClock struct {
25 elapsed time.Duration
26 }
27
28 func (c *fakeClock) since(t time.Time) time.Duration {
29 return c.elapsed
30 }
31
32 func TestMain(m *testing.M) {
33 flag.Parse()
34 setup()
35 code := m.Run()
36 teardown()
37 os.Exit(code)
38 }
39
40 func setup() {
41 tp := NewMemoryCacheTransport()
42 client := http.Client{Transport: tp}
43 s.transport = tp
44 s.client = client
45 s.done = make(chan struct{})
46
47 mux := http.NewServeMux()
48 s.server = httptest.NewServer(mux)
49
50 mux.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51 w.Header().Set("Cache-Control", "max-age=3600")
52 }))
53
54 mux.HandleFunc("/method", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55 w.Header().Set("Cache-Control", "max-age=3600")
56 w.Write([]byte(r.Method))
57 }))
58
59 mux.HandleFunc("/range", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60 lm := "Fri, 14 Dec 2010 01:01:50 GMT"
61 if r.Header.Get("if-modified-since") == lm {
62 w.WriteHeader(http.StatusNotModified)
63 return
64 }
65 w.Header().Set("last-modified", lm)
66 if r.Header.Get("range") == "bytes=4-9" {
67 w.WriteHeader(http.StatusPartialContent)
68 w.Write([]byte(" text "))
69 return
70 }
71 w.Write([]byte("Some text content"))
72 }))
73
74 mux.HandleFunc("/nostore", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75 w.Header().Set("Cache-Control", "no-store")
76 }))
77
78 mux.HandleFunc("/etag", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
79 etag := "124567"
80 if r.Header.Get("if-none-match") == etag {
81 w.WriteHeader(http.StatusNotModified)
82 return
83 }
84 w.Header().Set("etag", etag)
85 }))
86
87 mux.HandleFunc("/lastmodified", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88 lm := "Fri, 14 Dec 2010 01:01:50 GMT"
89 if r.Header.Get("if-modified-since") == lm {
90 w.WriteHeader(http.StatusNotModified)
91 return
92 }
93 w.Header().Set("last-modified", lm)
94 }))
95
96 mux.HandleFunc("/varyaccept", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97 w.Header().Set("Cache-Control", "max-age=3600")
98 w.Header().Set("Content-Type", "text/plain")
99 w.Header().Set("Vary", "Accept")
100 w.Write([]byte("Some text content"))
101 }))
102
103 mux.HandleFunc("/doublevary", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
104 w.Header().Set("Cache-Control", "max-age=3600")
105 w.Header().Set("Content-Type", "text/plain")
106 w.Header().Set("Vary", "Accept, Accept-Language")
107 w.Write([]byte("Some text content"))
108 }))
109 mux.HandleFunc("/2varyheaders", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110 w.Header().Set("Cache-Control", "max-age=3600")
111 w.Header().Set("Content-Type", "text/plain")
112 w.Header().Add("Vary", "Accept")
113 w.Header().Add("Vary", "Accept-Language")
114 w.Write([]byte("Some text content"))
115 }))
116 mux.HandleFunc("/varyunused", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117 w.Header().Set("Cache-Control", "max-age=3600")
118 w.Header().Set("Content-Type", "text/plain")
119 w.Header().Set("Vary", "X-Madeup-Header")
120 w.Write([]byte("Some text content"))
121 }))
122
123 mux.HandleFunc("/cachederror", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 etag := "abc"
125 if r.Header.Get("if-none-match") == etag {
126 w.WriteHeader(http.StatusNotModified)
127 return
128 }
129 w.Header().Set("etag", etag)
130 w.WriteHeader(http.StatusNotFound)
131 w.Write([]byte("Not found"))
132 }))
133
134 updateFieldsCounter := 0
135 mux.HandleFunc("/updatefields", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136 w.Header().Set("X-Counter", strconv.Itoa(updateFieldsCounter))
137 w.Header().Set("Etag", `"e"`)
138 updateFieldsCounter++
139 if r.Header.Get("if-none-match") != "" {
140 w.WriteHeader(http.StatusNotModified)
141 return
142 }
143 w.Write([]byte("Some text content"))
144 }))
145
146
147 mux.HandleFunc("/3seconds", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
148 time.Sleep(3 * time.Second)
149 }))
150
151 mux.HandleFunc("/infinite", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
152 for {
153 select {
154 case <-s.done:
155 return
156 default:
157 w.Write([]byte{0})
158 }
159 }
160 }))
161 }
162
163 func teardown() {
164 close(s.done)
165 s.server.Close()
166 }
167
168 func resetTest() {
169 s.transport.Cache = NewMemoryCache()
170 clock = &realClock{}
171 }
172
173
174
175 func TestCacheableMethod(t *testing.T) {
176 resetTest()
177 {
178 req, err := http.NewRequest("POST", s.server.URL+"/method", nil)
179 if err != nil {
180 t.Fatal(err)
181 }
182 resp, err := s.client.Do(req)
183 if err != nil {
184 t.Fatal(err)
185 }
186 var buf bytes.Buffer
187 _, err = io.Copy(&buf, resp.Body)
188 if err != nil {
189 t.Fatal(err)
190 }
191 err = resp.Body.Close()
192 if err != nil {
193 t.Fatal(err)
194 }
195 if got, want := buf.String(), "POST"; got != want {
196 t.Errorf("got %q, want %q", got, want)
197 }
198 if resp.StatusCode != http.StatusOK {
199 t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
200 }
201 }
202 {
203 req, err := http.NewRequest("GET", s.server.URL+"/method", nil)
204 if err != nil {
205 t.Fatal(err)
206 }
207 resp, err := s.client.Do(req)
208 if err != nil {
209 t.Fatal(err)
210 }
211 var buf bytes.Buffer
212 _, err = io.Copy(&buf, resp.Body)
213 if err != nil {
214 t.Fatal(err)
215 }
216 err = resp.Body.Close()
217 if err != nil {
218 t.Fatal(err)
219 }
220 if got, want := buf.String(), "GET"; got != want {
221 t.Errorf("got wrong body %q, want %q", got, want)
222 }
223 if resp.StatusCode != http.StatusOK {
224 t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
225 }
226 if resp.Header.Get(XFromCache) != "" {
227 t.Errorf("XFromCache header isn't blank")
228 }
229 }
230 }
231
232 func TestDontServeHeadResponseToGetRequest(t *testing.T) {
233 resetTest()
234 url := s.server.URL + "/"
235 req, err := http.NewRequest(http.MethodHead, url, nil)
236 if err != nil {
237 t.Fatal(err)
238 }
239 _, err = s.client.Do(req)
240 if err != nil {
241 t.Fatal(err)
242 }
243 req, err = http.NewRequest(http.MethodGet, url, nil)
244 if err != nil {
245 t.Fatal(err)
246 }
247 resp, err := s.client.Do(req)
248 if err != nil {
249 t.Fatal(err)
250 }
251 if resp.Header.Get(XFromCache) != "" {
252 t.Errorf("Cache should not match")
253 }
254 }
255
256 func TestDontStorePartialRangeInCache(t *testing.T) {
257 resetTest()
258 {
259 req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
260 if err != nil {
261 t.Fatal(err)
262 }
263 req.Header.Set("range", "bytes=4-9")
264 resp, err := s.client.Do(req)
265 if err != nil {
266 t.Fatal(err)
267 }
268 var buf bytes.Buffer
269 _, err = io.Copy(&buf, resp.Body)
270 if err != nil {
271 t.Fatal(err)
272 }
273 err = resp.Body.Close()
274 if err != nil {
275 t.Fatal(err)
276 }
277 if got, want := buf.String(), " text "; got != want {
278 t.Errorf("got %q, want %q", got, want)
279 }
280 if resp.StatusCode != http.StatusPartialContent {
281 t.Errorf("response status code isn't 206 Partial Content: %v", resp.StatusCode)
282 }
283 }
284 {
285 req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
286 if err != nil {
287 t.Fatal(err)
288 }
289 resp, err := s.client.Do(req)
290 if err != nil {
291 t.Fatal(err)
292 }
293 var buf bytes.Buffer
294 _, err = io.Copy(&buf, resp.Body)
295 if err != nil {
296 t.Fatal(err)
297 }
298 err = resp.Body.Close()
299 if err != nil {
300 t.Fatal(err)
301 }
302 if got, want := buf.String(), "Some text content"; got != want {
303 t.Errorf("got %q, want %q", got, want)
304 }
305 if resp.StatusCode != http.StatusOK {
306 t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
307 }
308 if resp.Header.Get(XFromCache) != "" {
309 t.Error("XFromCache header isn't blank")
310 }
311 }
312 {
313 req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
314 if err != nil {
315 t.Fatal(err)
316 }
317 resp, err := s.client.Do(req)
318 if err != nil {
319 t.Fatal(err)
320 }
321 var buf bytes.Buffer
322 _, err = io.Copy(&buf, resp.Body)
323 if err != nil {
324 t.Fatal(err)
325 }
326 err = resp.Body.Close()
327 if err != nil {
328 t.Fatal(err)
329 }
330 if got, want := buf.String(), "Some text content"; got != want {
331 t.Errorf("got %q, want %q", got, want)
332 }
333 if resp.StatusCode != http.StatusOK {
334 t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
335 }
336 if resp.Header.Get(XFromCache) != "1" {
337 t.Errorf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
338 }
339 }
340 {
341 req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
342 if err != nil {
343 t.Fatal(err)
344 }
345 req.Header.Set("range", "bytes=4-9")
346 resp, err := s.client.Do(req)
347 if err != nil {
348 t.Fatal(err)
349 }
350 var buf bytes.Buffer
351 _, err = io.Copy(&buf, resp.Body)
352 if err != nil {
353 t.Fatal(err)
354 }
355 err = resp.Body.Close()
356 if err != nil {
357 t.Fatal(err)
358 }
359 if got, want := buf.String(), " text "; got != want {
360 t.Errorf("got %q, want %q", got, want)
361 }
362 if resp.StatusCode != http.StatusPartialContent {
363 t.Errorf("response status code isn't 206 Partial Content: %v", resp.StatusCode)
364 }
365 }
366 }
367
368 func TestCacheOnlyIfBodyRead(t *testing.T) {
369 resetTest()
370 {
371 req, err := http.NewRequest("GET", s.server.URL, nil)
372 if err != nil {
373 t.Fatal(err)
374 }
375 resp, err := s.client.Do(req)
376 if err != nil {
377 t.Fatal(err)
378 }
379 if resp.Header.Get(XFromCache) != "" {
380 t.Fatal("XFromCache header isn't blank")
381 }
382
383 resp.Body.Close()
384 }
385 {
386 req, err := http.NewRequest("GET", s.server.URL, nil)
387 if err != nil {
388 t.Fatal(err)
389 }
390 resp, err := s.client.Do(req)
391 if err != nil {
392 t.Fatal(err)
393 }
394 defer resp.Body.Close()
395 if resp.Header.Get(XFromCache) != "" {
396 t.Fatalf("XFromCache header isn't blank")
397 }
398 }
399 }
400
401 func TestOnlyReadBodyOnDemand(t *testing.T) {
402 resetTest()
403
404 req, err := http.NewRequest("GET", s.server.URL+"/infinite", nil)
405 if err != nil {
406 t.Fatal(err)
407 }
408 resp, err := s.client.Do(req)
409 if err != nil {
410 t.Fatal(err)
411 }
412 buf := make([]byte, 10)
413 _, err = resp.Body.Read(buf)
414 if err != nil {
415 t.Fatal(err)
416 }
417 resp.Body.Close()
418 }
419
420 func TestGetOnlyIfCachedHit(t *testing.T) {
421 resetTest()
422 {
423 req, err := http.NewRequest("GET", s.server.URL, nil)
424 if err != nil {
425 t.Fatal(err)
426 }
427 resp, err := s.client.Do(req)
428 if err != nil {
429 t.Fatal(err)
430 }
431 defer resp.Body.Close()
432 if resp.Header.Get(XFromCache) != "" {
433 t.Fatal("XFromCache header isn't blank")
434 }
435 _, err = ioutil.ReadAll(resp.Body)
436 if err != nil {
437 t.Fatal(err)
438 }
439 }
440 {
441 req, err := http.NewRequest("GET", s.server.URL, nil)
442 if err != nil {
443 t.Fatal(err)
444 }
445 req.Header.Add("cache-control", "only-if-cached")
446 resp, err := s.client.Do(req)
447 if err != nil {
448 t.Fatal(err)
449 }
450 defer resp.Body.Close()
451 if resp.Header.Get(XFromCache) != "1" {
452 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
453 }
454 if resp.StatusCode != http.StatusOK {
455 t.Fatalf("response status code isn't 200 OK: %v", resp.StatusCode)
456 }
457 }
458 }
459
460 func TestGetOnlyIfCachedMiss(t *testing.T) {
461 resetTest()
462 req, err := http.NewRequest("GET", s.server.URL, nil)
463 if err != nil {
464 t.Fatal(err)
465 }
466 req.Header.Add("cache-control", "only-if-cached")
467 resp, err := s.client.Do(req)
468 if err != nil {
469 t.Fatal(err)
470 }
471 defer resp.Body.Close()
472 if resp.Header.Get(XFromCache) != "" {
473 t.Fatal("XFromCache header isn't blank")
474 }
475 if resp.StatusCode != http.StatusGatewayTimeout {
476 t.Fatalf("response status code isn't 504 GatewayTimeout: %v", resp.StatusCode)
477 }
478 }
479
480 func TestGetNoStoreRequest(t *testing.T) {
481 resetTest()
482 req, err := http.NewRequest("GET", s.server.URL, nil)
483 if err != nil {
484 t.Fatal(err)
485 }
486 req.Header.Add("Cache-Control", "no-store")
487 {
488 resp, err := s.client.Do(req)
489 if err != nil {
490 t.Fatal(err)
491 }
492 defer resp.Body.Close()
493 if resp.Header.Get(XFromCache) != "" {
494 t.Fatal("XFromCache header isn't blank")
495 }
496 }
497 {
498 resp, err := s.client.Do(req)
499 if err != nil {
500 t.Fatal(err)
501 }
502 defer resp.Body.Close()
503 if resp.Header.Get(XFromCache) != "" {
504 t.Fatal("XFromCache header isn't blank")
505 }
506 }
507 }
508
509 func TestGetNoStoreResponse(t *testing.T) {
510 resetTest()
511 req, err := http.NewRequest("GET", s.server.URL+"/nostore", nil)
512 if err != nil {
513 t.Fatal(err)
514 }
515 {
516 resp, err := s.client.Do(req)
517 if err != nil {
518 t.Fatal(err)
519 }
520 defer resp.Body.Close()
521 if resp.Header.Get(XFromCache) != "" {
522 t.Fatal("XFromCache header isn't blank")
523 }
524 }
525 {
526 resp, err := s.client.Do(req)
527 if err != nil {
528 t.Fatal(err)
529 }
530 defer resp.Body.Close()
531 if resp.Header.Get(XFromCache) != "" {
532 t.Fatal("XFromCache header isn't blank")
533 }
534 }
535 }
536
537 func TestGetWithEtag(t *testing.T) {
538 resetTest()
539 req, err := http.NewRequest("GET", s.server.URL+"/etag", nil)
540 if err != nil {
541 t.Fatal(err)
542 }
543 {
544 resp, err := s.client.Do(req)
545 if err != nil {
546 t.Fatal(err)
547 }
548 defer resp.Body.Close()
549 if resp.Header.Get(XFromCache) != "" {
550 t.Fatal("XFromCache header isn't blank")
551 }
552 _, err = ioutil.ReadAll(resp.Body)
553 if err != nil {
554 t.Fatal(err)
555 }
556
557 }
558 {
559 resp, err := s.client.Do(req)
560 if err != nil {
561 t.Fatal(err)
562 }
563 defer resp.Body.Close()
564 if resp.Header.Get(XFromCache) != "1" {
565 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
566 }
567
568 if resp.StatusCode != http.StatusOK {
569 t.Fatalf("response status code isn't 200 OK: %v", resp.StatusCode)
570 }
571 if _, ok := resp.Header["Connection"]; ok {
572 t.Fatalf("Connection header isn't absent")
573 }
574 }
575 }
576
577 func TestGetWithLastModified(t *testing.T) {
578 resetTest()
579 req, err := http.NewRequest("GET", s.server.URL+"/lastmodified", nil)
580 if err != nil {
581 t.Fatal(err)
582 }
583 {
584 resp, err := s.client.Do(req)
585 if err != nil {
586 t.Fatal(err)
587 }
588 defer resp.Body.Close()
589 if resp.Header.Get(XFromCache) != "" {
590 t.Fatal("XFromCache header isn't blank")
591 }
592 _, err = ioutil.ReadAll(resp.Body)
593 if err != nil {
594 t.Fatal(err)
595 }
596 }
597 {
598 resp, err := s.client.Do(req)
599 if err != nil {
600 t.Fatal(err)
601 }
602 defer resp.Body.Close()
603 if resp.Header.Get(XFromCache) != "1" {
604 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
605 }
606 }
607 }
608
609 func TestGetWithVary(t *testing.T) {
610 resetTest()
611 req, err := http.NewRequest("GET", s.server.URL+"/varyaccept", nil)
612 if err != nil {
613 t.Fatal(err)
614 }
615 req.Header.Set("Accept", "text/plain")
616 {
617 resp, err := s.client.Do(req)
618 if err != nil {
619 t.Fatal(err)
620 }
621 defer resp.Body.Close()
622 if resp.Header.Get("Vary") != "Accept" {
623 t.Fatalf(`Vary header isn't "Accept": %v`, resp.Header.Get("Vary"))
624 }
625 _, err = ioutil.ReadAll(resp.Body)
626 if err != nil {
627 t.Fatal(err)
628 }
629 }
630 {
631 resp, err := s.client.Do(req)
632 if err != nil {
633 t.Fatal(err)
634 }
635 defer resp.Body.Close()
636 if resp.Header.Get(XFromCache) != "1" {
637 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
638 }
639 }
640 req.Header.Set("Accept", "text/html")
641 {
642 resp, err := s.client.Do(req)
643 if err != nil {
644 t.Fatal(err)
645 }
646 defer resp.Body.Close()
647 if resp.Header.Get(XFromCache) != "" {
648 t.Fatal("XFromCache header isn't blank")
649 }
650 }
651 req.Header.Set("Accept", "")
652 {
653 resp, err := s.client.Do(req)
654 if err != nil {
655 t.Fatal(err)
656 }
657 defer resp.Body.Close()
658 if resp.Header.Get(XFromCache) != "" {
659 t.Fatal("XFromCache header isn't blank")
660 }
661 }
662 }
663
664 func TestGetWithDoubleVary(t *testing.T) {
665 resetTest()
666 req, err := http.NewRequest("GET", s.server.URL+"/doublevary", nil)
667 if err != nil {
668 t.Fatal(err)
669 }
670 req.Header.Set("Accept", "text/plain")
671 req.Header.Set("Accept-Language", "da, en-gb;q=0.8, en;q=0.7")
672 {
673 resp, err := s.client.Do(req)
674 if err != nil {
675 t.Fatal(err)
676 }
677 defer resp.Body.Close()
678 if resp.Header.Get("Vary") == "" {
679 t.Fatalf(`Vary header is blank`)
680 }
681 _, err = ioutil.ReadAll(resp.Body)
682 if err != nil {
683 t.Fatal(err)
684 }
685 }
686 {
687 resp, err := s.client.Do(req)
688 if err != nil {
689 t.Fatal(err)
690 }
691 defer resp.Body.Close()
692 if resp.Header.Get(XFromCache) != "1" {
693 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
694 }
695 }
696 req.Header.Set("Accept-Language", "")
697 {
698 resp, err := s.client.Do(req)
699 if err != nil {
700 t.Fatal(err)
701 }
702 defer resp.Body.Close()
703 if resp.Header.Get(XFromCache) != "" {
704 t.Fatal("XFromCache header isn't blank")
705 }
706 }
707 req.Header.Set("Accept-Language", "da")
708 {
709 resp, err := s.client.Do(req)
710 if err != nil {
711 t.Fatal(err)
712 }
713 defer resp.Body.Close()
714 if resp.Header.Get(XFromCache) != "" {
715 t.Fatal("XFromCache header isn't blank")
716 }
717 }
718 }
719
720 func TestGetWith2VaryHeaders(t *testing.T) {
721 resetTest()
722
723
724 const (
725 accept = "text/plain"
726 acceptLanguage = "da, en-gb;q=0.8, en;q=0.7"
727 )
728 req, err := http.NewRequest("GET", s.server.URL+"/2varyheaders", nil)
729 if err != nil {
730 t.Fatal(err)
731 }
732 req.Header.Set("Accept", accept)
733 req.Header.Set("Accept-Language", acceptLanguage)
734 {
735 resp, err := s.client.Do(req)
736 if err != nil {
737 t.Fatal(err)
738 }
739 defer resp.Body.Close()
740 if resp.Header.Get("Vary") == "" {
741 t.Fatalf(`Vary header is blank`)
742 }
743 _, err = ioutil.ReadAll(resp.Body)
744 if err != nil {
745 t.Fatal(err)
746 }
747 }
748 {
749 resp, err := s.client.Do(req)
750 if err != nil {
751 t.Fatal(err)
752 }
753 defer resp.Body.Close()
754 if resp.Header.Get(XFromCache) != "1" {
755 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
756 }
757 }
758 req.Header.Set("Accept-Language", "")
759 {
760 resp, err := s.client.Do(req)
761 if err != nil {
762 t.Fatal(err)
763 }
764 defer resp.Body.Close()
765 if resp.Header.Get(XFromCache) != "" {
766 t.Fatal("XFromCache header isn't blank")
767 }
768 }
769 req.Header.Set("Accept-Language", "da")
770 {
771 resp, err := s.client.Do(req)
772 if err != nil {
773 t.Fatal(err)
774 }
775 defer resp.Body.Close()
776 if resp.Header.Get(XFromCache) != "" {
777 t.Fatal("XFromCache header isn't blank")
778 }
779 }
780 req.Header.Set("Accept-Language", acceptLanguage)
781 req.Header.Set("Accept", "")
782 {
783 resp, err := s.client.Do(req)
784 if err != nil {
785 t.Fatal(err)
786 }
787 defer resp.Body.Close()
788 if resp.Header.Get(XFromCache) != "" {
789 t.Fatal("XFromCache header isn't blank")
790 }
791 }
792 req.Header.Set("Accept", "image/png")
793 {
794 resp, err := s.client.Do(req)
795 if err != nil {
796 t.Fatal(err)
797 }
798 defer resp.Body.Close()
799 if resp.Header.Get(XFromCache) != "" {
800 t.Fatal("XFromCache header isn't blank")
801 }
802 _, err = ioutil.ReadAll(resp.Body)
803 if err != nil {
804 t.Fatal(err)
805 }
806 }
807 {
808 resp, err := s.client.Do(req)
809 if err != nil {
810 t.Fatal(err)
811 }
812 defer resp.Body.Close()
813 if resp.Header.Get(XFromCache) != "1" {
814 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
815 }
816 }
817 }
818
819 func TestGetVaryUnused(t *testing.T) {
820 resetTest()
821 req, err := http.NewRequest("GET", s.server.URL+"/varyunused", nil)
822 if err != nil {
823 t.Fatal(err)
824 }
825 req.Header.Set("Accept", "text/plain")
826 {
827 resp, err := s.client.Do(req)
828 if err != nil {
829 t.Fatal(err)
830 }
831 defer resp.Body.Close()
832 if resp.Header.Get("Vary") == "" {
833 t.Fatalf(`Vary header is blank`)
834 }
835 _, err = ioutil.ReadAll(resp.Body)
836 if err != nil {
837 t.Fatal(err)
838 }
839 }
840 {
841 resp, err := s.client.Do(req)
842 if err != nil {
843 t.Fatal(err)
844 }
845 defer resp.Body.Close()
846 if resp.Header.Get(XFromCache) != "1" {
847 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
848 }
849 }
850 }
851
852 func TestUpdateFields(t *testing.T) {
853 resetTest()
854 req, err := http.NewRequest("GET", s.server.URL+"/updatefields", nil)
855 if err != nil {
856 t.Fatal(err)
857 }
858 var counter, counter2 string
859 {
860 resp, err := s.client.Do(req)
861 if err != nil {
862 t.Fatal(err)
863 }
864 defer resp.Body.Close()
865 counter = resp.Header.Get("x-counter")
866 _, err = ioutil.ReadAll(resp.Body)
867 if err != nil {
868 t.Fatal(err)
869 }
870 }
871 {
872 resp, err := s.client.Do(req)
873 if err != nil {
874 t.Fatal(err)
875 }
876 defer resp.Body.Close()
877 if resp.Header.Get(XFromCache) != "1" {
878 t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
879 }
880 counter2 = resp.Header.Get("x-counter")
881 }
882 if counter == counter2 {
883 t.Fatalf(`both "x-counter" values are equal: %v %v`, counter, counter2)
884 }
885 }
886
887
888
889
890 func TestCachedErrorsKeepStatus(t *testing.T) {
891 resetTest()
892 req, err := http.NewRequest("GET", s.server.URL+"/cachederror", nil)
893 if err != nil {
894 t.Fatal(err)
895 }
896 {
897 resp, err := s.client.Do(req)
898 if err != nil {
899 t.Fatal(err)
900 }
901 defer resp.Body.Close()
902 io.Copy(ioutil.Discard, resp.Body)
903 }
904 {
905 resp, err := s.client.Do(req)
906 if err != nil {
907 t.Fatal(err)
908 }
909 defer resp.Body.Close()
910 if resp.StatusCode != http.StatusNotFound {
911 t.Fatalf("Status code isn't 404: %d", resp.StatusCode)
912 }
913 }
914 }
915
916 func TestParseCacheControl(t *testing.T) {
917 resetTest()
918 h := http.Header{}
919 for range parseCacheControl(h) {
920 t.Fatal("cacheControl should be empty")
921 }
922
923 h.Set("cache-control", "no-cache")
924 {
925 cc := parseCacheControl(h)
926 if _, ok := cc["foo"]; ok {
927 t.Error(`Value "foo" shouldn't exist`)
928 }
929 noCache, ok := cc["no-cache"]
930 if !ok {
931 t.Fatalf(`"no-cache" value isn't set`)
932 }
933 if noCache != "" {
934 t.Fatalf(`"no-cache" value isn't blank: %v`, noCache)
935 }
936 }
937 h.Set("cache-control", "no-cache, max-age=3600")
938 {
939 cc := parseCacheControl(h)
940 noCache, ok := cc["no-cache"]
941 if !ok {
942 t.Fatalf(`"no-cache" value isn't set`)
943 }
944 if noCache != "" {
945 t.Fatalf(`"no-cache" value isn't blank: %v`, noCache)
946 }
947 if cc["max-age"] != "3600" {
948 t.Fatalf(`"max-age" value isn't "3600": %v`, cc["max-age"])
949 }
950 }
951 }
952
953 func TestNoCacheRequestExpiration(t *testing.T) {
954 resetTest()
955 respHeaders := http.Header{}
956 respHeaders.Set("Cache-Control", "max-age=7200")
957
958 reqHeaders := http.Header{}
959 reqHeaders.Set("Cache-Control", "no-cache")
960 if getFreshness(respHeaders, reqHeaders) != transparent {
961 t.Fatal("freshness isn't transparent")
962 }
963 }
964
965 func TestNoCacheResponseExpiration(t *testing.T) {
966 resetTest()
967 respHeaders := http.Header{}
968 respHeaders.Set("Cache-Control", "no-cache")
969 respHeaders.Set("Expires", "Wed, 19 Apr 3000 11:43:00 GMT")
970
971 reqHeaders := http.Header{}
972 if getFreshness(respHeaders, reqHeaders) != stale {
973 t.Fatal("freshness isn't stale")
974 }
975 }
976
977 func TestReqMustRevalidate(t *testing.T) {
978 resetTest()
979
980
981 respHeaders := http.Header{}
982
983 reqHeaders := http.Header{}
984 reqHeaders.Set("Cache-Control", "must-revalidate")
985 if getFreshness(respHeaders, reqHeaders) != stale {
986 t.Fatal("freshness isn't stale")
987 }
988 }
989
990 func TestRespMustRevalidate(t *testing.T) {
991 resetTest()
992 respHeaders := http.Header{}
993 respHeaders.Set("Cache-Control", "must-revalidate")
994
995 reqHeaders := http.Header{}
996 if getFreshness(respHeaders, reqHeaders) != stale {
997 t.Fatal("freshness isn't stale")
998 }
999 }
1000
1001 func TestFreshExpiration(t *testing.T) {
1002 resetTest()
1003 now := time.Now()
1004 respHeaders := http.Header{}
1005 respHeaders.Set("date", now.Format(time.RFC1123))
1006 respHeaders.Set("expires", now.Add(time.Duration(2)*time.Second).Format(time.RFC1123))
1007
1008 reqHeaders := http.Header{}
1009 if getFreshness(respHeaders, reqHeaders) != fresh {
1010 t.Fatal("freshness isn't fresh")
1011 }
1012
1013 clock = &fakeClock{elapsed: 3 * time.Second}
1014 if getFreshness(respHeaders, reqHeaders) != stale {
1015 t.Fatal("freshness isn't stale")
1016 }
1017 }
1018
1019 func TestMaxAge(t *testing.T) {
1020 resetTest()
1021 now := time.Now()
1022 respHeaders := http.Header{}
1023 respHeaders.Set("date", now.Format(time.RFC1123))
1024 respHeaders.Set("cache-control", "max-age=2")
1025
1026 reqHeaders := http.Header{}
1027 if getFreshness(respHeaders, reqHeaders) != fresh {
1028 t.Fatal("freshness isn't fresh")
1029 }
1030
1031 clock = &fakeClock{elapsed: 3 * time.Second}
1032 if getFreshness(respHeaders, reqHeaders) != stale {
1033 t.Fatal("freshness isn't stale")
1034 }
1035 }
1036
1037 func TestMaxAgeZero(t *testing.T) {
1038 resetTest()
1039 now := time.Now()
1040 respHeaders := http.Header{}
1041 respHeaders.Set("date", now.Format(time.RFC1123))
1042 respHeaders.Set("cache-control", "max-age=0")
1043
1044 reqHeaders := http.Header{}
1045 if getFreshness(respHeaders, reqHeaders) != stale {
1046 t.Fatal("freshness isn't stale")
1047 }
1048 }
1049
1050 func TestBothMaxAge(t *testing.T) {
1051 resetTest()
1052 now := time.Now()
1053 respHeaders := http.Header{}
1054 respHeaders.Set("date", now.Format(time.RFC1123))
1055 respHeaders.Set("cache-control", "max-age=2")
1056
1057 reqHeaders := http.Header{}
1058 reqHeaders.Set("cache-control", "max-age=0")
1059 if getFreshness(respHeaders, reqHeaders) != stale {
1060 t.Fatal("freshness isn't stale")
1061 }
1062 }
1063
1064 func TestMinFreshWithExpires(t *testing.T) {
1065 resetTest()
1066 now := time.Now()
1067 respHeaders := http.Header{}
1068 respHeaders.Set("date", now.Format(time.RFC1123))
1069 respHeaders.Set("expires", now.Add(time.Duration(2)*time.Second).Format(time.RFC1123))
1070
1071 reqHeaders := http.Header{}
1072 reqHeaders.Set("cache-control", "min-fresh=1")
1073 if getFreshness(respHeaders, reqHeaders) != fresh {
1074 t.Fatal("freshness isn't fresh")
1075 }
1076
1077 reqHeaders = http.Header{}
1078 reqHeaders.Set("cache-control", "min-fresh=2")
1079 if getFreshness(respHeaders, reqHeaders) != stale {
1080 t.Fatal("freshness isn't stale")
1081 }
1082 }
1083
1084 func TestEmptyMaxStale(t *testing.T) {
1085 resetTest()
1086 now := time.Now()
1087 respHeaders := http.Header{}
1088 respHeaders.Set("date", now.Format(time.RFC1123))
1089 respHeaders.Set("cache-control", "max-age=20")
1090
1091 reqHeaders := http.Header{}
1092 reqHeaders.Set("cache-control", "max-stale")
1093 clock = &fakeClock{elapsed: 10 * time.Second}
1094 if getFreshness(respHeaders, reqHeaders) != fresh {
1095 t.Fatal("freshness isn't fresh")
1096 }
1097
1098 clock = &fakeClock{elapsed: 60 * time.Second}
1099 if getFreshness(respHeaders, reqHeaders) != fresh {
1100 t.Fatal("freshness isn't fresh")
1101 }
1102 }
1103
1104 func TestMaxStaleValue(t *testing.T) {
1105 resetTest()
1106 now := time.Now()
1107 respHeaders := http.Header{}
1108 respHeaders.Set("date", now.Format(time.RFC1123))
1109 respHeaders.Set("cache-control", "max-age=10")
1110
1111 reqHeaders := http.Header{}
1112 reqHeaders.Set("cache-control", "max-stale=20")
1113 clock = &fakeClock{elapsed: 5 * time.Second}
1114 if getFreshness(respHeaders, reqHeaders) != fresh {
1115 t.Fatal("freshness isn't fresh")
1116 }
1117
1118 clock = &fakeClock{elapsed: 15 * time.Second}
1119 if getFreshness(respHeaders, reqHeaders) != fresh {
1120 t.Fatal("freshness isn't fresh")
1121 }
1122
1123 clock = &fakeClock{elapsed: 30 * time.Second}
1124 if getFreshness(respHeaders, reqHeaders) != stale {
1125 t.Fatal("freshness isn't stale")
1126 }
1127 }
1128
1129 func containsHeader(headers []string, header string) bool {
1130 for _, v := range headers {
1131 if http.CanonicalHeaderKey(v) == http.CanonicalHeaderKey(header) {
1132 return true
1133 }
1134 }
1135 return false
1136 }
1137
1138 func TestGetEndToEndHeaders(t *testing.T) {
1139 resetTest()
1140 var (
1141 headers http.Header
1142 end2end []string
1143 )
1144
1145 headers = http.Header{}
1146 headers.Set("content-type", "text/html")
1147 headers.Set("te", "deflate")
1148
1149 end2end = getEndToEndHeaders(headers)
1150 if !containsHeader(end2end, "content-type") {
1151 t.Fatal(`doesn't contain "content-type" header`)
1152 }
1153 if containsHeader(end2end, "te") {
1154 t.Fatal(`doesn't contain "te" header`)
1155 }
1156
1157 headers = http.Header{}
1158 headers.Set("connection", "content-type")
1159 headers.Set("content-type", "text/csv")
1160 headers.Set("te", "deflate")
1161 end2end = getEndToEndHeaders(headers)
1162 if containsHeader(end2end, "connection") {
1163 t.Fatal(`doesn't contain "connection" header`)
1164 }
1165 if containsHeader(end2end, "content-type") {
1166 t.Fatal(`doesn't contain "content-type" header`)
1167 }
1168 if containsHeader(end2end, "te") {
1169 t.Fatal(`doesn't contain "te" header`)
1170 }
1171
1172 headers = http.Header{}
1173 end2end = getEndToEndHeaders(headers)
1174 if len(end2end) != 0 {
1175 t.Fatal(`non-zero end2end headers`)
1176 }
1177
1178 headers = http.Header{}
1179 headers.Set("connection", "content-type")
1180 end2end = getEndToEndHeaders(headers)
1181 if len(end2end) != 0 {
1182 t.Fatal(`non-zero end2end headers`)
1183 }
1184 }
1185
1186 type transportMock struct {
1187 response *http.Response
1188 err error
1189 }
1190
1191 func (t transportMock) RoundTrip(req *http.Request) (resp *http.Response, err error) {
1192 return t.response, t.err
1193 }
1194
1195 func TestStaleIfErrorRequest(t *testing.T) {
1196 resetTest()
1197 now := time.Now()
1198 tmock := transportMock{
1199 response: &http.Response{
1200 Status: http.StatusText(http.StatusOK),
1201 StatusCode: http.StatusOK,
1202 Header: http.Header{
1203 "Date": []string{now.Format(time.RFC1123)},
1204 "Cache-Control": []string{"no-cache"},
1205 },
1206 Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1207 },
1208 err: nil,
1209 }
1210 tp := NewMemoryCacheTransport()
1211 tp.Transport = &tmock
1212
1213
1214 r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1215 r.Header.Set("Cache-Control", "stale-if-error")
1216 resp, err := tp.RoundTrip(r)
1217 if err != nil {
1218 t.Fatal(err)
1219 }
1220 if resp == nil {
1221 t.Fatal("resp is nil")
1222 }
1223 _, err = ioutil.ReadAll(resp.Body)
1224 if err != nil {
1225 t.Fatal(err)
1226 }
1227
1228
1229 tmock.response = nil
1230 tmock.err = errors.New("some error")
1231 resp, err = tp.RoundTrip(r)
1232 if err != nil {
1233 t.Fatal(err)
1234 }
1235 if resp == nil {
1236 t.Fatal("resp is nil")
1237 }
1238 }
1239
1240 func TestStaleIfErrorRequestLifetime(t *testing.T) {
1241 resetTest()
1242 now := time.Now()
1243 tmock := transportMock{
1244 response: &http.Response{
1245 Status: http.StatusText(http.StatusOK),
1246 StatusCode: http.StatusOK,
1247 Header: http.Header{
1248 "Date": []string{now.Format(time.RFC1123)},
1249 "Cache-Control": []string{"no-cache"},
1250 },
1251 Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1252 },
1253 err: nil,
1254 }
1255 tp := NewMemoryCacheTransport()
1256 tp.Transport = &tmock
1257
1258
1259 r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1260 r.Header.Set("Cache-Control", "stale-if-error=100")
1261 resp, err := tp.RoundTrip(r)
1262 if err != nil {
1263 t.Fatal(err)
1264 }
1265 if resp == nil {
1266 t.Fatal("resp is nil")
1267 }
1268 _, err = ioutil.ReadAll(resp.Body)
1269 if err != nil {
1270 t.Fatal(err)
1271 }
1272
1273
1274 tmock.response = nil
1275 tmock.err = errors.New("some error")
1276 resp, err = tp.RoundTrip(r)
1277 if err != nil {
1278 t.Fatal(err)
1279 }
1280 if resp == nil {
1281 t.Fatal("resp is nil")
1282 }
1283
1284
1285 tmock.response = &http.Response{StatusCode: http.StatusInternalServerError}
1286 tmock.err = nil
1287 resp, err = tp.RoundTrip(r)
1288 if err != nil {
1289 t.Fatal(err)
1290 }
1291 if resp == nil {
1292 t.Fatal("resp is nil")
1293 }
1294
1295
1296 clock = &fakeClock{elapsed: 200 * time.Second}
1297 _, err = tp.RoundTrip(r)
1298 if err != tmock.err {
1299 t.Fatalf("got err %v, want %v", err, tmock.err)
1300 }
1301 }
1302
1303 func TestStaleIfErrorResponse(t *testing.T) {
1304 resetTest()
1305 now := time.Now()
1306 tmock := transportMock{
1307 response: &http.Response{
1308 Status: http.StatusText(http.StatusOK),
1309 StatusCode: http.StatusOK,
1310 Header: http.Header{
1311 "Date": []string{now.Format(time.RFC1123)},
1312 "Cache-Control": []string{"no-cache, stale-if-error"},
1313 },
1314 Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1315 },
1316 err: nil,
1317 }
1318 tp := NewMemoryCacheTransport()
1319 tp.Transport = &tmock
1320
1321
1322 r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1323 resp, err := tp.RoundTrip(r)
1324 if err != nil {
1325 t.Fatal(err)
1326 }
1327 if resp == nil {
1328 t.Fatal("resp is nil")
1329 }
1330 _, err = ioutil.ReadAll(resp.Body)
1331 if err != nil {
1332 t.Fatal(err)
1333 }
1334
1335
1336 tmock.response = nil
1337 tmock.err = errors.New("some error")
1338 resp, err = tp.RoundTrip(r)
1339 if err != nil {
1340 t.Fatal(err)
1341 }
1342 if resp == nil {
1343 t.Fatal("resp is nil")
1344 }
1345 }
1346
1347 func TestStaleIfErrorResponseLifetime(t *testing.T) {
1348 resetTest()
1349 now := time.Now()
1350 tmock := transportMock{
1351 response: &http.Response{
1352 Status: http.StatusText(http.StatusOK),
1353 StatusCode: http.StatusOK,
1354 Header: http.Header{
1355 "Date": []string{now.Format(time.RFC1123)},
1356 "Cache-Control": []string{"no-cache, stale-if-error=100"},
1357 },
1358 Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1359 },
1360 err: nil,
1361 }
1362 tp := NewMemoryCacheTransport()
1363 tp.Transport = &tmock
1364
1365
1366 r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1367 resp, err := tp.RoundTrip(r)
1368 if err != nil {
1369 t.Fatal(err)
1370 }
1371 if resp == nil {
1372 t.Fatal("resp is nil")
1373 }
1374 _, err = ioutil.ReadAll(resp.Body)
1375 if err != nil {
1376 t.Fatal(err)
1377 }
1378
1379
1380 tmock.response = nil
1381 tmock.err = errors.New("some error")
1382 resp, err = tp.RoundTrip(r)
1383 if err != nil {
1384 t.Fatal(err)
1385 }
1386 if resp == nil {
1387 t.Fatal("resp is nil")
1388 }
1389
1390
1391 clock = &fakeClock{elapsed: 200 * time.Second}
1392 _, err = tp.RoundTrip(r)
1393 if err != tmock.err {
1394 t.Fatalf("got err %v, want %v", err, tmock.err)
1395 }
1396 }
1397
1398
1399
1400
1401 func TestStaleIfErrorKeepsStatus(t *testing.T) {
1402 resetTest()
1403 now := time.Now()
1404 tmock := transportMock{
1405 response: &http.Response{
1406 Status: http.StatusText(http.StatusNotFound),
1407 StatusCode: http.StatusNotFound,
1408 Header: http.Header{
1409 "Date": []string{now.Format(time.RFC1123)},
1410 "Cache-Control": []string{"no-cache"},
1411 },
1412 Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1413 },
1414 err: nil,
1415 }
1416 tp := NewMemoryCacheTransport()
1417 tp.Transport = &tmock
1418
1419
1420 r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1421 r.Header.Set("Cache-Control", "stale-if-error")
1422 resp, err := tp.RoundTrip(r)
1423 if err != nil {
1424 t.Fatal(err)
1425 }
1426 if resp == nil {
1427 t.Fatal("resp is nil")
1428 }
1429 _, err = ioutil.ReadAll(resp.Body)
1430 if err != nil {
1431 t.Fatal(err)
1432 }
1433
1434
1435 tmock.response = nil
1436 tmock.err = errors.New("some error")
1437 resp, err = tp.RoundTrip(r)
1438 if err != nil {
1439 t.Fatal(err)
1440 }
1441 if resp == nil {
1442 t.Fatal("resp is nil")
1443 }
1444 if resp.StatusCode != http.StatusNotFound {
1445 t.Fatalf("Status wasn't 404: %d", resp.StatusCode)
1446 }
1447 }
1448
1449
1450
1451
1452
1453
1454 func TestClientTimeout(t *testing.T) {
1455 if testing.Short() {
1456 t.Skip("skipping timeout test in short mode")
1457 }
1458 resetTest()
1459 client := &http.Client{
1460 Transport: NewMemoryCacheTransport(),
1461 Timeout: time.Second,
1462 }
1463 started := time.Now()
1464 resp, err := client.Get(s.server.URL + "/3seconds")
1465 taken := time.Since(started)
1466 if err == nil {
1467 t.Error("got nil error, want timeout error")
1468 }
1469 if resp != nil {
1470 t.Error("got non-nil resp, want nil resp")
1471 }
1472 if taken >= 2*time.Second {
1473 t.Error("client.Do took 2+ seconds, want < 2 seconds")
1474 }
1475 }
1476
View as plain text