# ======================================================================
#
# Copyright (C) 2000-2001 Paul Kulchenko (paulclinger@yahoo.com)
# SOAP::Lite is free software; you can redistribute it
# and/or modify it under the same terms as Perl itself.
#
# $Id: SOAP::Transport::HTTP.pm,v 0.46 2001/01/31 16:30:24 $
#
# ======================================================================

package SOAP::Transport::HTTPX;

use strict;
use vars qw($VERSION);
$VERSION = '0.46';

use SOAP::Transport::HTTP;

# ======================================================================

package SOAP::Transport::HTTPX::Client;

use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::Client); 

use SOAP::Lite;

my(%redirect, %mpost);


sub send_receive {
  my($self, %parameters) = @_;
  my($envelope, $endpoint, $action) = 
    @parameters{qw(envelope endpoint action)};

  $endpoint ||= $self->endpoint;

  $endpoint =~ s|httpx://|http://|;
  my $method = 'POST';
  my $resp;

  my $redir_count = 0;
  while (1) { 

    # check cache for redirect
    $endpoint = $redirect{$endpoint} if exists $redirect{$endpoint};
    # check cache for M-POST
    $method = 'M-POST' if exists $mpost{$endpoint};

    my $req = HTTP::Request->new($method => $endpoint, HTTP::Headers->new, $envelope);
    $req->proxy_authorization_basic($ENV{'HTTP_proxy_user'}, $ENV{'HTTP_proxy_pass'})
      if ($ENV{'HTTP_proxy_user'} && $ENV{'HTTP_proxy_pass'}); # by Murray Nesbitt 

    if ($method eq 'M-POST') {
      my $prefix = sprintf '%04d', int(rand(1000));
      $req->header(Man => qq!"$SOAP::Constants::NS_ENV"; ns=$prefix!);
      $req->header("$prefix-SOAPAction" => $action);  
    } else {
      $req->header(SOAPAction => $action);
    }
    $req->content_type('text/xml');
    $req->content_length(length($envelope));

    SOAP::Trace::transport($req);
    SOAP::Trace::debug($req->as_string);
    
    $self->SUPER::env_proxy if $ENV{'HTTP_proxy'};

    $resp = $self->SUPER::request($req);

    SOAP::Trace::transport($resp);
    SOAP::Trace::debug($resp->as_string);

    # 100 OK, continue to read?
    if (($resp->code == 510 || $resp->code == 501) && 
        $method ne 'M-POST') { 
      $mpost{$endpoint} = 1;
    } elsif ( $resp->code == 301 && $redir_count++ < 10 ) {
	  my $head = $resp->headers;
	  if ( $head->{soapaction} ) {
		my ($oldclass) = $action =~ m/(.*)#/;
		my $newaction = $action = $head->{soapaction};
		my ($newclass) = $newaction =~ m/(.*)#/;
		$envelope =~ s/$oldclass/$newclass/;
	  }
	  $endpoint = $head->{location} if ( $head->{location} );
    } else {
      last;
    }
  }


  $redirect{$endpoint} = $resp->request->url
    if $resp->previous && $resp->previous->is_redirect;

  $self->code($resp->code);
  $self->message($resp->message);
  $self->is_success($resp->is_success);
  $self->status($resp->status_line);

  join '', $resp->content_type =~ m!^multipart/! ? ($resp->headers_as_string, "\n") : '',
           $resp->content;
}

# ======================================================================

package SOAP::Transport::HTTPX::Server;

use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::Server);

# ======================================================================

package SOAP::Transport::HTTPX::CGI;

use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::CGI);

# ======================================================================

package SOAP::Transport::HTTPX::Daemon;

use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::Daemon);

# ======================================================================

package SOAP::Transport::HTTPX::Apache;

use vars qw(@ISA %Redirect $hc);
@ISA = qw(SOAP::Transport::HTTP::Apache);

my ( $forward, $redirect ) = ( 0, 1 );

sub handler { 
  my $self = shift->new; 
  my $r = shift || Apache->request; 

  $self->request(HTTP::Request->new( 
     $r->method => $r->uri,
     HTTP::Headers->new($r->headers_in),
     do { my $buf; $r->read($buf, $r->header_in('Content-length')); $buf; } 
  ));

  my $action = my $orig_action = $self->request->header('SOAPAction');

  $action    =~ s|"||g;
  $action    =~ s|(\w+:)(/+)?||;
  my $scheme = $1.$2;

  my ( $class, $method ) = $action =~ m|(.*?)#(.*)|;
  $class  =~ s|/|::|g;

  print STDERR " $scheme | $class | $method \n";
  unless ( %Redirect ) {
	foreach ( $self->dispatch_to() ) {
		push (@INC, $_ ) if m|/|;
	}
	eval "use Redirect"; die if $@;
  }

  if ( exists($Redirect{$class}) ) {
    my $re_proxy = $Redirect{$class}->[0];
    my $re_class = $Redirect{$class}->[1];

    $re_class = "urn:/$re_class" if ( $re_class && $re_class !~ /^\w+:/ );
    my $new_action = ( $re_class ) ? "\"$re_class#$method\"" : $orig_action;

    if ( $Redirect{$class}->[2] == $redirect ) {
      $r->header_out( 'SOAPAction' => $new_action );
      $r->header_out( 'Location'   => $re_proxy  );
      $r->status(301);
      $r->send_http_header;
      return 301;
    }
    elsif ( exists($Redirect{$class}) && $Redirect{$class}->[2] == $forward ) {
      my $content = $self->request->content;
      $content =~ s/$scheme$class/$re_class/ if ( $re_class );

      $hc ||= SOAP::Transport::HTTP::Client->new;

      my $response = $hc->send_receive (
        envelope => $content,
        endpoint => $re_proxy,
        action   => $new_action,
      );

      $response =~ s/$re_class/$class/ if ( $re_class );

      if ($hc->is_success) {
         $r->header_out('Content-Length' => length ($response) );
         $r->send_http_header($hc->{response}->content_type);
         $r->print($response);
      } else {
         $r->err_header_out('Content-length' => length ($response) );
         $r->content_type($hc->{response}->content_type);
         $r->custom_response($hc->code, $response);
      }
      return $hc->code;
    }
  }


  $self->SUPER::handle;


  if ($self->response->is_success) {
    $r->header_out('Content-length' => $self->response->content_length);
    $r->send_http_header($self->response->content_type);
    $r->print($self->response->content);
  } else {
    $r->err_header_out('Content-length' => $self->response->content_length);
    $r->content_type($self->response->content_type);
    $r->custom_response($self->response->code, $self->response->content);
  }

  $self->response->code;

}

# ======================================================================

1;

__END__

=head1 NAME

SOAP::Transport::HTTPX - Server/Client side HTTP Smart Proxy for SOAP::Lite

=head1 SYNOPSIS

 use SOAP::Lite +autodispatch =>
    uri      => 'urn:',
    proxy    => 'httpx://my.smart.server/soap',
    on_fault => sub { my($soap, $res) = @_; 
       die ref $res ? $res->faultdetail : $soap->transport->status, "\n";
    }
 ;


 print Hello->SOAP::echo ( 'Paul' ), "\n";


=head1 DESCRIPTION

The SmartProxy package is intended for use in a multi-server setting where
one or more servers may not be directly accessible to client side scripts.
The SmartProxy package makes request redirection and forwarding on a per class
basis easy.  Client scripts need not know which server is appropriate for a
specific request and may make all requests from a single master server which
can be relied upon to redirect clients to the server currently fulfilling a
given request.  The relieves a maintenance burden on the client side.  The
server may also redirect clients to a new class name or fully qualified
action URI (methods and arguments are assumed to remain constant however).


=head1 DEPENDENCIES

 The SOAP-Lite package.

=head1 SEE ALSO

 See SOAP::Transport::HTTP

=head1 COPYRIGHT

Copyright (C) 2000-2001 Paul Kulchenko. All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

 Daniel Yacob (yacob@rcn.com)
 Paul Kulchenko (paulclinger@yahoo.com)

=cut
