package Mojolicious::Plugin::ReverseProxy;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Transaction::HTTP;
use Mojo::UserAgent;
use Mojo::URL;
use Carp qw(croak);

# let's have our own private unadulterated useragent
# instead of using the shared one from app. Who knows
# what all the others are doing to the poor thing.

my $ua = Mojo::UserAgent->new(cookie_jar => Mojo::UserAgent::CookieJar->new(ignore => sub { 1 }));

our $VERSION = '0.706';

my $make_req = sub {
    my $c = shift;
    my $dest_url = Mojo::URL->new(shift);
    my $mount_point = shift;
    my $tx = Mojo::Transaction::HTTP->new( req=> $c->req->clone );
    my $url = $tx->req->url;
    my $req_path = $url->path;
    $url->scheme($dest_url->scheme);
    $url->host($dest_url->host);
    $url->port($dest_url->port);
    $url->path($dest_url->path);
    $url->path->trailing_slash(1);
    if ($mount_point){
        $req_path =~ s[^\Q${mount_point}\E/*][];
        $url->path($req_path);
    }
    $tx->req->headers->header('Host',$url->host_port);
    return $tx;
};

sub register {
    my $self = shift;
    my $app = shift;
    my $conf = shift;
    if ($conf->{helper_name}){
        die "helper_name is no more. In Mojolicious::Plugin::ReverseProxy 0.6 the API changed radically. Please check the docs.";
    }
    my $dest_url = $conf->{destination_url} or die "the destination_url parameter is mandatory";
    my $req_processor = $conf->{req_processor};
    my $res_processor = $conf->{res_processor};
    my $routes = $conf->{routes} || $app->routes;
    my $mount_point = $conf->{mount_point} || '';
    $mount_point =~ s{/$}{};
    my $log = $app->log;

    $routes->any($mount_point.'/*catchall' => { catchall => '' })->to(cb => sub {
        my $c = shift;
        $c->render_later;
        my $tx = $c->$make_req($dest_url,$mount_point);
        $req_processor->($c,$tx->req) if ref $req_processor eq 'CODE';
        # if we call $c->rendered in the preprocessor,
        # we are done ...
        return if $c->stash('mojo.finished');
        $ua->start($tx, sub {
             my ($ua,$tx) = @_;
             my $res = $tx->res;
             $res_processor->($c,$res) if ref $res_processor eq 'CODE';
             $c->tx->res($res);
             $c->rendered;
        });
    });
}

1;

__END__

=head1 Mojolicious::Plugin::ReverseProxy
 
 package ProxyFun;
 use Mojo::Base 'Mojolicious';

 sub startup {
    my $app = shift;

    $app->plugin('Mojolicious::Plugin::ReverseProxy',{
        # mandatory
        destination_url => 'http://www.oetiker.ch',
        # optional
        routes => $app->routes, # default 
        mount_point => '/', # default
        req_processor => sub { 
            my ($c,$req) = @_; 
            # do something to the request object prior
            # to passing it on to the destination_url
            # maybe fix the Origin or Referer headers
            for (qw(Origin Referer)){
                my $value = $req->headers->header($_) or next;
                if ( $value =~ s{http://www.oetiker.ch}{http://localhost:3000} ){
                    $req->headers->header($_,$value);   
                }
            }                
        },
        res_processor => sub {
            my ($c,$res) = @_;
            # do something to the response object prior
            # to passing it on to the client
            # maybe fixing the location header
            # or absolute URLs in the body
            if (my $location = $res->headers->location){
                if ( $location =~ s{http://www.oetiker.ch}{http://localhost:3000} ){
                    $res->headers->location($location); 
                }
            }
            if ($res->headers->content_type =~ m{text/html} and my $body = $res->body){
                if ( $body =~ s{http://www.oetiker.ch}{http://localhost:3000}g){
                    $res->body($body);
                    $res->headers->content_length(length($body));
                }
            }
        },
    }
 }

=head1 DESCRIPTION

The Mojolicious::Plugin::ReverseProxy lets your register a proxy route. The
module is rather mindless in the sense that it does not try to help you with
fixing headers or content to actually work with the proxy, apart from the
C<Host> header.

What makes this Plugin really useful, is that you can supply a
C<req_processor> and a C<res_processor> callback which will act on the
request prior to passing it on to the destination and on the response prior
to returning it to the client respectively.

The plugin takes the following options:

=over

=item destination_url

Where should the proxy connect to

  destination_url => 'http://www.oetiker.ch'

=item routes (defaults to app->routes)

the routes object to use for adding the proxy route

=item mount_point (defaults to /)

under which path should the proxy appear.

=item req_processor

Can be pointed to an anonymous subroutine which is called prior to handing
control over to the user agent.

If you render the page in the C<req_processor callback>, the page will be
returned immediately without calling the C<destination_url>

=item res_processor

Can be pointed to an anonymous subroutine which is called prior to rendering the response.

=head1 AUTHOR

S<Tobias Oetiker, E<lt>tobi@oetiker.chE<gt>>

=head1 COPYRIGHT

Copyright OETIKER+PARTNER AG 2014

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut