Plack-Middleware-MockProxyFrontend

 view release on metacpan or  search on metacpan

lib/Plack/Middleware/MockProxyFrontend.pm  view on Meta::CPAN


sub call {
	my $self = shift;
	my $env = shift;

	my ( $scheme, $auth, $path, $query, $client_fh );

	if ( 'CONNECT' eq $env->{'REQUEST_METHOD'} ) {
		$client_fh = $env->{'psgix.io'}
			or return [ 405, [], ['CONNECT is not supported'] ];
		$auth = $env->{'REQUEST_URI'};
		$scheme = 'https';
	}
	else {
		( $scheme, $auth, $path, $query ) = URI::Split::uri_split $env->{'REQUEST_URI'};
		return [ 400, [], ['Not a proxy request'] ] if not $scheme;
		return [ 400, [], ['Non-HTTP(S) requests are unsupported'] ] if $scheme !~ /\Ahttps?\z/i;
	}

	my ( $host, $port ) = ( lc $auth ) =~ m{^(?:.+\@)?(.+?)(?::(\d+))?$};
	$port //= 'https' eq lc $scheme ? 443 : 80;

	my $acceptor = $self->host_acceptor;
	return [ 403, [], ['Refused by MockProxyFrontend'] ]
		if $acceptor and not grep $acceptor->( $host ), $host;

	$client_fh
		? sub {
			# lie to the client that we have connected to the destination
			my $writer = shift->( [ 200, [] ] );

			# client starts SSL handshake only after hearing that the connection succeeded
			my $conn = IO::Socket::SSL->new_from_fd(
				fileno $client_fh,
				SSL_server    => 1,
				SSL_reuse_ctx => $self->_ssl_context,
			);

			# the client thinks it is establishing an SSL connection with the destination
			# if this fails, that failure is already communicated at the SSL layer
			# the proxy server only sees opaque traffic, it has no idea what happened
			# all it knows is that the connection has ended, so the request is simply over
			$writer->close, return if not $conn;

			# now act as the destination
			$self->http_server->handle_connection( {
				'psgi.url_scheme' => $scheme,
				SERVER_NAME       => $host,
				SERVER_PORT       => $port,
				SCRIPT_NAME       => '',
				'psgix.io'        => $conn,
				# pass-through
				REMOTE_ADDR    => $env->{'REMOTE_ADDR'},
				REMOTE_PORT    => $env->{'REMOTE_PORT'},
				'psgi.errors'  => $env->{'psgi.errors'},
				'psgi.version' => $env->{'psgi.version'},
				# constants
				'psgi.run_once'        => Plack::Util::TRUE,
				'psgi.multithread'     => Plack::Util::FALSE,
				'psgi.multiprocess'    => Plack::Util::FALSE,
				'psgi.streaming'       => Plack::Util::TRUE,
				'psgi.nonblocking'     => Plack::Util::FALSE,
				'psgix.input.buffered' => Plack::Util::TRUE,
			}, $conn, $self->app );

			$conn->close;
			$writer->close;
		}
		: $self->app->( {
			%$env,
			'psgi.url_scheme' => $scheme,
			HTTP_HOST         => $host,
			SERVER_PORT       => $port,
			REQUEST_URI       => ( join '?', $path, $query // () ),
			PATH_INFO         => $path =~ s!%([0-9]{2})!chr hex $1!rge,
		} );
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Plack::Middleware::MockProxyFrontend - virtualhost-aware PSGI app developer tool

=head1 SYNOPSIS

 # in app.psgi
 use Plack::Builder;
 
 builder {
     enable 'MockProxyFrontend',
         SSL_key_file  => 'key.pem',
         SSL_cert_file => 'cert.pem';
     $app;
 };

=head1 DESCRIPTION

This middleware implements the HTTP proxy protocolE<hellip> without the proxy:
it passes every request down to the wrapped PSGI application. Your application
becomes the browser's entire internet: no matter which address you navigate to,
the response comes from the wrapped PSGI application.

This is useful in the development of PSGI applications that do virtual hosting,
i.e. dispatching on hostname. Instead of testing your application by going to
C<http://localhost:5000/>, you go to C<https://example.com/> (or whatever your
site is). Your application will see a request for C<https://example.com/>, not
C<http://localhost:5000/>, e.g. when your framework generates absolute links.
And then when the page loads, the browser will think it is showing you the real
C<https://example.com/>, e.g. in the address bar.

The way this works is that instead of typing C<http://localhost:5000/> into the
browser's address bar to test your app (or wherever your development server is
listening), you put C<localhost:5000> as the HTTP/HTTPS proxy in the browser's
configuration. Then I<any> URL you navigate to will end up being served by your



( run in 0.766 second using v1.01-cache-2.11-cpan-39bf76dae61 )