23skiddoo has asked for the wisdom of the Perl Monks concerning the following question:

I have been forced into a crash-course in SOAP to write an application that will post a chunk of XML to a .NET web service. I have read all the issues regarding SOAP::Lite and .NET interoperability (or inter-non-operability), and I appear to be creating the correct output, but I continue to get "400 Bad Request" errors. The developer on the .NET end has tried posting my XML using his non-Perl code and it works for him. Grrrr... Here is what the service expects:
POST /service.asmx HTTP/1.1 Host: foo.com Content-Type: text/xml; charset=utf-8 Content-Length: [length] SOAPAction: "https://foo.com/IntegrateShipmentNotice" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" x +mlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schema +s.xmlsoap.org/soap/envelope/"> <soap:Body> <IntegrateShipmentNotice xmlns="https://foo.com/"> <shipmentNotice>[XML]</shipmentNotice> </IntegrateShipmentNotice> </soap:Body> </soap:Envelope>
And here is the output of my script with "debug" turned on (contents redacted for privacy):
SOAP::Transport::new: () SOAP::Serializer::new: () SOAP::Deserializer::new: () SOAP::Parser::new: () SOAP::Lite::new: () SOAP::Transport::HTTP::Client::new: () SOAP::Data::new: () SOAP::Lite::call: () SOAP::Serializer::envelope: () SOAP::Serializer::envelope: IntegrateShipmentNotice SOAP::Data=HASH(0x +89db70c) SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Data::new: () SOAP::Transport::HTTP::Client::send_receive: HTTP::Request=HASH(0x89fe +bf0) SOAP::Transport::HTTP::Client::send_receive: POST https://foo.com/serv +ice.asmx HTTP/1.1 Accept: text/xml Accept: multipart/* Accept: application/soap Content-Length: 2194 Content-Type: text/xml SOAPAction: https://foo.com/IntegrateShipmentNotice <?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:xsi="http:/ +/www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xm +lsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema +" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmln +s:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <IntegrateShipmentNotice xmlns="https://foo.com/"> <shipmentNotice>[live XML redacted out] </shipmentNotice> </IntegrateShipmentNotice> </soap:Body> </soap:Envelope> SOAP::Transport::HTTP::Client::send_receive: HTTP::Response=HASH(0x8d6 +5ddc) SOAP::Transport::HTTP::Client::send_receive: HTTP/1.1 400 Bad Request Cache-Control: private Date: Tue, 28 Aug 2007 14:02:56 GMT Server: Microsoft-IIS/6.0 Content-Length: 0 Client-Date: Tue, 28 Aug 2007 13:59:27 GMT Client-Peer: Client-Response-Num: 1 Client-SSL-Cert-Issuer: /C=GB/ST=Greater Manchester/L=Salford/O=Comodo + CA Limited/CN=PositiveSSL CA Client-SSL-Cert-Subject: /OU=Domain Control Validated/OU=PositiveSSL/C +N=fulfillment.sciona.com Client-SSL-Cipher: RC4-MD5 Client-SSL-Warning: Peer certificate not verified X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET 400 Bad Request at ./procShipAck.SOAP.pl line 80 SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Transport::HTTP::Client::DESTROY: () SOAP::Lite::DESTROY: () SOAP::Deserializer::DESTROY: () SOAP::Parser::DESTROY: () SOAP::Transport::DESTROY: () SOAP::Serializer::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: () SOAP::Data::DESTROY: ()
So, then, to my script:
#!/usr/bin/perl -w use strict; use SOAP::Lite qw( debug ); $SOAP::Constants::DO_NOT_USE_CHARSET = 1; my $XMLURI = 'https://foo.com/'; my $PROXY = 'https://foo.com/service.asmx'; my @files; # If files have been passed on the command line, use them. if ( @ARGV ) { foreach my $this_file ( @ARGV ) { if ( -e $this_file && $this_file =~ m/\.xml \Z/smxi ) { push @files, $this_file; } } } exit if @files < 1; foreach my $ackfile ( @files ) { # There's gotta be a way to simply pass $ackfile straight to # SOAP::Lite instead of building a string, but I haven't # figured it out yet... open my $fh, "<", $ackfile or die "Can't open $ackfile: $!"; my @xml = <$fh>; close $fh; my $xml = join "", @xml; if ( not $debug ) { my $soap = SOAP::Lite -> uri( $XMLURI ) -> proxy( $PROXY ) -> default_ns( $XMLURI ) -> autotype( 0 ) -> on_action( sub { 'https://foo.com/IntegrateShipmentNotice' +} ) ; print $soap->IntegrateShipmentNotice( SOAP::Data->name( shipmentNotice => $xml ) ) -> result . "\n\n" ; } else { print $xml . "\n"; } }
Now, I can't figure out what I'm doing wrong. The only difference I have been able to find is that SOAP::Lite produces two extra namespaces inside the SOAP envelope: xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" and soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" If anyone can lend any help, I would really appreciate it! Even help on how to further debug this would be useful. Unfortunately I do not have control over the server hosting the web service, so I can't access logs and such. Mike
fnord

Replies are listed 'Best First'.
Re: SOAP::Lite woes
by erroneousBollock (Curate) on Aug 29, 2007 at 00:56 UTC
    Firstly, IIRC the "400 Bad request" response code from a webserver usually means you have a malformed HTTP request, so there's probably something going on with the HTTP (not SOAP) headers you're sending.

    Secondly, does the service have an WSDL description? If so, please try SOAP::WSDL before trying mangle namespaces yourself. This module does 99% of the work for you in SOAP client development; it's a sub-class of SOAP::Lite that really doesn't get enough press.

    -David

      Thanks for your input. I actually got it working after reading this document: http://www.soaplite.com/2003/05/composing_messa.html . It seems that the guy on the server end misled me about the XML I was sending. He assured me that I should include the XML Declaration with what I was sending, but that is apparently not the case. I also scrubbed out any newlines for good measure. Then, everything was peachy! (He did offer WSDL access but said "I don't think you'll be able to use the wsdl though since I'm exposing complextypes"--whatever that means!)

      Thanks again!
      Mike

      fnord
        complexTypes are composite types built from simpler xsd types. Any SOAP client library that can't handle complexTypes is simply not useful. Luckily, SOAP::Lite can send and receive them just fine ;)

        Examples of complexTypes include ArrayOfInts and CustomerContact (the name of some class). The WSDL description of the service tells the client how to send and receive values of such types.

        SOAP::WSDL's primary benefit is that it *does* support whatever complexTypes are declared in the WSDL for the service... it therefor reduces the amount of work you as a client programmer must do to almost nothing.

        If you use SOAP::Lite directly, you'll have to hand-construct complexType instance values yourself using SOAP::Data et al... which is painful and error-prone. Don't do it if you don't have to.

        -David.