Created
October 9, 2025 00:32
-
-
Save xeioex/b7c31c20b31d9aadc299926d43cace12 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/perl | |
| # (C) Dmitry Volyntsev | |
| # (C) Nginx, Inc. | |
| # Tests for http njs module, fetch method with HTTPS through forward proxy. | |
| ############################################################################### | |
| use warnings; | |
| use strict; | |
| use Test::More; | |
| use Socket qw/ CRLF SOCK_STREAM /; | |
| use IO::Select; | |
| 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 http_ssl/) | |
| ->write_file_expand('nginx.conf', <<'EOF'); | |
| %%TEST_GLOBALS%% | |
| daemon off; | |
| events { | |
| } | |
| http { | |
| %%TEST_GLOBALS_HTTP%% | |
| js_import test.js; | |
| server { | |
| listen 127.0.0.1:8080; | |
| server_name localhost; | |
| location /https_via_proxy { | |
| js_fetch_proxy http://127.0.0.1:%%PORT_8082%%; | |
| js_fetch_proxy_auth_basic testuser testpass; | |
| js_content test.https_fetch; | |
| } | |
| location /https_no_proxy { | |
| js_content test.https_fetch; | |
| } | |
| location /https_via_proxy_status { | |
| js_fetch_proxy http://127.0.0.1:%%PORT_8082%%; | |
| js_fetch_proxy_auth_basic testuser testpass; | |
| js_content test.https_fetch_status; | |
| } | |
| } | |
| server { | |
| listen 127.0.0.1:%%PORT_8083%% ssl; | |
| server_name localhost; | |
| ssl_certificate localhost.crt; | |
| ssl_certificate_key localhost.key; | |
| location = /test { | |
| return 200 "ORIGIN:HTTPS:response"; | |
| } | |
| } | |
| } | |
| EOF | |
| my $p2 = port(8082); | |
| my $p3 = port(8083); | |
| $t->write_file('test.js', <<EOF); | |
| async function https_fetch(r) { | |
| try { | |
| let reply = await ngx.fetch('https://127.0.0.1:$p3/test', | |
| {verify: false}); | |
| let body = await reply.text(); | |
| r.return(200, body); | |
| } catch (e) { | |
| r.return(500, e.message); | |
| } | |
| } | |
| async function https_fetch_status(r) { | |
| try { | |
| let reply = await ngx.fetch('https://127.0.0.1:$p3/test', | |
| {verify: false}); | |
| r.return(200, 'STATUS:' + reply.status); | |
| } catch (e) { | |
| r.return(500, e.message); | |
| } | |
| } | |
| export default {https_fetch, https_fetch_status}; | |
| EOF | |
| $t->write_file('openssl.conf', <<EOF); | |
| [ req ] | |
| default_bits = 2048 | |
| encrypt_key = no | |
| distinguished_name = req_distinguished_name | |
| [ req_distinguished_name ] | |
| EOF | |
| my $d = $t->testdir(); | |
| system('openssl req -x509 -new ' | |
| . "-config $d/openssl.conf -subj /CN=localhost/ " | |
| . "-out $d/localhost.crt -keyout $d/localhost.key " | |
| . ">>$d/openssl.out 2>&1") == 0 | |
| or die "Can't create certificate: $!\n"; | |
| $t->try_run('no njs.fetch')->plan(6); | |
| $t->run_daemon(\&https_proxy_daemon, $p2, $p3); | |
| $t->waitforsocket('127.0.0.1:' . $p2); | |
| ############################################################################### | |
| my $resp = http_get('/https_via_proxy'); | |
| like($resp, qr/PROXY:method=CONNECT/, 'proxy received CONNECT method'); | |
| like($resp, qr/PROXY:uri=127\.0\.0\.1:$p3/, | |
| 'proxy received host:port in CONNECT'); | |
| like($resp, qr/ORIGIN:HTTPS:response/, 'origin HTTPS response through proxy'); | |
| $resp = http_get('/https_no_proxy'); | |
| like($resp, qr/ORIGIN:HTTPS:response/, 'origin HTTPS response without proxy'); | |
| $resp = http_get('/https_via_proxy_status'); | |
| like($resp, qr/STATUS:200/, 'HTTPS request status 200'); | |
| ############################################################################### | |
| sub https_proxy_daemon { | |
| my ($port, $origin_port) = @_; | |
| my $server = IO::Socket::INET->new( | |
| Proto => 'tcp', | |
| LocalAddr => "127.0.0.1:$port", | |
| Listen => 5, | |
| Reuse => 1 | |
| ) or die "Can't create listening socket: $!\n"; | |
| local $SIG{PIPE} = 'IGNORE'; | |
| while (my $client = $server->accept()) { | |
| $client->autoflush(1); | |
| my $headers = ''; | |
| my $uri = ''; | |
| my $method = ''; | |
| my $proxy_auth = ''; | |
| my $first_line = 1; | |
| while (<$client>) { | |
| $headers .= $_; | |
| last if (/^\x0d?\x0a?$/); | |
| if ($first_line) { | |
| $method = $1 if /^(\S+)\s+/; | |
| $uri = $1 if /^\S+\s+(\S+)\s+HTTP/; | |
| $first_line = 0; | |
| } | |
| $proxy_auth = $1 if /^Proxy-Authorization:\s*(.+?)\s*$/i; | |
| } | |
| if ($method eq 'CONNECT') { | |
| if ($proxy_auth =~ /^Basic\s+dGVzdHVzZXI6dGVzdHBhc3M=$/i) { | |
| print $client "HTTP/1.1 200 Connection established" . CRLF . | |
| CRLF; | |
| my $origin = IO::Socket::INET->new( | |
| PeerAddr => "127.0.0.1:$origin_port", | |
| Proto => 'tcp', | |
| Type => SOCK_STREAM | |
| ) or do { | |
| close $client; | |
| next; | |
| }; | |
| my $sel = IO::Select->new($client, $origin); | |
| while (1) { | |
| my @ready = $sel->can_read(3.0); | |
| last unless @ready; | |
| foreach my $sock (@ready) { | |
| my $buf; | |
| my $n = sysread($sock, $buf, 4096); | |
| if (!defined($n) || $n == 0) { | |
| $sel->remove($client, $origin); | |
| close $origin; | |
| close $client; | |
| last; | |
| } | |
| if ($sock == $client) { | |
| syswrite($origin, $buf); | |
| } else { | |
| syswrite($client, $buf); | |
| } | |
| } | |
| } | |
| } else { | |
| print $client | |
| "HTTP/1.1 407 Proxy Authentication Required" . CRLF . | |
| "Proxy-Authenticate: Basic realm=\"proxy\"" . CRLF . | |
| "Content-Length: 0" . CRLF . | |
| "Connection: close" . CRLF . CRLF; | |
| } | |
| } | |
| close $client; | |
| } | |
| } | |
| ############################################################################### |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment