Mercurial > hg > nginx-tests
view proxy_upgrade.t @ 1619:436d0ffc2ea3
Tests: correctly shutdown ssl for reproducible session reuse tests.
Previously, session reuse tests in stream_ssl_certificate.t were prone
to testing errors, since the client doesn't write any application data
before closing a connection, which is done so to pass tests on win32.
In this case, the server may happened to get an unexpected eof meaning
that it will abandon that session. This is specific to stream testing
pattern, changes to ssl_certificate.t are applied too for consistency.
This is also specific to SSL_R_UNEXPECTED_EOF_WHILE_READING, which is
implemented in OpenSSL 3.0.0.
author | Sergey Kandaurov <pluknet@nginx.com> |
---|---|
date | Mon, 23 Nov 2020 22:46:06 +0000 |
parents | 1603f2bad385 |
children |
line wrap: on
line source
#!/usr/bin/perl # (C) Maxim Dounin # Tests for http proxy upgrade support. # In contrast to proxy_websocket.t, this test doesn't try to use binary # WebSocket protocol, but uses simple plain text protocol instead. ############################################################################### use warnings; use strict; use Test::More; use IO::Poll; use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; use Test::Nginx; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http proxy ssi/) ->write_file_expand('nginx.conf', <<'EOF')->plan(31); %%TEST_GLOBALS%% daemon off; events { } http { %%TEST_GLOBALS_HTTP%% log_format test "$bytes_sent $body_bytes_sent $sent_http_connection"; access_log %%TESTDIR%%/cc.log test; server { listen 127.0.0.1:8080; server_name localhost; location / { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_read_timeout 2s; send_timeout 2s; } location /ssi.html { ssi on; } } } EOF my $d = $t->testdir(); $t->write_file('ssi.html', '<!--#include virtual="/upgrade" --> SEE-THIS'); $t->run_daemon(\&upgrade_fake_daemon); $t->run(); $t->waitforsocket('127.0.0.1:' . port(8081)) or die "Can't start test backend"; ############################################################################### # establish connection my @r; my $s = upgrade_connect(); ok($s, "handshake"); SKIP: { skip "handshake failed", 22 unless $s; # send a frame upgrade_write($s, 'foo'); is(upgrade_read($s), 'bar', "upgrade response"); # send some big frame upgrade_write($s, 'foo' x 16384); like(upgrade_read($s), qr/^(bar){16384}$/, "upgrade big response"); # send multiple frames for my $i (1 .. 10) { upgrade_write($s, ('foo' x 16384) . $i, continue => 1); upgrade_write($s, 'bazz' . $i, continue => $i != 10); } for my $i (1 .. 10) { like(upgrade_read($s), qr/^(bar){16384}\d+$/, "upgrade $i"); is(upgrade_read($s), 'bazz' . $i, "upgrade small $i"); } } push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed'; undef $s; # establish connection with some pipelined data # and make sure they are correctly passed upstream $s = upgrade_connect(message => "foo"); ok($s, "handshake pipelined"); SKIP: { skip "handshake failed", 2 unless $s; is(upgrade_read($s), "bar", "response pipelined"); upgrade_write($s, "foo"); is(upgrade_read($s), "bar", "next to pipelined"); } push @r, $s ? ${*$s}->{_upgrade_private}->{r} : 'failed'; undef $s; # connection should not be upgraded unless upgrade was actually # requested and allowed by configuration $s = upgrade_connect(noheader => 1); ok(!$s, "handshake noupgrade"); # connection upgrade in subrequests shouldn't cause a segfault $s = upgrade_connect(uri => '/ssi.html'); ok(!$s, "handshake in subrequests"); # bytes sent on upgraded connection # verify with 1) data actually read by client, 2) expected data from backend $t->stop(); open my $f, '<', "$d/cc.log" or die "Can't open cc.log: $!"; is($f->getline(), shift (@r) . " 540793 upgrade\n", 'log - bytes'); is($f->getline(), shift (@r) . " 22 upgrade\n", 'log - bytes pipelined'); like($f->getline(), qr/\d+ 0 /, 'log - bytes noupgrade'); ############################################################################### sub upgrade_connect { my (%opts) = @_; my $s = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => '127.0.0.1:' . port(8080), ) or die "Can't connect to nginx: $!\n"; # send request, $h->to_string my $uri = $opts{uri} || '/'; my $buf = "GET $uri HTTP/1.1" . CRLF . "Host: localhost" . CRLF . ($opts{noheader} ? '' : "Upgrade: foo" . CRLF) . "Connection: Upgrade" . CRLF . CRLF; $buf .= $opts{message} . CRLF . 'FIN' if defined $opts{message}; local $SIG{PIPE} = 'IGNORE'; log_out($buf); $s->syswrite($buf); # read response my $got = ''; $buf = ''; while (1) { $buf = upgrade_getline($s); last unless defined $buf and length $buf; log_in($buf); $got .= $buf; last if $got =~ /\x0d?\x0a\x0d?\x0a$/; } # parse server response return if $got !~ m!HTTP/1.1 101!; # make sure next line is "handshaked" $buf = upgrade_read($s); return if !defined $buf or $buf ne 'handshaked'; return $s; } sub upgrade_getline { my ($s) = @_; my ($h, $buf); ${*$s}->{_upgrade_private} ||= { b => '', r => 0 }; $h = ${*$s}->{_upgrade_private}; if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) { $h->{b} = $2; return $1; } $s->blocking(0); while (IO::Select->new($s)->can_read(3)) { my $n = $s->sysread($buf, 1024); last unless $n; $h->{b} .= $buf; $h->{r} += $n; if ($h->{b} =~ /^(.*?\x0a)(.*)/ms) { $h->{b} = $2; return $1; } }; } sub upgrade_write { my ($s, $message, %extra) = @_; $message = $message . CRLF; $message = $message . 'FIN' unless $extra{continue}; local $SIG{PIPE} = 'IGNORE'; $s->blocking(0); while (IO::Select->new($s)->can_write(1.5)) { my $n = $s->syswrite($message); last unless $n; $message = substr($message, $n); last unless length $message; } if (length $message) { $s->close(); } } sub upgrade_read { my ($s) = @_; my $m = upgrade_getline($s); $m =~ s/\x0d?\x0a// if defined $m; log_in($m); return $m; } ############################################################################### sub upgrade_fake_daemon { my $server = IO::Socket::INET->new( Proto => 'tcp', LocalAddr => '127.0.0.1:' . port(8081), Listen => 5, Reuse => 1 ) or die "Can't create listening socket: $!\n"; while (my $client = $server->accept()) { upgrade_handle_client($client); } } sub upgrade_handle_client { my ($client) = @_; $client->autoflush(1); $client->blocking(0); my $poll = IO::Poll->new; my $handshake = 1; my $unfinished = ''; my $buffer = ''; my $n; log2c("(new connection $client)"); while (1) { $poll->mask($client => ($buffer ? POLLIN|POLLOUT : POLLIN)); my $p = $poll->poll(0.5); log2c("(poll $p)"); foreach my $reader ($poll->handles(POLLIN)) { $n = $client->sysread(my $chunk, 65536); return unless $n; log2i($chunk); if ($handshake) { $buffer .= $chunk; next unless $buffer =~ /\x0d?\x0a\x0d?\x0a$/; log2c("(handshake done)"); $handshake = 0; $buffer = 'HTTP/1.1 101 Switching' . CRLF . 'Upgrade: foo' . CRLF . 'Connection: Upgrade' . CRLF . CRLF . 'handshaked' . CRLF; log2o($buffer); next; } $unfinished .= $chunk; if ($unfinished =~ m/\x0d?\x0aFIN\z/) { $unfinished =~ s/FIN\z//; $unfinished =~ s/foo/bar/g; log2o($unfinished); $buffer .= $unfinished; $unfinished = ''; } } foreach my $writer ($poll->handles(POLLOUT)) { next unless length $buffer; $n = $writer->syswrite($buffer); substr $buffer, 0, $n, ''; } } } sub log2i { Test::Nginx::log_core('|| <<', @_); } sub log2o { Test::Nginx::log_core('|| >>', @_); } sub log2c { Test::Nginx::log_core('||', @_); } ###############################################################################