Mercurial > hg > nginx-tests
comparison ssl_proxy_upgrade.t @ 585:5bb19f7448b5
Tests: Upgrade handling tests with http ssl module.
author | Sergey Kandaurov <pluknet@nginx.com> |
---|---|
date | Wed, 20 May 2015 14:44:14 +0300 |
parents | |
children | 53b7c3c33a79 |
comparison
equal
deleted
inserted
replaced
584:7d6db4ac6ab0 | 585:5bb19f7448b5 |
---|---|
1 #!/usr/bin/perl | |
2 | |
3 # (C) Maxim Dounin | |
4 # (C) Sergey Kandaurov | |
5 # (C) Nginx, Inc. | |
6 | |
7 # Tests for http proxy upgrade support with http ssl module. | |
8 # In contrast to proxy_websocket.t, this test doesn't try to use binary | |
9 # WebSocket protocol, but uses simple plain text protocol instead. | |
10 | |
11 ############################################################################### | |
12 | |
13 use warnings; | |
14 use strict; | |
15 | |
16 use Test::More; | |
17 | |
18 use IO::Poll; | |
19 use IO::Select; | |
20 use Socket qw/ CRLF /; | |
21 | |
22 BEGIN { use FindBin; chdir($FindBin::Bin); } | |
23 | |
24 use lib 'lib'; | |
25 use Test::Nginx; | |
26 | |
27 ############################################################################### | |
28 | |
29 select STDERR; $| = 1; | |
30 select STDOUT; $| = 1; | |
31 | |
32 eval { require IO::Socket::SSL; }; | |
33 plan(skip_all => 'IO::Socket::SSL not installed') if $@; | |
34 eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; | |
35 plan(skip_all => 'IO::Socket::SSL too old') if $@; | |
36 | |
37 my $t = Test::Nginx->new()->has(qw/http proxy http_ssl/)->has_daemon('openssl') | |
38 ->write_file_expand('nginx.conf', <<'EOF')->plan(30); | |
39 | |
40 %%TEST_GLOBALS%% | |
41 | |
42 daemon off; | |
43 | |
44 events { | |
45 } | |
46 | |
47 http { | |
48 %%TEST_GLOBALS_HTTP%% | |
49 | |
50 log_format test "$bytes_sent $body_bytes_sent"; | |
51 access_log %%TESTDIR%%/cc.log test; | |
52 | |
53 server { | |
54 listen 127.0.0.1:8080 ssl; | |
55 server_name localhost; | |
56 | |
57 ssl_certificate_key localhost.key; | |
58 ssl_certificate localhost.crt; | |
59 | |
60 location / { | |
61 proxy_pass http://127.0.0.1:8081; | |
62 proxy_http_version 1.1; | |
63 proxy_set_header Upgrade $http_upgrade; | |
64 proxy_set_header Connection "Upgrade"; | |
65 proxy_read_timeout 2s; | |
66 send_timeout 2s; | |
67 } | |
68 } | |
69 } | |
70 | |
71 EOF | |
72 | |
73 $t->write_file('openssl.conf', <<EOF); | |
74 [ req ] | |
75 default_bits = 2048 | |
76 encrypt_key = no | |
77 distinguished_name = req_distinguished_name | |
78 [ req_distinguished_name ] | |
79 EOF | |
80 | |
81 my $d = $t->testdir(); | |
82 | |
83 foreach my $name ('localhost') { | |
84 system('openssl req -x509 -new ' | |
85 . "-config '$d/openssl.conf' -subj '/CN=$name/' " | |
86 . "-out '$d/$name.crt' -keyout '$d/$name.key' " | |
87 . ">>$d/openssl.out 2>&1") == 0 | |
88 or die "Can't create certificate for $name: $!\n"; | |
89 } | |
90 | |
91 $t->run_daemon(\&upgrade_fake_daemon); | |
92 $t->run(); | |
93 | |
94 $t->waitforsocket('127.0.0.1:8081') | |
95 or die "Can't start test backend"; | |
96 | |
97 ############################################################################### | |
98 | |
99 # establish connection | |
100 | |
101 my @r; | |
102 my $s = upgrade_connect(); | |
103 ok($s, "handshake"); | |
104 | |
105 SKIP: { | |
106 skip "handshake failed", 22 unless $s; | |
107 | |
108 # send a frame | |
109 | |
110 upgrade_write($s, 'foo'); | |
111 is(upgrade_read($s), 'bar', "upgrade response"); | |
112 | |
113 # send some big frame | |
114 | |
115 upgrade_write($s, 'foo' x 16384); | |
116 like(upgrade_read($s), qr/^(bar){16384}$/, "upgrade big response"); | |
117 | |
118 # send multiple frames | |
119 | |
120 for my $i (1 .. 10) { | |
121 upgrade_write($s, ('foo' x 16384) . $i); | |
122 upgrade_write($s, 'bazz' . $i); | |
123 } | |
124 | |
125 for my $i (1 .. 10) { | |
126 like(upgrade_read($s), qr/^(bar){16384}\d+$/, "upgrade $i"); | |
127 is(upgrade_read($s), 'bazz' . $i, "upgrade small $i"); | |
128 } | |
129 } | |
130 | |
131 push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed'; | |
132 undef $s; | |
133 | |
134 # establish connection with some pipelined data | |
135 # and make sure they are correctly passed upstream | |
136 | |
137 $s = upgrade_connect(message => "foo"); | |
138 ok($s, "handshake pipelined"); | |
139 | |
140 SKIP: { | |
141 skip "handshake failed", 2 unless $s; | |
142 | |
143 is(upgrade_read($s), "bar", "response pipelined"); | |
144 | |
145 upgrade_write($s, "foo"); | |
146 is(upgrade_read($s), "bar", "next to pipelined"); | |
147 } | |
148 | |
149 push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed'; | |
150 undef $s; | |
151 | |
152 # connection should not be upgraded unless upgrade was actually | |
153 # requested and allowed by configuration | |
154 | |
155 $s = upgrade_connect(noheader => 1); | |
156 ok(!$s, "handshake noupgrade"); | |
157 | |
158 # bytes sent on upgraded connection, fixed in c2f309fb7ad2 (1.7.11) | |
159 # verify with 1) data actually read by client, 2) expected data from backend | |
160 | |
161 $t->stop(); | |
162 | |
163 open my $f, '<', "$d/cc.log" or die "Can't open cc.log: $!"; | |
164 | |
165 is($f->getline(), shift (@r) . " 540793\n", 'log - bytes'); | |
166 is($f->getline(), shift (@r) . " 22\n", 'log - bytes pipelined'); | |
167 is($f->getline(), "0 0\n", 'log - bytes noupgrade'); | |
168 | |
169 ############################################################################### | |
170 | |
171 sub upgrade_connect { | |
172 my (%opts) = @_; | |
173 | |
174 my $s = IO::Socket::SSL->new( | |
175 Proto => 'tcp', | |
176 PeerAddr => '127.0.0.1:8080', | |
177 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), | |
178 ) | |
179 or die "Can't connect to nginx: $!\n"; | |
180 | |
181 # send request, $h->to_string | |
182 | |
183 my $buf = "GET / HTTP/1.1" . CRLF | |
184 . "Host: localhost" . CRLF | |
185 . ($opts{noheader} ? '' : "Upgrade: foo" . CRLF) | |
186 . "Connection: Upgrade" . CRLF . CRLF; | |
187 | |
188 $buf .= $opts{message} . CRLF if defined $opts{message}; | |
189 | |
190 local $SIG{PIPE} = 'IGNORE'; | |
191 | |
192 log_out($buf); | |
193 $s->syswrite($buf); | |
194 | |
195 # read response | |
196 | |
197 my $got = ''; | |
198 $buf = ''; | |
199 | |
200 while (1) { | |
201 $buf = upgrade_getline($s); | |
202 last unless defined $buf and length $buf; | |
203 log_in($buf); | |
204 $got .= $buf; | |
205 last if $got =~ /\x0d?\x0a\x0d?\x0a$/; | |
206 } | |
207 | |
208 # parse server response | |
209 | |
210 return if $got !~ m!HTTP/1.1 101!; | |
211 | |
212 # make sure next line is "handshaked" | |
213 | |
214 $buf = upgrade_read($s); | |
215 | |
216 return if !defined $buf or $buf ne 'handshaked'; | |
217 return $s; | |
218 } | |
219 | |
220 sub upgrade_getline { | |
221 my ($s) = @_; | |
222 my ($h, $buf, $line); | |
223 | |
224 ${*$s}->{_upgrade_private} ||= { b => '', r => 0 }; | |
225 $h = ${*$s}->{_upgrade_private}; | |
226 | |
227 if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) { | |
228 $h->{b} = $2; | |
229 return $1; | |
230 } | |
231 | |
232 $s->blocking(0); | |
233 while (IO::Select->new($s)->can_read(1.5)) { | |
234 my $n = $s->sysread($buf, 1024); | |
235 last unless $n; | |
236 | |
237 $h->{b} .= $buf; | |
238 $h->{r} += $n; | |
239 | |
240 if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) { | |
241 $h->{b} = $2; | |
242 return $1; | |
243 } | |
244 }; | |
245 } | |
246 | |
247 sub upgrade_write { | |
248 my ($s, $message) = @_; | |
249 | |
250 $message = $message . CRLF; | |
251 | |
252 local $SIG{PIPE} = 'IGNORE'; | |
253 | |
254 $s->blocking(0); | |
255 while (IO::Select->new($s)->can_write(1.5)) { | |
256 my $n = $s->syswrite($message); | |
257 last unless $n; | |
258 $message = substr($message, $n); | |
259 last unless length $message; | |
260 } | |
261 | |
262 if (length $message) { | |
263 $s->close(); | |
264 } | |
265 } | |
266 | |
267 sub upgrade_read { | |
268 my ($s) = @_; | |
269 my $m = upgrade_getline($s); | |
270 $m =~ s/\x0d?\x0a// if defined $m; | |
271 log_in($m); | |
272 return $m; | |
273 } | |
274 | |
275 ############################################################################### | |
276 | |
277 sub upgrade_fake_daemon { | |
278 my $server = IO::Socket::INET->new( | |
279 Proto => 'tcp', | |
280 LocalAddr => '127.0.0.1:8081', | |
281 Listen => 5, | |
282 Reuse => 1 | |
283 ) | |
284 or die "Can't create listening socket: $!\n"; | |
285 | |
286 while (my $client = $server->accept()) { | |
287 upgrade_handle_client($client); | |
288 } | |
289 } | |
290 | |
291 sub upgrade_handle_client { | |
292 my ($client) = @_; | |
293 | |
294 $client->autoflush(1); | |
295 $client->blocking(0); | |
296 | |
297 my $poll = IO::Poll->new; | |
298 | |
299 my $handshake = 1; | |
300 my $unfinished = ''; | |
301 my $buffer = ''; | |
302 my $n; | |
303 | |
304 log2c("(new connection $client)"); | |
305 | |
306 while (1) { | |
307 $poll->mask($client => ($buffer ? POLLIN|POLLOUT : POLLIN)); | |
308 my $p = $poll->poll(0.5); | |
309 log2c("(poll $p)"); | |
310 | |
311 foreach my $reader ($poll->handles(POLLIN)) { | |
312 $n = $client->sysread(my $chunk, 65536); | |
313 return unless $n; | |
314 | |
315 log2i($chunk); | |
316 | |
317 if ($handshake) { | |
318 $buffer .= $chunk; | |
319 next unless $buffer =~ /\x0d?\x0a\x0d?\x0a$/; | |
320 | |
321 log2c("(handshake done)"); | |
322 | |
323 $handshake = 0; | |
324 $buffer = 'HTTP/1.1 101 Switching' . CRLF | |
325 . 'Upgrade: foo' . CRLF | |
326 . 'Connection: Upgrade' . CRLF . CRLF | |
327 . 'handshaked' . CRLF; | |
328 | |
329 log2o($buffer); | |
330 | |
331 next; | |
332 } | |
333 | |
334 $unfinished .= $chunk; | |
335 | |
336 if ($unfinished =~ m/\x0d?\x0a\z/) { | |
337 $unfinished =~ s/foo/bar/g; | |
338 log2o($unfinished); | |
339 $buffer .= $unfinished; | |
340 $unfinished = ''; | |
341 } | |
342 } | |
343 | |
344 foreach my $writer ($poll->handles(POLLOUT)) { | |
345 next unless length $buffer; | |
346 $n = $writer->syswrite($buffer); | |
347 substr $buffer, 0, $n, ''; | |
348 } | |
349 } | |
350 } | |
351 | |
352 sub log2i { Test::Nginx::log_core('|| <<', @_); } | |
353 sub log2o { Test::Nginx::log_core('|| >>', @_); } | |
354 sub log2c { Test::Nginx::log_core('||', @_); } | |
355 | |
356 ############################################################################### |