Mercurial > hg > nginx-tests
view proxy_upgrade.t @ 1940:aec72dcee93b
Tests: unconditional QUIC datagram expansion with Initial packets.
Used to get rid of "quic UDP datagram is too small for initial packet" messages.
In future, we may need this to reconsider to allow mocking with custom padding,
but for now suppressing such messages is sufficiently good enough.
author | Sergey Kandaurov <pluknet@nginx.com> |
---|---|
date | Mon, 11 Dec 2023 14:01:49 +0400 |
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('||', @_); } ###############################################################################