#!/usr/bin/perl # Example of RPC-encoded style SOAP, where we ignore the fact that # there is a schema file. DO NOT USE THIS, UNLESS YOU HAVE NO CHOICE! # Thanks to Thomas Bayer, for providing this service # See http://www.thomas-bayer.com/names-service/ # Author: Mark Overmeer, 26 Nov 2007 # Using: XML::Compile 0.60 # XML::Compile::SOAP 0.64 # Copyright by the Author, under the terms of Perl itself. # Feel invited to contribute your examples! # Of course, all Perl programs start like this! use warnings; use strict; # To make Perl find the modules without the package being installed. use lib '../../lib' , '../../../XMLCompile/lib' # my home test environment , '../../../LogReport/lib'; use XML::Compile::SOAP11::Client; use XML::Compile::Transport::SOAPHTTP; use XML::Compile::Util qw/pack_type/; # Other useful modules use Data::Dumper; # Data::Dumper is your friend. $Data::Dumper::Indent = 1; use List::Util qw/first/; my $format_list; format = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~ $format_list . # Forward declarations sub get_countries($); sub get_name_info(); sub get_names_in_country(); #### MAIN use Term::ReadLine; my $term = Term::ReadLine->new('namesservice'); # # Get the Client and Schema definitions # my $client = XML::Compile::SOAP11::Client->new; my $myns = 'http://namesservice.thomas_bayer.com/'; my $address = 'http://www.thomas-bayer.com:80/names-service/soap'; # # In RPC-encoded, all messages share the same definition. # my $http = XML::Compile::Transport::SOAPHTTP ->new(address => $address) ->compileClient; my $output = $client->compileMessage(SENDER => style => 'rpc-encoded'); my $input = $client->compileMessage(RECEIVER => style => 'rpc-encoded'); # # Pick one of these tests # my $answer = ''; while(lc $answer ne 'q') { print <<__SELECTOR; Which call do you like to see: 1) getCountries 2) getCountries with trace output 3) getNameInfo 4) getNamesInCountry Q) quit demo __SELECTOR $answer = $term->readline("Pick one of above [1/2/3/4/Q] "); chomp $answer; if($answer eq '1') { get_countries(0) } elsif($answer eq '2') { get_countries(1) } elsif($answer eq '3') { get_name_info() } elsif($answer eq '4') { get_names_in_country() } elsif(lc $answer ne 'q' && length $answer) { print "Illegal choice\n"; } } exit 0; sub make_get_countries_out($$$) { my ($soap, $doc, $data) = @_; # define the additional namespaces explicitly $soap->encAddNamespace(ns => $myns); # top level must be namespace qualified! my $top = $soap->struct(pack_type($myns, 'getCountries')); # my $top = $soap->struct("{$myns}getCountries"); # alt, less clean # you may return a LIST of elements to be put in the Body. The # first must be name-space qualified, the other don't. ($top); } sub create_get_countries { my $getCountries = $client->compileClient ( name => 'getCountries' # shared information , encode => $output , transport => $http , decode => $input # the RPC intelligence wrapper , rpcout => \&make_get_countries_out # , rpcin => \&my_decoder # default is usually good enough ); $getCountries; # return the code reference } sub get_countries($) { my $show_trace = shift; # first compile a handler which you can call as often as you want. my $getCountries = create_get_countries; my ($answer, $trace) = $getCountries->(); # If you do not need the trace, simply say: # my $answer = $getCountries->(); # # Some ways of debugging # if($show_trace) { printf "Call initiated at: $trace->{date}\n"; print "SOAP call timing:\n"; printf " encoding: %7.2f ms\n", $trace->{encode_elapse} *1000; printf " transport: %7.2f ms\n", $trace->{transport_elapse} *1000; printf " decoding: %7.2f ms\n", $trace->{decode_elapse} *1000; printf " total time: %7.2f ms ", $trace->{elapse} *1000; printf "= %.3f seconds\n\n", $trace->{elapse}; print "transport time components:\n"; printf " stringify: %7.2f ms\n", $trace->{stringify_elapse} *1000; printf " connection: %7.2f ms\n", $trace->{connect_elapse} *1000; printf " parsing: %7.2f ms\n", $trace->{parse_elapse} *1000; if(my $request = $trace->{http_request}) # a HTTP::Request object { my $req = $request->as_string; $req =~ s/^/ /gm; print "\nRequest:\n", $req; } if(my $response = $trace->{http_response}) # a HTTP::Response object { my $resp = $response->as_string; $resp =~ s/^/ /gm; print "\nResponse:\n", $resp; } } # And now? What do I get back? I love Data::Dumper. #warn Dumper $answer; # # Handling faults # if(my $fault_raw = $answer->{Fault}) { my $fault_nice = $answer->{$fault_raw->{_NAME}}; die "Cannot get list of countries: $fault_nice->{reason}\n"; } # # Collecting the country names # my $countries = $answer->{getCountriesResponse}{country}; print "getCountries() lists ".scalar(@$countries)." countries:\n"; foreach my $country (sort @$countries) { print " $country\n"; } } # # Second example # sub make_get_name_info(@) { my ($soap, $doc, $name) = @_; $soap->encAddNamespace(ns => $myns); my $ni = $soap->element('string', name => $name); $soap->struct(pack_type($myns, 'getNameInfo'), $ni); } sub create_get_name_info() { $client->compileClient ( name => 'getNameInfo' , encode => $output, transport => $http, decode => $input , rpcout => \&make_get_name_info ); } # As you see, we start repeating ourselves a little. It might # therefore, be convinient to define a "macro" like wrapper: # sub newRPC($@) # { my $code = shift; # $client->compileClient # ( encode => $output, transport => $http, decode => $input # , rpcout => $code # , @_ # additional options # ); # } # my $get_name_info = newRpc(\&my_creator); # # or # my $newRpc = sub # { my $code = shift; # $client->compileClient # ( encode => $output, transport => $http, decode => $input # , rpcout => $code # , @_ # additional options # ); # }; # my $get_name_info = $newRpc->(\&my_creator); # # The $output, $http, and $input are needed only once, so this whole # creation process could be hidden in a single sub-routine. sub get_name_info() { my $getNameInfo = create_get_name_info; # ## From here on, just like the WSDL version # # ask the user for a name my $name = $term->readline("Personal name for info: "); chomp $name; length $name or return; my ($answer, $trace2) = $getNameInfo->($name); #print Dumper $answer; #print Dumper $trace2; die "Lookup for '$name' failed: $answer->{Fault}{faultstring}\n" if $answer->{Fault}; my $nameinfo = $answer->{getNameInfoResponse}{nameinfo}; # answer is untyped, so we have to interpret (and validate) ourselves my $is_male = $nameinfo->{male} =~ m/^(?:1|true)$/ ? 'yes' : 'no'; my $is_female = $nameinfo->{female} =~ m/^(?:1|true)$/ ? 'yes' : 'no'; print "The name '$nameinfo->{name}' is\n"; print " male: $is_male\n"; print " female: $is_female\n"; print " gender: $nameinfo->{gender}\n"; print "and used in countries:\n"; $format_list = join ', ', @{$nameinfo->{countries}{country}}; write; } # # Third example # # by the way: if you need more than one data fact in the RPC call, then # you must create an ARRAY to pass it. So: # $my_rpc_call->( [1,2,3] ); # and it will be passed as ARRAT as third constructor element. This is # done because the call can have more parameters: for the header. Like # this: # $my_rpc_call->( [1,2,3], login => {username => 'me', password => ...}); sub make_get_names_in_country($$$) { my ($soap, $doc, $country) = @_; $soap->encAddNamespace(ns => $myns); my $c = $soap->element(string => country => $country); $soap->struct(pack_type($myns, 'getNamesInCountry'), $c); } sub get_names_in_country() { # usually in the top of your script: reusable my $getCountries = create_get_countries; # creating calls is quite simple, so let's in-line this time. my $getNamesInCountry = $client->compileClient ( encode => $output, transport => $http, decode => $input , rpcout => \&make_get_names_in_country ); # ## From here on the same as the WSDL version # my $answer1 = $getCountries->(); die "Cannot get countries: $answer1->{Fault}{faultstring}\n" if $answer1->{Fault}; #print Dumper $answer1; my $countries = $answer1->{getCountriesResponse}{country}; my $country; while(1) { $country = $term->readline("Most common names in which country? "); chomp $country; $country eq '' or last; print " please specify a country name.\n"; } # find the name case-insensitive in the list of available countries my $name = first { /^\Q$country\E$/i } @$countries; unless($name) { $name = 'other countries'; print "Cannot find name '$country', defaulting to '$name'\n"; print "Available countries are:\n"; $format_list = join ', ', @$countries; write; } print "Most common names in $name:\n"; my $answer2 = $getNamesInCountry->($name); die "Cannot get names in country: $answer2->{Fault}{faultstring}\n" if $answer2->{Fault}; #print Dumper $answer2; my $names = $answer2->{getNamesInCountryResponse}{name}; $names or die "No data available for country `$name'\n"; $format_list = join ', ', @$names; write; }