7.5 Improving the Code and the Service


At this stage, the code reflects a functional web service with a clearly defined interface. Now that the basic elements are written and proven, it is time to improve the server-level performance, and examine the interface and code more closely to find areas that can be improved.

7.5.1 Moving the Server to Apache

Example 7-7 is quite functional for a lighter-access server, but if the target deployment environment already has Apache and mod_perl available, why not use them?

There are two easily available ways to interface to Apache with the components of the SOAP::Lite toolkit. One of these uses the Apache::SOAP wrapper module included in the SOAP::Lite distribution. The other uses the Apache form of the HTTP code, SOAP::Transport::HTTP::Apache , directly within a <Perl> configuration block. This second approach is used in Example 7-8. The Apache transport is subclassed as Apache::SOAP does, with a handler method that extracts the cookie information in much the same way as the request method of WishListCustomer::Daemon .

The code in Example 7-8 bears a striking resemblance to the subclass of HTTP::Daemon shown earlier in Example 7-4. The basic task and approach are the same; what differs is when the process has the opportunity to get the request object and retrieve the cookies from it. With Apache and mod_perl , the handler method is passed an Apache-style request object, which can fetch these headers much more quickly, so the task is taken care of at this stage rather than at a later one.

Example 7-8. The WishListCustomer::Apache class
 package WishListCustomer::Apache;     use strict; use vars qw(@ISA);     use SOAP::Transport::HTTP; use WishListCustomer::SOAP; @ISA = qw(SOAP::Transport::HTTP::Apache);     1;     sub handler ($$) {     my ($self, $request) = @_;         my $cookies = $request->header_in('cookie');     my @cookies = ref $cookies ? @$cookies : $cookies;     %WishListCustomer::SOAP::COOKIES = ( );     for my $line (@cookies) {         for (split(/; /, $line)) {             next unless /(.*?)=(.*)/;             $WishListCustomer::SOAP::COOKIES{} = ;         }     }         $self->SUPER::handler($request); } 

Interestingly enough, the class has no need to include any of the Apache :: classes, let alone inherit from them. Were it not for the difference in interfaces between the Apache and daemon-based SOAP::Transport::HTTP classes, the same code might have been used for both.

Example 7-9 shows how Example 7-8 might be used within a <Perl> block of the Apache configuration file.

Example 7-9. Using the WishListCustomer::Apache handler
 <Perl>     use lib '/var/www/WishListCustomer';     use WishListCustomer::Apache;         $main::wishlist =         WishListCustomer::Apache             ->new             ->dispatch_with({ 'urn:/WishListCustomer' =>                               'WishListCustomer::SOAP' });         $Location{'/SOAP'} = {         PerlHandler => '$main::wishlist',         SetHandler  => 'perl-script'     }; </Perl> 

Some of what is done in that example would be better done in a dedicated initialization file that manages the loading and configuration of Perl modules in a mod_perl environment. But what is shown in the example does work, just as well as the HTTP::Daemon approach (and much more quickly). [2]

[2] For more about optimizing and managing a mod_perl installation, see Writing Apache Modules with Perl and C , by Lincoln Stein and Doug MacEachern (O'Reilly).

Note that this version of the server doesn't use objects_by_reference in the configuration of the SOAP server. Managing objects by reference is much more efficient in both operational speed (as fewer objects are created and destroyed ) and in the quantity of data being sent along the wire. Object references are much more compact when serialized, in most cases. It is also very useful when the nature of the object doesn't lend itself very well to full serialization. If this server had to send the object itself, instead of the method results, the serialization would probably not preserve all the underlying attributes of the user and catalog abstraction layers . And in fact, should the client see them at all? With objects that are passed back and forth by reference, the client remains shielded from these implementation details.

While the technique works for single-process approaches, such as that used with the HTTP::Daemon approach, it runs into a very basic problem with Apache: multiple processes. Under Apache, there is no guarantee that the server process that handled the object construction request will handle a method call request. So, if a client passes an object handle as part of a method call, it will almost certainly be invalid to the server process that handles that call.

The server-side code addresses this by allowing all the methods to initialize an object when they go to set their localized $self variable. Because the authentication cookie will be sent with all requests , each transaction simply creates a new instance as it's needed. As a result, all operations that modify the data of an object are obligated to write such changes immediately, such as with the AddBook or PurchaseBooks method. To overcome this limitation would require adding a layer to the serialization and deserialization steps in the SOAP server that maintained object data in a shared-memory segment, or some other form of interprocess communication (IPC).

By designing the test client to use cookies from the outset, it's easier to test the Apache SOAP server with the same set of inputs that were fed to the initial server.

7.5.2 Revisiting the Interface

The SOAP layer as it is currently designed exactly mirrors the original class interface. Some of the operations defined by the interface seem to be redundant, however, and a SOAP interface has the option of addressing this without requiring an update of the underlying code. The SOAP interface can simply present a different API to the client.

As an example of this, the BooksByAuthor and BooksByTitle methods will be rolled into a single interface point, called FindBooks . Doing this also provides an opportunity to demonstrate accessing the SOAP envelope from within server-side code.

7.5.2.1 Designing WishListCustomer::SOAP2

The second-generation interface code replaces the two search methods with the single one suggested earlier. The WishListCustomer class will not change, but the client isn't concerned with that class. Their concern is with the interface that the SOAP server offers. The details aren't important to them.

There are two ways that the FindBooks method could discern the type of search to perform. The first would be to have a string-typed parameter passed in with the search term , and use the value of that parameter to decide whether to search by author or title. That would be the equivalent of the familiar Perl-style interface:

 $books = $obj->FindBooks(author => 'Christiansen'); 

However, there's no need for the extra parameter; it only adds to the overall size of the message being sent over the wire. Since SOAP provides for named parameters, why not let the name be the selector? If the input parameter is called "author," the search is by author. Similarly, the search is by title if the parameter is named "title."

This turns the nearly identical blocks that make up BooksByAuthor and BooksByTitle into the single routine (with pseudocode):

 sub FindBooks {     my ($class, $arg) = @_;         my $hook = (NAME_OF_ARG($arg) eq 'author') ?                    \&SoapExBook::get_books_by_author :                    \&SoapExBook::get_books_by_title;     my $bookdb = ref($class) ? $class->{_catalog} :                                SoapExBook::connect( );     return undef unless $bookdb;         my @books = $hook->($bookdb, $author);     \@books; } 

This routine cheats slightly by not checking for invalid names but rather defaulting to a title search. In doing so, clients can get away with badly named parameters; they just end up with a title search unless they are specific about searching by author. What remains is filling in the pseudocode that allows the routine to determine the name by which the parameter was sent.

7.5.2.2 Accessing the SOAP envelope

Code that runs on a SOAP server that ultimately derives from the SOAP::Server class (as all the SOAP::Transport::* server classes do) can arrange to have the SOAP::SOM object that represents the deserialized request object passed along with the parameter list. With access to this object, code can retrieve the full SOAP::Data object that represents a given input parameter. What gets passed in the parameter list itself is just the Perl representation of the data value, and in most cases that's all that's needed. When there is a need for more information, it's available.

If the method's class inherits from SOAP::Server::Parameters , SOAP::Lite appends the SOAP::SOM object to the list of parameters when the method is called. Using the class methods introduced in Chapter 6, the paramsin method appears to be the solution to the problem, as shown in the final version of FindBooks in Example 7-10.

Example 7-10. Using the SOAP envelope to complete FindBooks
 sub FindBooks {     my ($class, $arg, $env) = @_;         my $argname = $env->match(SOAP::SOM::paramsin)->dataof;     my $hook = ($argname->name eq 'author') ?                    \&SoapExBook::get_books_by_author :                    \&SoapExBook::get_books_by_title;     my $bookdb = ref($class) ? $class->{_catalog} :                                SoapExBook::connect( );     return undef unless $bookdb;         my @books = $hook->($bookdb, $author);     \@books; } 

Initially, $argname is set to the SOAP::Data object that represents the first input parameter. The match method on the SOAP::SOM object locates this; the dataof method then retrieves that part of the structure as the SOAP::Data object. In the next line, the name method is called on the data object to get the name by which the parameter was sent.

In this simple example, it would be enough to get a reference to a hash table of the parameters by calling $env->method , which returns the contents of the "method" portion of the envelope. With only one parameter in the mix, the hash-table reference would have only one key. The method shown in Example 7-10 scales more easily for longer, more complicated argument lists.

7.5.2.3 Testing the new method

Finally, Example 7-11 shows a modified version of the earlier client that searches for books based on command-line usage.

Example 7-11. Simple client to search for books using FindBooks
 #!/usr/bin/perl     use strict;     use SOAP::Lite;     my ($type, $string) = (shift, shift); die "USAGE: 
 #!/usr/bin/perl use strict; use SOAP::Lite; my ($type, $string) = (shift, shift); die "USAGE: $0 { author  title } pattern [ endpoint ]\n" unless ($type and $string); my $endpoint = shift  'http://localhost.localdomain:9000'; my $soap = SOAP::Lite->uri('urn:/WishListCustomer') ->proxy($endpoint); my $result = $soap->FindBooks(SOAP::Data->name($type, $string)); if ($result->fault) { die "$0: Operation failed: " . $result->faultstring; } my $books = $result->result; format = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>>>>>>>> $result->{title}, $result->{isbn} . for (@$books) { $result = $soap->GetBook($_); # Quietly skip books that cause faults next if ($result->fault); $result = $result->result; write; } exit; 
{ author title } pattern [ endpoint ]\n" unless ($type and $string); my $endpoint = shift 'http://localhost.localdomain:9000'; my $soap = SOAP::Lite->uri('urn:/WishListCustomer') ->proxy($endpoint); my $result = $soap->FindBooks(SOAP::Data->name($type, $string)); if ($result->fault) { die "
 #!/usr/bin/perl use strict; use SOAP::Lite; my ($type, $string) = (shift, shift); die "USAGE: $0 { author  title } pattern [ endpoint ]\n" unless ($type and $string); my $endpoint = shift  'http://localhost.localdomain:9000'; my $soap = SOAP::Lite->uri('urn:/WishListCustomer') ->proxy($endpoint); my $result = $soap->FindBooks(SOAP::Data->name($type, $string)); if ($result->fault) { die "$0: Operation failed: " . $result->faultstring; } my $books = $result->result; format = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>>>>>>>> $result->{title}, $result->{isbn} . for (@$books) { $result = $soap->GetBook($_); # Quietly skip books that cause faults next if ($result->fault); $result = $result->result; write; } exit; 
: Operation failed: " . $result->faultstring; } my $books = $result->result; format = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>>>>>>>> $result->{title}, $result->{isbn} . for (@$books) { $result = $soap->GetBook($_); # Quietly skip books that cause faults next if ($result->fault); $result = $result->result; write; } exit;

Using the SOAP::Data class and methods to construct the argument to the method being called, you get complete control over aspects such as name that let use the full features of the FindBooks method. Simple indeed, and scalable. Should the FindBooks method add other types of search keys in the future, this client can easily use them as well.



Programming Web Services with Perl
Programming Web Services with Perl
ISBN: 0596002068
EAN: 2147483647
Year: 2000
Pages: 123

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net