Long details of how I got SOAP::Lite to work with my WSDL when I was using packaged implementations (instead of main::* implementations).
Overview This article describes tools, environment configurations, diagnostic techniques and all of the little gotchas that will getcha when you try to use SOAP::Lite from a WSDL. Tools- XMLSpy - If you do a lot of XML / SOAP / WSDL work, this should just be in your toolbox. If you do XML / SOAP / WSDL work relatively infrequently, using your one-time (presumed) 30-day eval will help you justify the cost when you begin to use it more frequetnly. [Optional].
- A good Editor.
- Apache or some other webserver that can run CGI.
Environment Prerequisites- You have Apache functional with CGI support.
- You should know where your access and error logs are at. You will occaissionally need to review them to understand why you get 500 errors because of dumb typos. (Ok, well, I did.)
Diagnostics You will want to write / use a logging module that will write to a log file somewhere so that your clients and servers all talk to the same file and automatically single-thread during development unit testing. Having the ability to dump the stack at arbitrary points in your diagnostics will help when you are digging around inside SOAP::Lite. (Hopefully you will not need to, but I found it mandatory just to understand what was happening.) Web Service Definition If you have XMLSpy with WSDL support, use it. As of this writing it is a little weird in that multi-color lines are turned on by default and the first line color is red (which is bad in my book, but apparently not in other books). You will find that the WSDL UI is reasonable with a 4-component display, but you will need to use the property-sheets to completely wire up your WSDL. If you do not use XMLSpy, you may still want to read these instructions to get your bearings inside WSDL. Using XMLSpy, here are the operations. - Name your portType "foo"
- Append an Operation to the "foo" portType. Name it "bar.
- Right click on Service and Append a new Port. Name it "foo".
- Drag and Drop the Service port "foo" onto the Bindings UI widget. All of your items are now connected.
- In the Property-Sheet, append a new message. Name it "foo.request".
- Append a second message. Name it "foo.response".
- Append a third message. Name it "foo.fault".
- For each of the messages, add a message part. Each of the message parts are positional parameter in your API. Adjust as you see fit for your experiment.
- In the Operations Widget, right click on "foo" and add an Input, Output and Fault element.
- Drag each of the corresponding messages from the property browser (right edge) to the operations elements for "foo".
- Select the ServiceName "foo". Set the Location value to: http://localhost/path/to/your/soap/server.cgi
- Select the Bindings | BindingName (default name) from the property-browser. Set the type of binding to "SOAP". Set the style to "rpc". Set the transport URI to http://localhost/path/to/your/soap/server.cgi
- Select the "soap_sample" operation from the Bindings widget, then enter the soapAction as "#soap_sample" in the properties pane.
- Select the Soap:body elements from the Bindings widget and set the use to "encoded".
- File | Save
- XML | Validate (F8) Resolve any issues if the file does not validate at this point.
Now, when we look at the source of the WSDL that has been generated at this point, we have: <?xml version="1.0" encoding="UTF-8"?> <!-- edited with XMLSpy v2006 rel. 3 sp2 (http://www.altova.com) by John Fa'atuai (Self) --> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:y="http://localhost/path/to/your/soap/server.cgi" targetNamespace="http://localhost/path/to/your/soap/server.cgi"> <types> <xs:schema/> </types> <message name="soap_sample.request"> <part name="name" type="xs:string"/> <part name="age" type="xs:nonNegativeInteger"/> <part name="birthdate" type="xs:date"/> </message> <message name="soap_sample.response"> <part name="success" type="xs:nonNegativeInteger"/> <part name="hashref" type="xs:string"/> </message> <message name="soap_sample.fault"> <part name="exception" type="xs:string"/> </message> <portType name="soap_port"> <operation name="soap_sample"> <input message="y:soap_sample.request"/> <output message="y:soap_sample.response"/> <fault name="soap_sample_fault" message="y:soap_sample.fault"/> </operation> </portType> <binding name="soap_binding" type="y:soap_port"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="soap_sample"> <soap:operation soapAction="#soap_sample" style="rpc"/> <input> <soap:body use="encoded"/> <documentation>
This is the input portion of your soap_binding. It controlled the encoding / literal treatment of the associated soap_sample.request message.
</documentation> </input> <output> <soap:body use="encoded"/> <documentation>
This is the input portion of your soap_binding. It controlled the encoding / literal treatment of the associated soap_sample.response message.
</documentation> </output> <fault name="soap_sample_fault"> <documentation>
This is the input portion of your soap_binding. It controlled the encoding / literal treatment of the associated soap_sample.fault message. This is where you specify exceptional responses.
</documentation> </fault> </operation> </binding> <service name="soap_service"> <port name="soap_port" binding="y:soap_binding"> <soap:address location="http://localhost/path/to/your/soap/server.cgi"/> </port> </service> </definitions>
SOAP Server So, your SOAP Server will look something like this: Note that we need to implement a main::bar(), even though we told SOAP::Lite to use our package. (Sigh). Attempts to vary the soapAction URI resulted in loads of diagnostic fun inside the SOAP::Server that I'll spare you. Suffice it to say that these are the rules. SOAP ClientSince the WSDL has the service URIs embedded, we'll use the SOAP::Lite stubmaker.pl to generate a concrete web service implementation for use to use:
perl stubmaker.pl file:///c:/path/to/your/sample.wsdl Accessing... Writing... ./soap_service.pm done
Your actual client shouldlook something like this: #!perl -w #---------------------------------------------------------------------- =pod
=head1 NAME
soap_wsdl.pl - Sample SOAP Client code.
=head2 DESCRIPTION
This script provides example source.
=head2 STUB GENERATION
cd /sd/applications/uds/soap perl stubmaker.pl http://${host}/path/to/soap/server.cgi?WSDL
This will generate the UDS.pm required by this script based on the URL. If ${host} is undefined, try: file:///c:/path/to/your/soap_sample.wsdl.
=cut
use strict; no warnings; ### SOAP::Lite is very noisy. use Data::Dumper; use EMAN::API::AD;
use lib "."; use soap_service; ## Generated by SOAP::Lite's stubmaker.pl
####use SOAP::Lite +trace => qw( debug );
my( $username ) = $ENV{USERNAME}; my( $password ) = shift; die "You must provide your password on the command line." unless( $password ne "" );
&main(); exit( 0 );
#---------------------------------------------------------------------- =pod
=head2 main()
USAGE:
&main(); exit( 0 );
DESCRIPTION:
This method establishes a SOAP client connect, registers a fault callback and makes a request to demonstrate a normal return / result, as well as a request to demonstrate a fault-handler invocation.
=cut
sub main { my( $service ) = new soap_service( on_fault => \&exception_handler );
my( $hash ) = {}; $hash->{name} = 'my name'; $hash->{age} = 42; $hash->{birthdate} = "1964-10-11"; ## ISO8601 by convention only. my( @results ) = $uds->soap_sample( $hash->{name}, $hash->{age}, $hash->{birthdate}, ); print "soap_sample() \@results: ", Dumper( @results ); }
#---------------------------------------------------------------------- =pod
=head2 exception_handler()
USAGE:
my( $uds ) = new sample_wsdl( on_fault => \&exception_handler );
DESCRIPTION:
This method is called by SOAP::Lite when ever the SOAP server throws an exception (fault).
=cut
sub exception_handler { my( $soap ) = shift; my( $res ) = shift; my( $e ) = $res->faultdetail() if( defined( $res ) ); my( $name ) = keys %{ $e }; die ref( $res ) ? join( "\n", "SERVER-SIDE EXCEPTION:" . $e->{$name}->{MESSAGE}, $e->{$name}->{STACK_TRACE}, ) : $soap->transport()->status(), "\n"; }
#---------------------------------------------------------------------- =pod
=head2 get_basic_credentials()
USAGE:
%credentials = $soap_client->get-basic_credentials();
DESCRIPTION:
This method is called by SOAP. It is called by the SOAP client when HTTP credentials are requested. $username and $password should be defined before the SOAP client is invoked.
=cut
sub SOAP::Transport::HTTP::Client::get_basic_credentials { return( $username => $password ); }
The nice thing about this is that the code no longer knows about the WSDL, or even the service URI. That is all embedded and hidden in the generated stub class. There and Back... More than likely something will go wrong as you through this exercise. Here are some things to try if you get 500 errors or otherwise do not get a success path. - Verify that your request is making it to the webserver access.log.
- Verify that your request is NOT bombing out via your webserver error.log.
- Add diagnostics to append to a log file and put log calls into both your client and server and implementation to ensure that you get to a particular piece of code.
- Turn on SOAP::Lite tracing via: use SOAP::Lite +trace => qw( debug );
Good luck. |