471
|
1 #!/usr/bin/perl
|
|
2
|
|
3 # (C) Maxim Dounin
|
|
4
|
|
5 # Tests for http proxy cache, the Vary header.
|
|
6
|
|
7 ###############################################################################
|
|
8
|
|
9 use warnings;
|
|
10 use strict;
|
|
11
|
|
12 use Test::More;
|
|
13
|
|
14 BEGIN { use FindBin; chdir($FindBin::Bin); }
|
|
15
|
|
16 use lib 'lib';
|
|
17 use Test::Nginx;
|
|
18
|
|
19 ###############################################################################
|
|
20
|
|
21 select STDERR; $| = 1;
|
|
22 select STDOUT; $| = 1;
|
|
23
|
|
24 plan(skip_all => 'win32') if $^O eq 'MSWin32';
|
|
25
|
|
26 my $t = Test::Nginx->new()->has(qw/http proxy cache gzip rewrite/)
|
|
27 ->write_file_expand('nginx.conf', <<'EOF');
|
|
28
|
|
29 %%TEST_GLOBALS%%
|
|
30
|
|
31 daemon off;
|
|
32
|
|
33 events {
|
|
34 }
|
|
35
|
|
36 http {
|
|
37 %%TEST_GLOBALS_HTTP%%
|
|
38
|
|
39 proxy_cache_path %%TESTDIR%%/cache keys_zone=one:1m inactive=5s;
|
|
40 proxy_cache_key $uri;
|
|
41
|
|
42 server {
|
|
43 listen 127.0.0.1:8080;
|
|
44 server_name localhost;
|
|
45
|
|
46 add_header X-Cache-Status $upstream_cache_status;
|
|
47
|
|
48 location / {
|
|
49 proxy_pass http://127.0.0.1:8081/;
|
|
50 proxy_cache one;
|
|
51 }
|
|
52
|
|
53 location /replace/ {
|
|
54 proxy_pass http://127.0.0.1:8081/;
|
|
55 proxy_cache one;
|
|
56 }
|
|
57
|
|
58 location /revalidate/ {
|
|
59 proxy_pass http://127.0.0.1:8081/;
|
|
60 proxy_cache one;
|
|
61 proxy_cache_revalidate on;
|
|
62 }
|
|
63
|
|
64 location /ignore/ {
|
|
65 proxy_pass http://127.0.0.1:8081/;
|
|
66 proxy_cache one;
|
|
67 proxy_ignore_headers Vary;
|
|
68 }
|
|
69 }
|
|
70
|
|
71 server {
|
|
72 listen 127.0.0.1:8081;
|
|
73 server_name localhost;
|
|
74
|
|
75 gzip on;
|
|
76 gzip_min_length 0;
|
|
77 gzip_http_version 1.0;
|
|
78 gzip_vary on;
|
|
79
|
|
80 expires 2s;
|
|
81
|
|
82 location / {
|
|
83 if ($args = "novary") {
|
|
84 return 200 "the only variant\n";
|
|
85 }
|
|
86 }
|
|
87
|
|
88 location /asterisk {
|
|
89 gzip off;
|
|
90 add_header Vary "*";
|
|
91 }
|
|
92
|
|
93 location /complex {
|
|
94 gzip off;
|
|
95 add_header Vary ",, Accept-encoding , ,";
|
|
96 }
|
|
97 }
|
|
98 }
|
|
99
|
|
100 EOF
|
|
101
|
|
102 $t->write_file('index.html', 'SEE-THIS');
|
|
103 $t->write_file('asterisk', 'SEE-THIS');
|
|
104 $t->write_file('complex', 'SEE-THIS');
|
|
105
|
|
106 $t->try_run('no proxy_ignore_headers Vary')->plan(40);
|
|
107
|
|
108 ###############################################################################
|
|
109
|
|
110 local $TODO = 'not yet';
|
|
111
|
|
112 like(get('/', 'gzip'), qr/MISS/ms, 'first request');
|
|
113 like(get('/', 'gzip'), qr/HIT/ms, 'vary match cached');
|
|
114 like(get('/', 'deflate'), qr/MISS/ms, 'vary mismatch');
|
|
115 like(get('/', 'deflate'), qr/HIT/ms, 'vary mismatch cached');
|
|
116 like(get('/', 'foo'), qr/MISS/ms, 'vary mismatch 2');
|
|
117 like(get('/', 'foo'), qr/HIT/ms, 'vary mismatch 2 cached');
|
|
118 like(get('/', 'gzip'), qr/HIT/ms, 'multiple representations cached');
|
|
119
|
|
120 SKIP: {
|
|
121 skip 'long tests', 6 unless $ENV{TEST_NGINX_UNSAFE};
|
|
122
|
|
123 # make sure all variants are properly expire
|
|
124 # and removed after inactive timeout
|
|
125
|
|
126 sleep(3);
|
|
127
|
|
128 like(get('/', 'gzip'), qr/EXPIRED/ms, 'first expired');
|
|
129 like(get('/', 'deflate'), qr/EXPIRED/ms, 'second variant expired');
|
|
130
|
|
131 like(get('/', 'gzip'), qr/HIT/ms, 'first cached after expire');
|
|
132 like(get('/', 'deflate'), qr/HIT/ms, 'second cached after expire');
|
|
133
|
|
134 sleep(12);
|
|
135
|
|
136 like(get('/', 'gzip'), qr/MISS/ms, 'first inactive removed');
|
|
137 like(get('/', 'deflate'), qr/MISS/ms, 'second variant removed');
|
|
138
|
|
139 }
|
|
140
|
|
141 SKIP: {
|
|
142 skip 'long tests', 6 unless $ENV{TEST_NGINX_UNSAFE};
|
|
143
|
|
144 # check if the variant which was loaded first will be properly
|
|
145 # removed if it's not requested (but another variant is requested
|
|
146 # at the same time)
|
|
147
|
|
148 sleep(3);
|
|
149 like(get('/', 'deflate'), qr/EXPIRED/ms, 'bump1');
|
|
150 sleep(3);
|
|
151 like(get('/', 'deflate'), qr/EXPIRED/ms, 'bump2');
|
|
152 sleep(3);
|
|
153 like(get('/', 'deflate'), qr/EXPIRED/ms, 'bump3');
|
|
154 sleep(3);
|
|
155 like(get('/', 'deflate'), qr/EXPIRED/ms, 'bump4');
|
|
156
|
|
157 TODO: {
|
|
158 local $TODO = 'not yet';
|
|
159
|
|
160 like(head('/', 'gzip'), qr/MISS/ms, 'first not bumped by second requests');
|
|
161
|
|
162 }
|
|
163
|
|
164 like(head('/', 'deflate'), qr/HIT/ms, 'second variant cached');
|
|
165
|
|
166 }
|
|
167
|
|
168 # if a response without Vary is returned to replace previously returned
|
|
169 # responses with Vary, make sure it is then used in all cases
|
|
170
|
|
171 like(get('/replace/', 'gzip'), qr/MISS/, 'replace first');
|
|
172 like(get('/replace/', 'deflate'), qr/MISS/, 'replace second');
|
|
173
|
|
174 sleep(3);
|
|
175
|
|
176 like(get('/replace/?novary', 'deflate'), qr/EXPIRED/, 'replace novary');
|
|
177 like(get('/replace/?zztest', 'gzip'), qr/HIT/, 'all replaced');
|
|
178
|
|
179 # make sure revalidation of variants works fine
|
|
180
|
|
181 like(get('/revalidate/', 'gzip'), qr/MISS/, 'revalidate first');
|
|
182 like(get('/revalidate/', 'deflate'), qr/MISS/, 'revalidate second');
|
|
183
|
|
184 sleep(3);
|
|
185
|
|
186 like(get('/revalidate/', 'gzip'), qr/REVALIDATED/, 'revalidated first');
|
|
187 like(get('/revalidate/', 'deflate'), qr/REVALIDATED/, 'revalidated second');
|
|
188 like(get('/revalidate/', 'gzip'), qr/HIT/, 'revalidate first after');
|
|
189 like(get('/revalidate/', 'deflate'), qr/HIT/, 'revalidate second after');
|
|
190
|
|
191 # if the Vary header is ignored, cached version can be returned
|
|
192 # regardless of request headers
|
|
193
|
|
194 like(get('/ignore/', 'gzip'), qr/MISS/ms, 'another request');
|
|
195 like(get('/ignore/', 'deflate'), qr/HIT/ms, 'vary ignored');
|
|
196
|
|
197 # check parsing of Vary with multiple headers listed
|
|
198
|
|
199 like(get('/complex', 'gzip'), qr/MISS/ms, 'vary complex first');
|
|
200 like(get('/complex', 'deflate'), qr/MISS/ms, 'vary complex second');
|
|
201 like(get('/complex', 'gzip'), qr/HIT/ms, 'vary complex first cached');
|
|
202 like(get('/complex', 'deflate'), qr/HIT/ms, 'vary complex second cached');
|
|
203
|
|
204 # From RFC 7231, "7.1.4. Vary",
|
|
205 # http://tools.ietf.org/html/rfc7231#section-7.1.4:
|
|
206 #
|
|
207 # A Vary field value of "*" signals that anything about the request
|
|
208 # might play a role in selecting the response representation, possibly
|
|
209 # including elements outside the message syntax (e.g., the client's
|
|
210 # network address). A recipient will not be able to determine whether
|
|
211 # this response is appropriate for a later request without forwarding
|
|
212 # the request to the origin server.
|
|
213 #
|
|
214 # In theory, If-None-Match can be used to check if the representation
|
|
215 # present in the cache is appropriate. This seems to be only possible
|
|
216 # with strong entity tags though, as representation with different
|
|
217 # content condings may share the same weak entity tag.
|
|
218
|
|
219 like(get('/asterisk', 'gzip'), qr/MISS/ms, 'vary asterisk first');
|
|
220 like(get('/asterisk', 'gzip'), qr/MISS/ms, 'vary asterisk second');
|
|
221
|
|
222 # From RFC 7234, "4.1. Calculating Secondary Keys with Vary",
|
|
223 # http://tools.ietf.org/html/rfc7234#section-4.1:
|
|
224 #
|
|
225 # The selecting header fields from two requests are defined to match if
|
|
226 # and only if those in the first request can be transformed to those in
|
|
227 # the second request by applying any of the following:
|
|
228 #
|
|
229 # o adding or removing whitespace, where allowed in the header field's
|
|
230 # syntax
|
|
231 #
|
|
232 # o combining multiple header fields with the same field name (see
|
|
233 # Section 3.2 of [RFC7230])
|
|
234 #
|
|
235 # o normalizing both header field values in a way that is known to
|
|
236 # have identical semantics, according to the header field's
|
|
237 # specification (e.g., reordering field values when order is not
|
|
238 # significant; case-normalization, where values are defined to be
|
|
239 # case-insensitive)
|
|
240 #
|
|
241 # No normalization is currently implemented.
|
|
242
|
|
243 like(get('/', 'foo, bar'), qr/MISS/ms, 'normalize first');
|
|
244
|
|
245 TODO: {
|
|
246 local $TODO = 'not yet';
|
|
247
|
|
248 like(get('/', 'foo,bar'), qr/HIT/ms, 'normalize whitespace');
|
|
249 like(get('/', 'bar,foo'), qr/HIT/ms, 'normalize order');
|
|
250
|
|
251 }
|
|
252
|
|
253 ###############################################################################
|
|
254
|
|
255 sub get {
|
|
256 my ($url, $extra) = @_;
|
|
257 return http(<<EOF);
|
|
258 GET $url HTTP/1.1
|
|
259 Host: localhost
|
|
260 Connection: close
|
|
261 Accept-Encoding: $extra
|
|
262
|
|
263 EOF
|
|
264 }
|
|
265
|
|
266 ###############################################################################
|