Thursday, December 3, 2015

Consuming Siebel web services with Perl

The first time that I tried to consume a Siebel web service with Perl wasn't a pleasant experience.

Going back to 2009, I saw a opportunity to flex my programming muscles and do it to get contact information. It was not only a opportunity to put my old book about web services with Perl to use, but also to greatly reduce operations time in the company I was working at that time.

Let's be fair: that book was really outdated. Back that time the book was published WSDL was available, but things were much easier since SOAP was RPC-based, not document based. Guess which implementation those web services in Siebel were based? Document.

I exhausted all my options with SOAP::Lite. I tried even editing manually the WSDL exported from Siebel, gave a shot with SOAP::WSDL (better, but not good enough) and after hours debugging I decided to have a chat with a fellow .Net programmer that worked in the same company and see if he was able to consume the WSDL from Siebel and do something with the results. To my shame he was and not just accomplished the job but did it in 15 minutes with a working POC.

I already had found references about XML::Compile at that moment, but the documentation from it was poor... and given the amount of time already spent, I went with my college .Net code.

Let's now go back to present. This year I decided to give another try with SOAP in Perl and the choice was with XML::Compile. After reading it's documentation (that was improved compared to the past, but still has a lot of space for doing better), starting from XML::Compile::WSDL and after a while I was able to pull up a working piece of code:

    use XML::Compile::WSDL11;
    use XML::Compile::SOAP11;
    use XML::Compile::Transport::SOAPHTTP;

    my $wsdlfile = File::Spec->catfile( 't', 'SWIContactServices.WSDL' );
    my %request = (
        ListOfSwicontactio => {
            Contact =>
                { Id => '0-1', FirstName => 'Siebel', LastName => 'Administrator' }
        }
    );

    my $wsdl = XML::Compile::WSDL11->new($wsdlfile);


    my $call = $wsdl->compileClient(
        operation      => 'SWIContactServicesQueryByExample',
        transport_hook => \&do_auth
    );

    my ( $answer, $trace ) = $call->(%request);

    if ( my $e = $@->wasFatal ) {

        $e->throw;

    } else {

        # do something with the response

    }

The "SWI Contact Services" is a vanilla Siebel inbound web service, very simple indeed. In this case, I'm just providing to it some information regarding SADMIN and recover the contact details in the response payload. Not very exciting, but SADMIN contact will always be in database.

Siebel has it's own authentication process instead of using some of the standards available out there. Unless the inbound web service in question uses that degenerate "user and password in the URL" method, you probably will want to use Siebel session management. That's exactly what the sub reference do_auth does in this sample code: such sub is well documented in XML::Compile::WSDL Pod, which includes the manipulation of the SOAP envelope containing the SOAP header, which is exactly what Siebel expects you to do to use session management.

Session management in Siebel has several advantages, including improved performance. By receiving a authentication token, several steps of authentication are skipped until that token is not valid anymore.

With that in mind, I think that doing this over and over would be quite boring. It was time to cook something and deliver it to CPAN: enter Siebel::SOAP::Auth.

By using a tiny object built with Moo now you can not only provide the Siebel authentication but also handle it entirely automatically. Here is the same code with it:

    use XML::Compile::WSDL11;
    use XML::Compile::SOAP11;
    use XML::Compile::Transport::SOAPHTTP;
    use Siebel::SOAP::Auth;

    my $wsdlfile = File::Spec->catfile( 't', 'SWIContactServices.WSDL' );
    my %request = (
        ListOfSwicontactio => {
            Contact =>
                { Id => '0-1', FirstName => 'Siebel', LastName => 'Administrator' }
        }
    );

    my $wsdl = XML::Compile::WSDL11->new($wsdlfile);
    my $auth = Siebel::SOAP::Auth->new(
        {
            user          => 'sadmin',
            password      => 'XXXXXXX',
            token_timeout => MAGIC_NUMBER,
        }
    );

    my $call = $wsdl->compileClient(
        operation      => 'SWIContactServicesQueryByExample',
        transport_hook => sub { 
            my ( $request, $trace, $transporter ) = @_;
            # request was modified
            my $new_request = $auth->add_auth_header($request);
            return $trace->{user_agent}->request($new_request);
        }
    );

    my ( $answer, $trace ) = $call->(%request);

    if ( my $e = $@->wasFatal ) {

        $e->throw;

    } else {

        # do something with the answer

    }

    $auth->find_token( $answer );

The MAGIC_NUMBER above is constant that defines the number of seconds the token will timeout.

The secret is to use the instance inside a sub reference, pass to it the $request (which will have the SOAP envelope to be manipulated) and return from the request made, exactly the same you would need to do in the do_auth sub. You need then to provide the new token  receive to the $auth object find_token method.

The good news is that a instance of Siebel::SOAP::Auth, being maintained and used during all the life time you need a web service from Siebel will take care of renewing the token, and hopefully, even avoid a additional round trip to the server in the case the token expires before it requests a new one. In the case it misses the opportunity, you need to be careful and check the exception throw by $@->wasFatal and do something with it (probably just repeat the request).

I'm still waiting for some bad news (not because I didn't find one it is not there). Please let me know if you find any!

No comments:

Post a Comment