package SOAP::WSDL::Server::Mod_Perl2; use strict; use warnings; use base qw(SOAP::WSDL::Server); use Scalar::Util qw(blessed); use HTTP::Request (); use Apache2::RequestIO (); # $r->read() use Apache2::RequestRec (); # $r->headers_in use Apache2::RequestUtil(); # $r->dir_config() use APR::Table (); # $r->headers_in->get() use Apache2::Log (); # $r->log use Apache2::Const -compile => qw( OK SERVER_ERROR HTTP_LENGTH_REQUIRED ); use version; our $VERSION = qv('2.00.07'); my %LOADED_OF = (); sub handler { my $r = shift; my $rlog = $r->log(); # # Set up section; import requested modules, throwing errors if we're # unable to do so. For maximum performance, this should be re-worked # to use perl-based Apache directives rather than dir_config(), since # the former happens at startup time and the latter at request time. # # dispatch_to my $dispatch_to = $r->dir_config('dispatch_to'); if (! $dispatch_to) { $rlog->error("No 'dispatch_to' variable set in httpd.conf"); return Apache2::Const::SERVER_ERROR; } if (! exists $LOADED_OF{$dispatch_to}) { eval "require $dispatch_to"; if ($@) { $rlog->error("Failed to require [$dispatch_to]: $@"); return Apache2::Const::SERVER_ERROR; } $LOADED_OF{$dispatch_to} = undef; } # # SOAP service my $soap_service_package = $r->dir_config('soap_service'); if (! $soap_service_package) { $rlog->error("No 'soap_service' variable set in httpd.conf"); return Apache2::Const::SERVER_ERROR; } if (! exists $LOADED_OF{$soap_service_package}) { eval "require $soap_service_package"; if ($@) { $rlog->error("Failed to require [$soap_service_package]: $@"); return Apache2::Const::SERVER_ERROR; } $LOADED_OF{$soap_service_package} = undef; } # # transport_class (optional) my $transport_class = $r->dir_config('transport_class'); if ($transport_class) { eval "require $transport_class"; if ($@) { $rlog->error("Failed to require [$transport_class]: $@"); return Apache2::Const::SERVER_ERROR; } } else { # # if no transport class was specified, use this package's handle() # method $transport_class = __PACKAGE__; } # # instantiate SOAP server object my $server = $soap_service_package->new({ dispatch_to => $dispatch_to, # methods transport_class => $transport_class, # handle() call }); my $response_msg = $server->handle($r); if ($response_msg =~ /^\d{3}$/) { # # a 3-digit number is presumed to be an HTTP return status; since # we got this and not a SOAP response, it's presumed to be an # error; pass it back to the client as-is $rlog->error("Dispatcher returned HTTP $response_msg"); return $response_msg; } if ($response_msg) { $r->content_type('text/xml; charset="utf-8"'); $r->print($response_msg); return Apache2::Const::OK; } else { $rlog->error("No response returned from dispatcher"); return Apache2::Const::SERVER_ERROR; } } sub handle { my ($self, $r) = @_; my $rlog = $r->log(); my $length = $r->headers_in->get('content-length'); if (! $length) { $rlog->error("No content-length provided"); # TODO maybe throw instead of returning a HTTP code? # ... it's an exception, anyway... return Apache2::Const::HTTP_LENGTH_REQUIRED; } # read may return less than requested - read until there's no more... # TODO: We should note that LimitRequestBody is a must in apache config my ($buffer, $read_length); my $content = q{}; while ($read_length = $r->read($buffer, $length)) { $content .= $buffer; } if ($length != length $content) { $rlog->error("Read length mismatch; read [" . length($content) . "] bytes but received [$length] bytes"); return Apache2::Const::SERVER_ERROR; } # Shamelessly copied (with mild tweaks) from SOAP::WSDL::Server::CGI # ... which was as shamelessly copied from SOAP::Transport::HTTP... my $request = HTTP::Request->new( $r->method => $r->uri, HTTP::Headers->new( SOAPAction => $r->headers_in()->get('SOAPAction'), ), $content, ); my $response_message = eval { $self->SUPER::handle($request) }; # TODO return response if @$ is a SOAP::WSDL::XSD::Typelib::Builtin::anyType object if ($@ || blessed($@)) { $rlog->error("Failed to handle request: $@"); return Apache2::Const::SERVER_ERROR; } else { return $response_message; } } 1; __END__ =pod =head1 NAME SOAP::WSDL::Server::Mod_Perl2 - mod_perl based SOAP server using SOAP::WSDL =head1 DESCRIPTION Perl module providing a mod_perl2-based SOAP server using SOAP::WSDL =head1 CONFIGURATION Configuration is managed through the use of PerlSetVar directives. The following variables are available: =head2 dispatch_to Takes as a single argument the package name of the module which contains the methods which handle SOAP requests. PerlSetVar dispatch_to "WebPackage::SOAPMethods" =head2 soap_service Takes as a single argument the package name of the Server module generated by SOAP::WSDL using wsdl2perl.pl --server file:///path/to/your/wsdl By default, the name of the package is MyServer::$SERVICENAME::$PORTTYPE. EXAMPLE: Given this sample WSDL which wsdl2perl.pl was run against to generate perl packages: [...] [...] The following directive would be correct: PerlSetVar soap_service "MyServer::WebService::WebServiceSoap" =head2 transport_class [OPTIONAL] Takes as a single argument the package name of the perl module containing a handle() method used to assemble the HTTP request which will be passed to the methods in your L module (see above). A default handle() method is supplied in this module which should handle most common cases. handle() is called with the following parameters: $r - Apache::RequestRec object =head1 EXAMPLES The following snippet added to httpd.conf will enable a SOAP server at /WebService on your webserver: SetHandler perl-script PerlResponseHandler SOAP::WSDL::Server::Mod_Perl2 PerlSetVar dispatch_to "WebPackage::SOAPMethods" PerlSetVar soap_service "MyServer::WebService::WebServiceSoap" =head1 PERFORMANCE On my machine, a simple SOAP server (the HelloWorld service from the examples) needs around 20s to process 300 requests to a CGI script implemented with SOAP::WSDL::Server::CGI, around 4.5s to the same CGI with mod_perl enabled, and around 3.2s with SOAP::WSDL::Server::Mod_Perl2. All these figures include the time for creating the request and parsing the response. As general advice, using mod_perl is highly recommended in high-performance environments. Using SOAP::WSDL::Server::Mod_Perl2 yields an extra 20% speedup compared with mod_perl enabled CGI scripts - and it allows one to configure SOAP servers in the Apache config. =head1 THREAD SAFETY SOAP::WSDL uses Class::Std::Fast, which is not guaranteed to be threadsafe yet. Thread safety in Class::Std::Fast is dependent on whether my $foo = $bar++; is an atomic operation. I haven't found out yet. A load test on a single CPU machine with 4 clients using the worker mpm did not reveal any threading issues - but that does not mean there are none. =head1 CREDITS Contributed (along with lots of other little improvements) by Noah Robin. Thanks! =head1 LICENSE AND COPYRIGHT This file is part of SOAP-WSDL. You may distribute/modify it under the same terms as perl itself =head1 AUTHOR Noah Robin Enoah.robin gmail.comE Based on SOAP::WSDL::Server::CGI, by Martin Kutter Emartin.kutter fen-net.deE =head1 REPOSITORY INFORMATION $Rev: 583 $ $LastChangedBy: kutterma $ $Id: $ $HeadURL: $ =cut