-
Notifications
You must be signed in to change notification settings - Fork 1
/
cache.xml
1059 lines (1017 loc) · 35.7 KB
/
cache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V5.0//EN"
"/usr/share/xml/docbook/schema/dtd/5.0/docbook.dtd" [
<!ENTITY article.author.xml SYSTEM "../common/article.author.xml">
<!ENTITY book.info.legalnotice.xml SYSTEM "../common/book.info.legalnotice.xml">
<!ENTITY book.info.abstract.xml SYSTEM "../common/book.info.abstract.xml">
]>
<article xml:base="http://netkiller.github.io/journal/cache.html"
xmlns="http://docbook.org/ns/docbook" xml:lang="zh-cn">
<articleinfo>
<title>实操 Web Cache</title>
<subtitle>http://netkiller.github.io/journal/cache.html</subtitle>
&article.author.xml;
<pubdate>$Date$</pubdate>
<releaseinfo>$Id$</releaseinfo>
&book.info.legalnotice.xml;
<abstract>
<para>写这篇文章的原因,是我看到网上很多谈这类的文章,多是人云亦云,不求实事,误导读者。</para>
<para>下面文中我会一个一个做实验,并展示给你,说明为什么会这样。只有自己亲自尝试才能拿出有说服力的真凭实据。</para>
<para>2014-03-12 首次发布</para>
<para>2015-08-27 修改,增加特殊数据缓存</para>
</abstract>
&book.info.abstract.xml;
<keywordset>
<keyword>If-Modified-Since, Last-Modified</keyword>
<keyword>ETag, If-None-Match</keyword>
<keyword></keyword>
</keywordset>
</articleinfo>
<section>
<title>测试环境</title>
<para>CentOS 6.5</para>
<para>Nginx安装脚本 https://github.com/oscm/shell/blob/master/nginx/nginx.sh</para>
<para>php安装脚本 https://github.com/oscm/shell/blob/master/php/5.5.8.sh</para>
</section>
<section id="modified">
<title>文件修改日期 If-Modified-Since / Last-Modified</title>
<para>If-Modified-Since 小于 Last-Modified 返回 HTTP/1.1 200 OK, 否则返回 HTTP/1.0 304 Not Modified</para>
<para>每次浏览器请求文件会携带 If-Modified-Since 头,将当前时间发送给服务器,与服务器的Last-Modified时间对对比,如果大于Last-Modified时间,返回HTTP/1.0 304 Not Modified不会重新打开文件,否则重新读取文件并返回内容</para>
<section>
<title>静态文件</title>
<para>nginx/1.0.15 静态文件自动产生 Last-Modified 头</para>
<screen>
# nginx -v
nginx version: nginx/1.0.15
# curl -I http://192.168.6.9/index.html
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 07:36:03 GMT
Content-Type: text/html
Content-Length: 6
Last-Modified: Thu, 27 Feb 2014 07:29:50 GMT
Connection: keep-alive
Accept-Ranges: bytes
</screen>
<para>图片文件</para>
<screen>
# curl -I http://192.168.6.9/image.png
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 07:37:18 GMT
Content-Type: image/png
Content-Length: 41516
Last-Modified: Thu, 27 Feb 2014 07:36:59 GMT
Connection: keep-alive
Accept-Ranges: bytes
</screen>
<tip>
<para>疑问 nginx/1.4.5 默认没有 Last-Modified</para>
<screen>
# nginx -v
nginx version: nginx/1.4.5
# curl -I http://192.168.2.15/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:13:44 GMT
Content-Type: text/html
Connection: keep-alive
</screen>
<para>经过一番周折最终找到答案 Nginx 如果开启 ssi 会禁用Last-Modified 关闭 ssi 后输出如下</para>
<screen>
# curl -I http://localhost/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:44:29 GMT
Content-Type: text/html
Content-Length: 6
Last-Modified: Wed, 25 Dec 2013 03:18:16 GMT
Connection: keep-alive
ETag: "52ba4e78-6"
Accept-Ranges: bytes
</screen>
<para></para>
</tip>
<para>再测试一次</para>
<screen>
# curl -H "If-Modified-Since: Fir, 28 Feb 2014 07:42:55 GMT" -I http://192.168.2.15/test.html
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:34:54 GMT
Last-Modified: Fri, 28 Feb 2014 01:55:50 GMT
Connection: keep-alive
ETag: "530feca6-8b"
</screen>
<para>测试结果成功返回 HTTP/1.1 304 Not Modified, 但又莫名其妙的出现了 ETag。 这就是Nignx本版差异,非常混乱。</para>
<para>既然出现了ETag我们也顺便测试一下</para>
<screen>
# curl -H 'If-None-Match: "530feca6-8b"' -I http://192.168.2.15/test.html
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:39:18 GMT
Last-Modified: Fri, 28 Feb 2014 01:55:50 GMT
Connection: keep-alive
ETag: "530feca6-8b"
</screen>
<para>也是成功的</para>
<para>测试图片</para>
<screen>
# curl -I http://localhost/logo.jpg
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:59:04 GMT
Content-Type: image/jpeg
Content-Length: 10103
Last-Modified: Fri, 28 Feb 2014 02:56:37 GMT
Connection: keep-alive
ETag: "530ffae5-2777"
Accept-Ranges: bytes
# curl -H 'If-None-Match: "530ffae5-2777"' -I http://localhost/logo.jpg
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 03:03:33 GMT
Last-Modified: Fri, 28 Feb 2014 02:56:37 GMT
Connection: keep-alive
ETag: "530ffae5-2777"
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 12:04:18 GMT" -I http://localhost/logo.jpg
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 03:04:45 GMT
Content-Type: image/jpeg
Content-Length: 10103
Last-Modified: Fri, 28 Feb 2014 02:56:37 GMT
Connection: keep-alive
ETag: "530ffae5-2777"
Accept-Ranges: bytes
</screen>
<para>测试结果,ETag通过测试,If-Modified-Since无论如何也无法返回 304 可能还需要其他的HTTP头,浏览器测试都通过返回 HTTP/1.1 304 Not Modified</para>
<para>现在换成浏览器测试 Chrome Firefox成功, 因为浏览器不会主动发送If-Modified-Since, 浏览器只有发现Last-Modified后,第二次请求才会推送 If-Modified-Since 需要刷新两次页面。</para>
<section>
<title>if_modified_since</title>
<para>在开启ssi的情况下,通过参数 if_modified_since 可以开启 Last-Modified</para>
<screen>
server {
listen 80;
server_name 192.168.2.15;
if_modified_since before;
}
</screen>
<para>测试结果看不到 Last-Modified, 因为 Nginx 的 if_modified_since before;参数只有接收到浏览器发过来的If-Modified-Since头才会发送Last-Modified</para>
<screen>
# curl -I http://192.168.2.15/test.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:39:42 GMT
Content-Type: text/html
Connection: keep-alive
</screen>
<para>最终 if_modified_since before; 数没有起到作用</para>
<para>参数设置为 if_modified_since exact;</para>
<screen>
# curl -I http://192.168.2.15/test.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:45:40 GMT
Content-Type: text/html
Connection: keep-alive
# curl -H 'If-None-Match: "530feca6-8b"' -I http://192.168.2.15/test.html
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:45:44 GMT
Last-Modified: Fri, 28 Feb 2014 01:55:50 GMT
Connection: keep-alive
ETag: "530feca6-8b"
# curl -H "If-Modified-Since: Fir, 28 Feb 2014 07:42:55 GMT" -I http://192.168.2.15/test.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 02:45:50 GMT
Content-Type: text/html
Connection: keep-alive
</screen>
<para>测试失败,浏览器也是实测失败,ETag却成功</para>
</section>
</section>
<section>
<title>通过rewrite伪静态处理</title>
<para>index.php仍然是上面的那个php文件,我们只是做了伪静态</para>
<screen>
<![CDATA[
location / {
root /www;
index index.html index.htm;
rewrite ^/test.html$ /index.php last;
}
]]>
</screen>
<para>现在我们分别通过curl有chrome/firefox进行测试</para>
<screen>
<![CDATA[
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 08:42:55 GMT" -I http://192.168.6.9/test.html
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 08:55:19 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Thu, 26 Feb 2014 08:39:35 GMT
]]>
</screen>
<para>经过测试无论是 curl 还是 chrome/firefox 均无法返回304.</para>
<para>下面是我的分析,仅供参考。用户请求index.html Nginx 会找到该文件读取 mtime 与 If-Modified-Since 匹配,如果If-Modified-Since大于 Last-Modified返回 304否则返回200.</para>
<para>为什么同样操作经过伪静态的test.html就不行呢? 我分析当用户请求test.html Nginx 首先做Rewrite处理,然后跳转到index.php 整个过程nginx 并没有访问实际物理文件test.html也就没有mtime, 所以Nginx 返回200.</para>
<para>如果 Nginx 按预想的返回304,nginx 需要读取程序返回的HTTP头,Nginx 并没有这样的处理逻辑。</para>
</section>
<section>
<title>动态文件</title>
<para>动态文件没有 Last-Modified 头,我们可以伪造一个</para>
<screen>
<![CDATA[
# curl -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 07:57:59 GMT
Content-Type: text/html
Connection: keep-alive
]]>
</screen>
<para>在程序中加入HTTP头推送操作,Last-Modified时间是27号,当前时间是28号,我们要让Last-Modified 小于当前时间才行。</para>
<screen>
<![CDATA[
# cat index.php
<?php
header('Last-Modified: Thu, 27 Feb 2014 08:39:35 GMT' );
//header('Last-Modified: ' .gmdate('D, d M Y H:i:s') . ' GMT' );
?>
Hello
]]>
</screen>
<para>现在你将看到 Last-Modified</para>
<screen>
<![CDATA[
# curl -I http://localhost/modified.php
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:59:28 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 10:04:18 GMT
]]>
</screen>
<note>
<title>注意</title>
<para>虽然我们让动态程序返回了 Last-Modified ,但浏览器不认,经过测试 Chrome / Firefox 均不会承认.php文件,并缓存其内容。</para>
<screen>
<![CDATA[
# curl -I http://localhost/modified.php
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:59:28 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 10:04:18 GMT
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 08:42:55 GMT" -I http://localhost/modified.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Fri, 28 Feb 2014 05:32:30 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Thu, 26 Feb 2014 08:39:35 GMT
]]>
</screen>
<para>Last-Modified 对动态程序来说没有起到实际作用</para>
</note>
<para>Last-Modified是程序产生的,Nginx无法读到,让程序去处理状态返回是可行的,下面我们修改程序如下。</para>
<screen>
<![CDATA[
# cat modified.php
<?php
$mtime = 'Fri, 28 Feb 2014 12:04:18 GMT';
cache($mtime);
function cache($mtime)
{
$http_if_modified_since = null;
if(array_key_exists ('HTTP_IF_MODIFIED_SINCE',$_SERVER)){
$http_if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
}
echo $http_if_modified_since;
if ($http_if_modified_since >= $mtime)
{
header('Last-Modified: '.$mtime, true, 304);
exit;
} else {
header('Last-Modified: ' . $mtime );
}
}
print_r($_SERVER);
echo date("Y-m-d H:i:s");
?>
]]>
</screen>
<para>测试效果</para>
<screen>
<![CDATA[
# curl -I http://localhost/modified.php
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:22:28 GMT
Content-Type: text/html
Connection: keep-alive
]]>
</screen>
<para>伪造一个 If-Modified-Since 日期小于我们指定的日期程序返回HTTP/1.1 200 OK</para>
<screen>
<![CDATA[
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 10:04:18 GMT" -I http://localhost/modified.php
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:22:13 GMT
Content-Type: text/html
Connection: keep-alive
]]>
</screen>
<para>伪造一个 If-Modified-Since 日期大于我们指定的日期程序返回HTTP/1.1 304 Not Modified</para>
<screen>
<![CDATA[
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 20:04:18 GMT" -I http://localhost/modified.php
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 05:21:31 GMT
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 12:04:18 GMT
]]>
</screen>
<para>测试成功,并且在浏览器端也测试成功 HTTP/1.1 304 Not Modified</para>
<para>将modified.php伪静态处理 </para>
<screen>
location / {
root /www;
index index.html index.htm;
rewrite ^/modified.html$ /modified.php last;
}
</screen>
<para>测试</para>
<screen>
# curl -I http://localhost/modified.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 06:21:10 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 10:04:18 GMT
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 12:04:18 GMT" -I http://localhost/modified.html
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 06:21:22 GMT
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 10:04:18 GMT
</screen>
<para>达到预期效果</para>
</section>
</section>
<section id="etag">
<title>ETag / If-None-Match</title>
<para>上面的Last-Modified测试中发现ETag虽然不限制,但是暗中还是可用的:)</para>
<para>etag on; 开启Nginx etag支持,lighttpd 默认开启</para>
<screen>
<![CDATA[
server {
listen 80;
server_name phalcon;
charset utf-8;
access_log /var/log/nginx/host.access.log main;
etag on;
location / {
root /www/phalcon/public;
index index.html index.php;
}
}
]]>
</screen>
<para>检查ETag输出</para>
<screen>
# curl -I http://localhost/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 03:08:28 GMT
Content-Type: text/html
Connection: keep-alive
# curl -I http://phalcon/img/css.png
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 27 Feb 2014 09:20:49 GMT
Content-Type: image/png
Content-Length: 1133
Last-Modified: Fri, 14 Feb 2014 08:05:03 GMT
Connection: keep-alive
ETag: "52fdce2f-46d"
Accept-Ranges: bytes3
</screen>
<para>即使你开启了 ETag Nginx 对 HTML、CSS文件也不做处理。最终在一个外国网站是找到一个nginx-static-etags模块,有兴趣自己尝试,这里就不讲了。</para>
<section>
<title>静态文件</title>
<para>首先查询etag值</para>
<screen>
# curl -I http://phalcon/img/css.png
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 27 Feb 2014 09:25:41 GMT
Content-Type: image/png
Content-Length: 1133
Last-Modified: Fri, 14 Feb 2014 08:05:03 GMT
Connection: keep-alive
ETag: "52fdce2f-46d"
Accept-Ranges: bytes
</screen>
<para>然后向服务器发送If-None-Match HTTP头</para>
<screen>
# curl -H 'If-None-Match: "52fdce2f-46d"' -I http://phalcon/img/css.png
HTTP/1.1 304 Not Modified
Server: nginx
Date: Thu, 27 Feb 2014 09:25:44 GMT
Last-Modified: Fri, 14 Feb 2014 08:05:03 GMT
Connection: keep-alive
ETag: "52fdce2f-46d"
</screen>
<para>这次比较顺利,成功返回HTTP/1.1 304 Not Modified</para>
</section>
<section>
<title>动态程序</title>
<para>默认情况输出如下</para>
<screen>
# curl -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 27 Feb 2014 09:29:13 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
</screen>
<para>测试程序</para>
<screen>
<![CDATA[
<?php
header('Last-Modified: Thu, 26 Feb 2014 08:39:35 GMT' );
header('Etag: "abcdefg"');
#header('Last-Modified: ' .gmdate('D, d M Y H:i:s') . ' GMT' );
?>
Hello
]]>
</screen>
<para>测试效果</para>
<screen>
# curl -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 09:41:06 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Thu, 26 Feb 2014 08:39:35 GMT
Etag: "abcdefg"
[root@centos6 ~]# curl -H 'If-None-Match: "abcdefg"' -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 09:41:42 GMT
Content-Type: text/html
Connection: keep-alive
Last-Modified: Thu, 26 Feb 2014 08:39:35 GMT
Etag: "abcdefg"
</screen>
<para>测试情况与之前的Last-Modified结果一样</para>
<para>动态程序返回Etag真的就没有用了吗?</para>
<para>答案是:非也, 有一个方法可以让动态程序返回的 Etag 也能发挥作用,程序修改如下</para>
<screen>
<![CDATA[
<?php
$etag = md5('http://netkiller.github.io');
cache($etag);
function cache($etag)
{
$http_if_none_match = null;
if(array_key_exists ('HTTP_IF_NONE_MATCH',$_SERVER)){
$http_if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
}
if ($http_if_none_match == $etag)
{
header('Etag: '.$etag, true, 304);
exit;
} else {
header('Etag: '.$etag);
}
}
print_r($_SERVER);
echo date("Y-m-d H:i:s");
?>
]]>
</screen>
<para>首先查看Etag值</para>
<screen>
# curl -I http://192.168.6.9/test.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 10:07:19 GMT
Content-Type: text/html
Connection: keep-alive
Etag: 7467675324d0f7a3e01ce5151848fedb
</screen>
<para>发送If-None-Match头</para>
<screen>
# curl -H 'If-None-Match: 7467675324d0f7a3e01ce5151848fedb' -I http://192.168.6.9/test.php
HTTP/1.1 304 Not Modified
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 10:07:39 GMT
Connection: keep-alive
Etag: 7467675324d0f7a3e01ce5151848fedb
</screen>
<para>达成预计效果,此种方法同样可以用于 Last-Modified,伪静态后效果更好</para>
<para>Etag 值的运算技巧,我习惯上采用URL同时配合伪静态例如</para>
<screen>
$etag = $_SERVER['REQUEST_URI']
</screen>
<para>URL类似 http://www.example.com/news/100/1000.html 一次请求便缓存页面,这样带来一个更新的问题,于是又做了这样的处理</para>
<screen>
http://www.example.com/news/100/1000.1.html
</screen>
<para>.1.是版本号,每次修改后+1操作,.1.没有人格意义rewrite操作是会丢弃这个参数,仅仅是为了始终有新的URL对应内容</para>
</section>
</section>
<section id="expires">
<title>Expires / Cache-Control</title>
<para>前面所讲 Last-Modified 与 Etag 主要用于分辨文件是否修改过, 无法控制页面在浏览器端缓存的时间。Expires / Cache-Control 可以控制缓存的时间段</para>
<para>Expires 是 HTTP/1.0标准,Cache-Control是 HTTP/1.1标准。都能正常工作,HTTP/1.1规范中max-age优先级高于Expires,有些浏览器会联动设置,例如你设置了Cache-Control随之自动生成Expires,仅仅为了兼容。</para>
<section>
<title>静态文件</title>
<para>首先配置nginx设置html与png文件缓存1天</para>
<screen>
location ~ .*\.(html|png)$
{
expires 1d;
}
</screen>
<para>当前情况</para>
<screen>
# curl -I http://192.168.6.9/index.html
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 10:47:08 GMT
Content-Type: text/html
Content-Length: 6
Last-Modified: Thu, 27 Feb 2014 07:29:50 GMT
Connection: keep-alive
Accept-Ranges: bytes
</screen>
<para>重启Nginx后的HTTP协议头多出Expires与Cache-Control</para>
<screen>
# curl -I http://192.168.6.9/index.html
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 10:42:09 GMT
Content-Type: text/html
Content-Length: 3698
Last-Modified: Fri, 26 Apr 2013 20:36:51 GMT
Connection: keep-alive
Expires: Fri, 28 Feb 2014 10:42:09 GMT
Cache-Control: max-age=86400
Accept-Ranges: bytes
</screen>
</section>
<section>
<title>动态文件</title>
<para>默认返回</para>
<screen>
# curl -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 11:45:05 GMT
Content-Type: text/html
Connection: keep-alive
</screen>
<para>index.php 增加 Cache-Control 输出控制</para>
<screen>
<![CDATA[
header('Cache-Control: max-age=259200');
]]>
</screen>
<para>再次查看</para>
<screen>
# curl -I http://192.168.6.9/index.php
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Thu, 27 Feb 2014 11:53:48 GMT
Content-Type: text/html
Connection: keep-alive
Cache-Control: max-age=259200
</screen>
<para>现在使用 Chrome 、Firefox 测试,你会发现始终返回200,并且max-age=259200数值不会改变。</para>
<para>原因是Cache-Control程序输出的,Nginx并不知道,所以Nginx 不会给你返回304</para>
<screen>
<![CDATA[
header('Last-Modified: ' .gmdate('D, d M Y H:i:s') . ' GMT' );
$offset = 60 * 60 * 24;
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $offset) . ' GMT');
$ttl=3600;
header("Cache-Control: max-age=$ttl, must-revalidate");
]]>
</screen>
<para>这种方法不能实现缓存的目的</para>
</section>
</section>
<section id="fastcgi">
<title>FastCGI 缓存相关</title>
<para>我们做个尝试将 expires 1d;加到location ~ \.php$中,看看能不能实现缓存的目的。</para>
<screen>
location ~ \.php$ {
root /www;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www$fastcgi_script_name;
include fastcgi_params;
expires 1d;
}
</screen>
<para>测试程序</para>
<screen>
<![CDATA[
# cat expires.php
<?php
echo date("Y-m-d H:i:s");
?>
]]>
</screen>
<para>测试结果</para>
<screen>
# curl -I http://localhost/expires.php
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 04:39:57 GMT
Content-Type: text/html
Connection: keep-alive
Expires: Sat, 01 Mar 2014 04:39:57 GMT
Cache-Control: max-age=86400
</screen>
<para>虽然推送 Cache-Control: max-age=86400 但是 IE Chrome Firefox 仍不能缓存页面</para>
</section>
<section id="meta">
<title>HTML META 与 Cache</title>
<para>创建一个测试文件如下</para>
<screen>
<![CDATA[
<html>
<head>
<title>Hello</title>
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="expires" content="Fri, 28 Feb 2014 12:04:18 GMT" />
</head>
<body>
Helloworld
</body>
</html>
]]>
</screen>
<para>测试HTML页面</para>
<screen>
<![CDATA[
# curl -i http://localhost/test.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 03:30:45 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
<html>
<head>
<title>Hello</title>
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="expires" content="Fri, 28 Feb 2014 12:04:18 GMT" />
</head>
<body>
Helloworld
</body>
</html>
]]>
</screen>
<para>我们可以看到HTML页面中meta设置缓存对Nginx并不起作用, 很多人会说对浏览器起作用!</para>
<para>这次我测试了 IE11, Chrome, Firefox 发现都无法缓存页面,可能对IE5什么的还有用,我没有环境测试,因为10年前我们在B/S开发经常这样使用</para>
<screen>
<![CDATA[
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
]]>
</screen>
<para>至少在当年IE是认这些Meta的,进入HTML5时代很多都发生了变化,所以不能一概而论</para>
</section>
<section id="gzip">
<title>gzip</title>
<para>defalte 是 Apache httpd 的标准这里只谈gzip</para>
<para>首先创建一个 gzip.html</para>
<screen>
# curl -I http://localhost/gzip.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Mon, 03 Mar 2014 01:49:45 GMT
Content-Type: text/html
Content-Length: 19644
Last-Modified: Mon, 03 Mar 2014 01:49:02 GMT
Connection: keep-alive
ETag: "5313df8e-4cbc"
Accept-Ranges: bytes
</screen>
<para>开启 gzip on;</para>
<screen>
server {
listen 80;
server_name localhost;
#charset utf-8;
#access_log /var/log/nginx/log/host.access.log main;
#etag on;
#ssi on;
gzip on;
</screen>
<para>现在看看效果</para>
<screen>
# curl -I http://localhost/gzip.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Mon, 03 Mar 2014 01:51:56 GMT
Content-Type: text/html
Content-Length: 19644
Last-Modified: Mon, 03 Mar 2014 01:49:02 GMT
Connection: keep-alive
ETag: "5313df8e-4cbc"
Accept-Ranges: bytes
</screen>
<para>并没有什么不同,现在增加HTTP头Accept-Encoding:gzip,defalte看看</para>
<screen>
<![CDATA[
# curl -H Accept-Encoding:gzip,defalte http://localhost/gzip.html
]]>
</screen>
<para>如果你能看到非文本内容(俗称乱码)就表示成功了。输入内容就是gzip压缩后二进制数据,我们使用gunzip可以解压缩</para>
<screen>
# curl -H Accept-Encoding:gzip,defalte http://localhost/gzip.html | gunzip
</screen>
<para>如果能正常看到html输出,表示压缩无误。</para>
<section>
<title>gzip 总结</title>
<para>gzip on; 开启后默认支持 text/html 不能在 gzip_types 再次定义,否则会提示重复MIME类型</para>
<screen>
Starting nginx: nginx: [warn] duplicate MIME type "text/html" in /etc/nginx/conf.d/localhost.conf:16
</screen>
<para>高级配置参考</para>
<screen>
gzip on;
gzip_http_version 1.0;
gzip_types text/plain text/xml text/css application/xml application/xhtml+xml application/rss+xml application/atom_xml application/javascript application/x-javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_disable "Mozilla/4";
gzip_comp_level 6;
gzip_proxied any;
gzip_vary on;
gzip_buffers 4 8k;
gzip_min_length 1000;
</screen>
</section>
</section>
<section id="proxy">
<title>反向代理与缓存</title>
<para>反向代理服务器缓存方式分为:</para>
<para>强制缓存,指定文件,扩展名,URL设置缓存时间</para>
<para>遵循HTTP协议头标准进行缓存</para>
<para>默认配置,只进行代理,不进行缓存</para>
<screen>
server {
listen 80;
server_name 192.168.2.15;
#access_log /var/log/nginx/log/host.access.log main;
location / {
proxy_pass http://localhost:80;
proxy_set_header X-Real-IP $remote_addr;
}
}
</screen>
<para>反向代理会产生两条日志(access_log 写入一个文件中,如果分开写,则会分开写入日志)</para>
<screen>
192.168.2.15 - - [28/Feb/2014:18:09:33 +0800] "HEAD /modified.html HTTP/1.1" 200 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
127.0.0.1 - - [28/Feb/2014:18:09:33 +0800] "HEAD /modified.html HTTP/1.0" 200 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
</screen>
<para>Last-Modified 与 ETag 会透传过去</para>
<screen>
# curl -H "If-Modified-Since: Fri, 28 Feb 2014 12:04:18 GMT" -I http://192.168.2.15/modified.html
HTTP/1.1 304 Not Modified
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 10:17:30 GMT
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 10:04:18 GMT
</screen>
<para>我们可以看到两条日志都返回304</para>
<screen>
192.168.2.15 - - [28/Feb/2014:18:17:30 +0800] "HEAD /modified.html HTTP/1.1" 304 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
127.0.0.1 - - [28/Feb/2014:18:17:30 +0800] "HEAD /modified.html HTTP/1.0" 304 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
</screen>
<para>下面为反向代理增加缓存功能</para>
<screen>
proxy_temp_path /tmp/proxy_temp_dir;
proxy_cache_path /tmp/proxy_cache_dir levels=1:2 keys_zone=nginx_cache:200m inactive=3d max_size=30g;
server {
listen 80;
server_name 192.168.2.15;
location / {
proxy_cache nginx_cache;
proxy_cache_key $host$uri$is_args$args;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_valid 200 10m;
proxy_pass http://localhost;
}
location ~ .*\.(php|jsp|cgi)?$
{
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://backend_server;
}
}
</screen>
<para></para>
<screen>
# curl -I http://192.168.2.15/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 10:57:35 GMT
Content-Type: text/html
Content-Length: 12
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 06:54:45 GMT
ETag: "531032b5-c"
Expires: Sat, 01 Mar 2014 10:57:35 GMT
Cache-Control: max-age=86400
Accept-Ranges: bytes
# curl -I http://192.168.2.15/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 10:57:41 GMT
Content-Type: text/html
Content-Length: 12
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 06:54:45 GMT
ETag: "531032b5-c"
Expires: Sat, 01 Mar 2014 10:57:35 GMT
Cache-Control: max-age=86400
Accept-Ranges: bytes
# curl -I http://192.168.2.15/index.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Fri, 28 Feb 2014 10:57:46 GMT
Content-Type: text/html
Content-Length: 12
Connection: keep-alive
Last-Modified: Fri, 28 Feb 2014 06:54:45 GMT
ETag: "531032b5-c"
Expires: Sat, 01 Mar 2014 10:57:35 GMT
Cache-Control: max-age=86400
Accept-Ranges: bytes
</screen>
<para>上面共请求了3次服务器</para>
<screen>
192.168.2.15 - - [28/Feb/2014:18:57:35 +0800] "HEAD /index.html HTTP/1.1" 200 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
127.0.0.1 - - [28/Feb/2014:18:57:35 +0800] "GET /index.html HTTP/1.0" 200 12 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "192.168.2.15"
192.168.2.15 - - [28/Feb/2014:18:57:41 +0800] "HEAD /index.html HTTP/1.1" 200 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
192.168.2.15 - - [28/Feb/2014:18:57:46 +0800] "HEAD /index.html HTTP/1.1" 200 0 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-"
</screen>
<para>第一次连接192.168.2.15然后转发给127.0.0.1 返回 HTTP/1.1 200 OK</para>
<para>后面两次连接192.168.2.15没有转发给127.0.0.1 直接返回 HTTP/1.1 200 OK</para>
<para>查看缓存目录,我们可以看到生成的缓存文件</para>
<screen>
# find /tmp/proxy_*
/tmp/proxy_cache_dir
/tmp/proxy_cache_dir/1
/tmp/proxy_cache_dir/1/79
/tmp/proxy_cache_dir/1/79/b47a0009c531900de2a15ba80c0e3791
/tmp/proxy_temp_dir
</screen>
<!-- proxy_ignore_headers Cache-Control; -->
<section id="proxy.gzip">
<title>gzip 处理</title>
<para>http://localhost/gzip.html 是支持压缩的,192.168.2.15 proxy_pass http://localhost</para>
<screen>
# curl -H Accept-Encoding:gzip,defalte http://localhost/gzip.html
</screen>
<para>运行后输出乱码</para>
<screen>
# curl -H Accept-Encoding:gzip,defalte http://192.168.2.15/gzip.html
</screen>
<para>现在透过反向代理请求试试,你会发现gzip压缩无效,输出的是HTML,这是怎么回事呢?这是因为反向代理不清楚后面的服务器是否支持gzip,所以一律按照正常html请求。现在我们开启 gzip_vary on; 每次返回数据会携带Vary: Accept-Encoding 头。</para>
<screen>
gzip on;
gzip_vary on;
</screen>
<para>reload nginx 后查看Vary: Accept-Encoding输出</para>
<screen>
# curl -I http://localhost/gzip.html
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Mon, 03 Mar 2014 02:09:16 GMT
Content-Type: text/html
Content-Length: 19644
Last-Modified: Mon, 03 Mar 2014 01:49:02 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: "5313df8e-4cbc"
Accept-Ranges: bytes
</screen>
<para>有 Vary: Accept-Encoding 头,现在再测试一次</para>
<screen>
<![CDATA[
# curl -H "Accept-Encoding: gzip" http://192.168.2.15/gzip.html
<html>
<head>
<title>Hello</title>
]]>
</screen>
<para>测试失败,并没有出现预期效果,于是到网站找答案,中文与英文资料都看个遍,没有解决.</para>
<para>最后只能让反向代理取到数据后再压缩一次,配置开启 gzip on;</para>
<screen>
proxy_temp_path /tmp/proxy_temp_dir;
proxy_cache_path /tmp/proxy_cache_dir levels=1:2 keys_zone=nginx_cache:200m inactive=3d max_size=30g;
server {
listen 80;
server_name 192.168.2.15;
gzip on;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header Accept-Encoding "gzip"; 没有任何效果
proxy_pass http://localhost;
}
}
</screen>
<para>Nginx 反向代理作为代理绰绰有余,如果做缓存服务器,还是使用squid, varnish吧。</para>
</section>
</section>
<section id="data">
<title>特殊数据缓存</title>
<para>缓存并非只能缓存静态内容,HTML,CSS,JS以及图片意外的数据一样可以缓存。</para>
<para>只要处理好HTTP头即可。例如Ajax动态内容缓存,JSON数据缓存。</para>
<section>
<title>json</title>
<para>当用户请求json地址时,我们将 json 数据附加HTTP头(Cache-Control, Expires, ETag),然后返回给用户,用户的设备会遵循HTTP的声明,进行缓存操作。</para>
<screen>
curl -I http://api.example.com/article/json/2/20/0.html