File Coverage

blib/lib/Catalyst/Controller/SOAP.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             { package Catalyst::Controller::SOAP;
2              
3 2     2   58125 use strict;
  2         7  
  2         84  
4 2     2   13 use base 'Catalyst::Controller';
  2         3  
  2         1280  
5 2     2   941 use XML::LibXML;
  0            
  0            
6             use XML::Compile::WSDL11;
7             use XML::Compile::SOAP11;
8             use MRO::Compat;
9             use mro 'c3';
10             use Encode;
11              
12             use constant NS_SOAP_ENV => "http://schemas.xmlsoap.org/soap/envelope/";
13             use constant NS_WSDLSOAP => "http://schemas.xmlsoap.org/wsdl/soap/";
14              
15             our $VERSION = '1.25';
16              
17             __PACKAGE__->mk_accessors (qw(wsdl wsdlobj decoders encoders ports
18             wsdlservice xml_compile soap_action_prefix rpc_endpoint_paths
19             doclitwrapped_endpoint_paths));
20              
21             sub __init_wsdlobj {
22             my ($self, $c) = @_;
23              
24             my $wsdlfile = $self->wsdl;
25              
26             if ($wsdlfile) {
27             if (!$self->wsdlobj) {
28             my $schema;
29             if (ref $wsdlfile eq 'HASH') {
30             $schema = $wsdlfile->{schema};
31             $wsdlfile = $wsdlfile->{wsdl};
32             }
33              
34             if (ref $wsdlfile eq 'ARRAY') {
35             my $main = shift @{$wsdlfile};
36             $c->log->debug("WSDL: adding main WSDL $main")
37             if $c->debug;
38             $self->wsdlobj(XML::Compile::WSDL11->new($main));
39             foreach my $file (@{$wsdlfile}) {
40             $c->log->debug("WSDL: adding additional WSDL $file")
41             if $c->debug;
42             $self->wsdlobj->addWSDL($file);
43             }
44             }
45             else {
46             $c->log->debug("WSDL: adding WSDL $wsdlfile")
47             if $c->debug;
48             $self->wsdlobj(XML::Compile::WSDL11->new($wsdlfile));
49             }
50              
51             if (ref $schema eq 'ARRAY') {
52             foreach my $file (@$schema) {
53             $c->log->debug("WSDL: Import schema $file")
54             if $c->debug;
55             $self->wsdlobj->importDefinitions($file);
56             }
57             }
58             elsif ($schema) {
59             $c->log->debug("WSDL: Import schema $schema") if $c->debug;
60             $self->wsdlobj->importDefinitions($schema)
61             }
62             }
63             }
64              
65             return $self->wsdlobj ? 1 : 0;
66             }
67              
68             sub _parse_WSDLPort_attr {
69             my ($self, $c, $name, $value, $wrapped) = @_;
70              
71             die 'Cannot use WSDLPort without WSDL.'
72             unless $self->__init_wsdlobj($c);
73              
74             $self->ports({}) unless $self->ports();
75             $self->ports->{$name} = $value;
76             my $operation = $self->wsdlobj->operation($name,
77             port => $value,
78             service => $self->wsdlservice)
79             or die 'Every operation should be on the WSDL when using one.';
80             # TODO: Use more intelligence when selecting the address.
81             my ($path) = $operation->endPoints;
82              
83             $path =~ s#^[^:]+://[^/]+##;
84              
85             my $style = $operation->style;
86             my $use = $operation->{input_def}->{body}->{use};
87              
88             $style = $style =~ /document/i ? 'Document' : 'RPC';
89             $use = $use =~ /literal/i ? 'Literal' : 'Encoded';
90             $c->log->debug("WSDLPort: [$name] [$value] [$path] [$style] [$use]")
91             if $c->debug;
92              
93             if ($style eq 'Document' && !$wrapped) {
94             return
95             (
96             Path => $path,
97             $self->_parse_SOAP_attr($c, $name, $style.$use)
98             );
99             } elsif ($style eq 'Document' && $wrapped) {
100             $self->doclitwrapped_endpoint_paths([]) unless $self->doclitwrapped_endpoint_paths;
101             $path =~ s/\/$//;
102             push @{$self->doclitwrapped_endpoint_paths}, $path
103             unless grep { $_ eq $path }
104             @{$self->doclitwrapped_endpoint_paths};
105             return
106             (
107             $self->_parse_SOAP_attr($c, $name, $style.$use)
108             );
109             } else {
110             $self->rpc_endpoint_paths([]) unless $self->rpc_endpoint_paths;
111             $path =~ s/\/$//;
112             push @{$self->rpc_endpoint_paths}, $path
113             unless grep { $_ eq $path }
114             @{$self->rpc_endpoint_paths};
115             return
116             (
117             $self->_parse_SOAP_attr($c, $name, $style.$use),
118             );
119             }
120             }
121              
122             sub _parse_WSDLPortWrapped_attr {
123             my ($self, $c, $name, $value) = @_;
124             my %attrs = $self->_parse_WSDLPort_attr($c, $name, $value, 'wrapped');
125             delete $attrs{Path};
126             return %attrs;
127             }
128              
129             # Let's create the rpc_endpoint action.
130             sub register_actions {
131             my $self = shift;
132             my ($c) = @_;
133             $self->SUPER::register_actions(@_);
134              
135             if ($self->rpc_endpoint_paths) {
136             my $namespace = $self->action_namespace($c);
137             my $action = $self->create_action
138             (
139             name => '___base_rpc_endpoint',
140             code => sub { },
141             reverse => ($namespace ? $namespace.'/' : '') . '___base_rpc_endpoint',
142             namespace => $namespace,
143             class => (ref $self || $self),
144             attributes => { ActionClass => [ 'Catalyst::Action::SOAP::RPCEndpoint' ],
145             Path => $self->rpc_endpoint_paths }
146             );
147             $c->dispatcher->register($c, $action);
148             }
149              
150             if ($self->doclitwrapped_endpoint_paths) {
151             my $namespace = $self->action_namespace($c);
152             my $action = $self->create_action
153             (
154             name => '___base_doclitwrapped_endpoint',
155             code => sub { },
156             reverse => ($namespace ? $namespace.'/' : '') . '___base_doclitwrapped_endpoint',
157             namespace => $namespace,
158             class => (ref $self || $self),
159             attributes => { ActionClass => [ 'Catalyst::Action::SOAP::DocumentLiteralWrapped' ],
160             Path => $self->doclitwrapped_endpoint_paths }
161             );
162             $c->dispatcher->register($c, $action);
163             }
164              
165             }
166              
167             sub _parse_SOAP_attr {
168             my ($self, $c, $name, $value) = @_;
169              
170             my $wsdlfile = $self->wsdl;
171             my $wsdlservice = $self->wsdlservice;
172             my $compile_opts = $self->xml_compile || {};
173             my $reader_opts = $compile_opts->{reader} || {};
174             my $writer_opts = $compile_opts->{writer} || {};
175              
176             if ($wsdlfile) {
177              
178             die 'WSDL initialization failed.'
179             unless $self->__init_wsdlobj($c);
180              
181             $self->ports({}) unless $self->ports();
182             my $operation = $self->wsdlobj->operation($name,
183             port => $self->ports->{$name},
184             service => $wsdlservice)
185             or die 'Every operation should be on the WSDL when using one.';
186              
187             my $in_message = $operation->{input_def}->{body}->{message};
188             my $in_namespace = $operation->{input_def}{body}{namespace};
189             my $out_message = $operation->{output_def}->{body}->{message};
190             my $out_namespace = $operation->{output_def}{body}{namespace};
191              
192             $c->log->debug("SOAP: ".$operation->name." ".($in_message||'(none)').' '.($out_message||'(none)'))
193             if $c->debug;
194              
195             if ($in_message) {
196             my $input_parts = $self->wsdlobj->findDef(message => $in_message)
197             ->{wsdl_part};
198              
199             for (@{$input_parts}) {
200             my $type = $_->{type} ? $_->{type} : $_->{element};
201             $c->log->debug("SOAP: @{[$operation->name]} input part $_->{name}, type $type")
202             if $c->debug;
203             $_->{compiled_reader} = $self->wsdlobj->compile
204             (READER => $type,
205             %$reader_opts);
206             };
207              
208             $self->decoders({}) unless $self->decoders();
209             $self->decoders->{$name} = sub {
210             my $body = shift;
211             my @nodes = grep { UNIVERSAL::isa($_, 'XML::LibXML::Element') } $body->childNodes();
212             return
213             {
214             map {
215             my $data = $_->{compiled_reader}->(shift @nodes);
216             $_->{name} => $data;
217             } @{$input_parts}
218             }, @nodes;
219             };
220             }
221              
222             my $name = $operation->name;
223             if ($out_message) {
224              
225             my $output_parts = $self->wsdlobj->findDef(message => $out_message)
226             ->{wsdl_part};
227             for (@{$output_parts}) {
228             my $type = $_->{type} ? $_->{type} : $_->{element};
229             $c->log->debug("SOAP: @{[$operation->name]} out part $_->{name}, type $type")
230             if $c->debug;
231             $_->{compiled_writer} = $self->wsdlobj->compile
232             (WRITER => $_->{type} ? $_->{type} : $_->{element},
233             elements_qualified => 'ALL',
234             %$writer_opts);
235             }
236              
237             $self->encoders({}) unless $self->encoders();
238             if ($operation->style eq 'rpc') {
239             $self->encoders->{$name} = sub {
240             my ($doc, $data) = @_;
241             my $element = $doc->createElementNS($out_namespace,$name.'Response');
242             $element->appendChild($_) for map {
243             $_->{compiled_writer}->($doc, $data->{$_->{name}})
244             } @{$output_parts};
245             return
246             [ $element ];
247             };
248              
249             } else {
250             $self->encoders->{$name} = sub {
251             my ($doc, $data) = @_;
252             return
253             [
254             map {
255             $_->{compiled_writer}->($doc, $data->{$_->{name}})
256             } @{$output_parts}
257             ];
258             };
259             }
260              
261             }
262             }
263              
264             my $actionclass = ($value =~ /^\+/ ? $value :
265             'SOAP::'.$value);
266             (
267             ActionClass => $actionclass,
268             )
269             }
270              
271             # this is implemented as to respond a SOAP message according to
272             # what has been sent to $c->stash->{soap}
273             sub end : Private {
274             my ($self, $c) = (shift, shift);
275             my $soap = $c->stash->{soap};
276              
277             return $self->maybe::next::method($c, @_)
278             unless $soap;
279              
280             if (scalar @{$c->error}) {
281             $c->stash->{soap}->fault
282             ({ code => '{'.NS_SOAP_ENV.'}Client',
283             reason => 'Unexpected Error', detail =>
284             'Unexpected error in the application: '.(join "\n", @{$c->error} ).'!'})
285             unless $c->stash->{soap}->fault;
286             $c->error(0);
287             }
288              
289             my $namespace = $soap->namespace || NS_SOAP_ENV;
290             my $response = XML::LibXML->createDocument('1.0','UTF8');
291              
292             my $envelope;
293              
294             if ($soap->fault) {
295              
296             $envelope = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Envelope");
297              
298             $response->setDocumentElement($envelope);
299              
300             my $body = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Body");
301              
302             $envelope->appendChild($body);
303              
304             my $fault = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Fault");
305             $body->appendChild($fault);
306              
307             my $code = $response->createElement("faultcode");
308             $fault->appendChild($code);
309             my $codestr = $soap->fault->{code};
310             if (my ($ns, $val) = $codestr =~ m/^\{(.+)\}(.+)$/) {
311             my $prefix = $fault->lookupNamespacePrefix($ns);
312             if ($prefix) {
313             $code->appendText($prefix.':'.$val);
314             } else {
315             $code->appendText($val);
316             }
317             } else {
318             $code->appendText('SOAP-ENV:'.$codestr);
319             }
320              
321             my $faultstring = $response->createElement("faultstring");
322             $fault->appendChild($faultstring);
323             $faultstring->appendText($soap->fault->{reason});
324              
325             if (UNIVERSAL::isa($soap->fault->{detail}, 'XML::LibXML::Node')) {
326             my $detail = $response->createElement("detail");
327             $detail->appendChild($soap->fault->{detail});
328             $fault->appendChild($detail);
329             } elsif ($soap->fault->{detail}) {
330             my $detail = $response->createElement("detail");
331             $fault->appendChild($detail);
332             # TODO: we don't support the xml:lang attribute yet.
333             my $text = $response->createElementNS
334             ('http://www.w3.org/2001/XMLSchema','xsd:documentation');
335             $detail->appendChild($text);
336             $text->appendText($soap->fault->{detail});
337             }
338             } else {
339              
340             if ($soap->string_return) {
341             $envelope = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Envelope");
342             $response->setDocumentElement($envelope);
343             my $body = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Body");
344             $envelope->appendChild($body);
345             $body->appendText($soap->string_return);
346             } elsif (my $lit = $soap->literal_return) {
347             $envelope = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Envelope");
348             $response->setDocumentElement($envelope);
349             my $body = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Body");
350             $envelope->appendChild($body);
351             if (ref $lit eq 'XML::LibXML::NodeList') {
352             for ($lit->get_nodelist) {
353             $body->appendChild($_);
354             }
355             } else {
356             $body->appendChild($lit);
357             }
358             } elsif (my $cmp = $soap->compile_return) {
359             $envelope = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Envelope");
360             $response->setDocumentElement($envelope);
361             my $body = $response->createElementNS(NS_SOAP_ENV, "SOAP-ENV:Body");
362             $envelope->appendChild($body);
363             die 'Tried to use compile_return without WSDL'
364             unless $self->wsdlobj;
365              
366             my $arr = $self->encoders->{$soap->operation_name}->($response, $cmp);
367              
368             $body->appendChild($_) for @$arr;
369             }
370             }
371              
372             if ($envelope) {
373             $c->log->debug("Outgoing XML: ".$envelope->toString()) if $c->debug;
374             $c->res->content_type('text/xml; charset=UTF-8');
375             $c->res->body(encode('utf8',$envelope->toString()));
376             } else {
377             $c->maybe::next::method($c, @_);
378             }
379             }
380              
381              
382             };
383              
384             { package Catalyst::Controller::SOAP::Helper;
385              
386             use base 'Class::Accessor::Fast';
387              
388             __PACKAGE__->mk_accessors(qw{envelope parsed_envelope arguments fault namespace
389             encoded_return literal_return string_return
390             compile_return operation_name});
391              
392              
393             };
394              
395              
396             1;
397              
398             __END__
399              
400             =head1 NAME
401              
402             Catalyst::Controller::SOAP - Catalyst SOAP Controller
403              
404             =head1 SYNOPSIS
405              
406             package MyApp::Controller::Example;
407             use base 'Catalyst::Controller::SOAP';
408              
409             # When using a WSDL, you can just specify the Port name, and it
410             # will infer the style and use. To do that, you just need to use
411             # the WSDLPort attribute. This might be required if your service
412             # has more than one port. This operation will be made available
413             # using the path part of the location attribute of the port
414             # definition.
415             __PACKAGE__->config->{wsdl} = 'file.wsdl';
416             sub servicefoo : WSDLPort('ServicePort') {}
417              
418             # available in "/example" as operation "ping". The arguments are
419             # treated as a literal document and passed to the method as a
420             # XML::LibXML object
421             # Using XML::Compile here will help you reading the message.
422             sub ping : SOAP('RPCLiteral') {
423             my ( $self, $c, $xml) = @_;
424             my $name = $xml->findValue('some xpath expression');
425             }
426              
427             # avaiable as "/example/world" in document context. The entire body
428             # is delivered to the method as a XML::LibXML object.
429             # Using XML::Compile here will help you reading the message.
430             sub world :Local SOAP('DocumentLiteral') {
431             my ($self, $c, $xml) = @_;
432             }
433              
434             # avaiable as "/example/get" in HTTP get context.
435             # the get attributes will be available as any other
436             # get operation in Catalyst.
437             sub get :Local SOAP('HTTPGet') {
438             my ($self, $c) = @_;
439             }
440              
441             # this is the endpoint from where the RPC operations will be
442             # dispatched. This code won't be executed at all.
443             # See Catalyst::Controller::SOAP::RPC.
444             sub index :Local SOAP('RPCEndpoint') {}
445              
446              
447             =head1 ABSTACT
448              
449             Implements SOAP serving support in Catalyst.
450              
451             =head1 DESCRIPTION
452              
453             SOAP Controller for Catalyst which we tried to make compatible with
454             the way Catalyst works with URLS.It is important to notice that this
455             controller declares by default an index operation which will dispatch
456             the RPC operations under this class.
457              
458             =head1 ATTRIBUTES
459              
460             This class implements the SOAP attribute wich is used to do the
461             mapping of that operation to the apropriate action class. The name of
462             the class used is formed as Catalyst::Action::SOAP::$value, unless the
463             parameter of the attribute starts with a '+', which implies complete
464             namespace.
465              
466             The implementation of SOAP Action classes helps delivering specific
467             SOAP scenarios, like HTTP GET, RPC Encoded, RPC Literal or Document
468             Literal, or even Document RDF or just about any required combination.
469              
470             See L<Catalyst::Action::SOAP::DocumentLiteral> for an example.
471              
472             =head1 ACCESSORS
473              
474             Once you tagged one of the methods, you'll have an $c->stash->{soap}
475             accessor which will return an C<Catalyst::Controller::SOAP::Helper>
476             object. It's important to notice that this is achieved by the fact
477             that all the SOAP Action classes are subclasses of
478             Catalyst::Action::SOAP, which implements most of that.
479              
480             You can query this object as follows:
481              
482             =over 4
483              
484             =item $c->stash->{soap}->envelope()
485              
486             The original SOAP envelope as string.
487              
488             =item $c->stash->{soap}->parsed_envelope()
489              
490             The parsed envelope as an XML::LibXML object.
491              
492             =item $c->stash->{soap}->arguments()
493              
494             The arguments of a RPC call.
495              
496             =item $c->stash->{soap}->fault({code => $code,reason => $reason, detail => $detail])
497              
498             Allows you to set fault code and message. Optionally, you may define
499             the code itself as an arrayref where the first item will be this code
500             and the second will be the subcode, which recursively may be another
501             arrayref.
502              
503             =item $c->stash->{soap}->encoded_return(\@data)
504              
505             This method will prepare the return value to be a soap encoded data.
506              
507             # TODO: At this moment, only Literals are working...
508              
509             =item $c->stash->{soap}->literal_return($xml_node)
510              
511             This method will prepare the return value to be a literal XML
512             document, in this case, you can pass just the node that will be the
513             root in the return message or a nodelist.
514              
515             Using XML::Compile will help to elaborate schema based returns.
516              
517             =item $c->stash->{soap}->string_return($non_xml_text)
518              
519             In this case, the given text is encoded as CDATA inside the SOAP
520             message.
521              
522             =back
523              
524             =head1 USING WSDL
525              
526             If you define the "wsdl" configuration key, Catalyst::Controller::SOAP
527             will automatically map your operations into the WSDL operations, in
528             which case you will receive the parsed Perl structure as returned by
529             XML::Compile according to the type defined in the WSDL message.
530              
531             You can define additional wsdl files or even additional schema
532             files. If $wsdl is an arrayref, the first element is the one passed to
533             new, and the others will be the argument to subsequent addWsdl calls.
534             If $wsdl is a hashref, the "wsdl" key will be handled like above and
535             the "schema" key will be used to importDefinitions. If the content of
536             the schema key is an arrayref, it will result in several calls to
537             importDefinition.
538              
539             When using WSDL, you can use the WSDLPort attribute, that not only
540             sets the port name but also infer which is the style of the binding,
541             the use of the input body and also declares the Path for the operation
542             according to the 'location' attribute in the WSDL file. For RPC
543             operations, the endpoint action will be created dinamically also in
544             the path defined by the WSDL file.
545              
546             This is the most convenient way of defining a SOAP service, which, in
547             the end, will require you to have it as simple as:
548              
549             package SOAPApp::Controller::WithWSDL;
550             use base 'Catalyst::Controller::SOAP';
551             __PACKAGE__->config->{wsdl} = 't/hello4.wsdl';
552            
553             # in this case, the input has two parts, named 'who' and 'greeting'
554             # and the output has a single 'greeting' part.
555             sub Greet : WSDLPort('Greet') {
556             my ( $self, $c, $args ) = @_;
557             my $who = $args->{who};
558             my $grt = $args->{greeting};
559             $c->stash->{soap}->compile_return({ greeting => $grt.' '.$who.'!' });
560             }
561              
562             When using WSDL with more than one port, the use of this attribute is
563             mandatory.
564              
565             When the WSDL describes more than one service, the controller can only
566             represent one of them, so you must define the 'wsdlservice' config key
567             that will be used to select the service.
568              
569             Also, when using wsdl, you can define the response using:
570              
571             $c->stash->{soap}->compile_return($perl_structure)
572              
573             In this case, the given structure will be transformed by XML::Compile,
574             according to what's described in the WSDL file.
575              
576             If you define "xml_compile" as a configuration key (which is a
577             hashref with keys 'reader' and 'writer', which both have a hashref
578             as their value), those key / value pairs will be passed as options
579             to the XML::Compile::Schema::compile() method.
580              
581             __PACKAGE__->config->{xml_compile} = {
582             reader => {sloppy_integers => 1}, writer => {sloppy_integers => 1},
583             };
584              
585             =head1 Support for Document/Literal-Wrapped
586              
587             Please make sure you read the documentation at
588             L<Catalyst::Action::SOAP::DocumentLiteralWrapped> before using this
589             feature.
590              
591             The support for Document/Literal-Wrapped works by faking RPC style
592             even when the WSDL says the service is in the "Document" mode. The
593             parameter used for the actual dispatch is the soapAction attribute.
594              
595             In practice, the endpoint of the action is an empty action that will
596             redirect the request to the actual action based on the name of the
597             soapAction. It uses the soap_action_prefix controller configuration
598             variable to extract the name of the action.
599              
600             There is an important restriction in that fact. The name of the
601             operation in the WSDL must match the suffix of the soapAction
602             attribute.
603              
604             If you have a Document/Literal-Wrapped WSDL and rewriting it as
605             RPC/Literal is not an option, take the following steps:
606              
607             =over
608              
609             =item Set the soap_action_prefix
610              
611             It is assumed that all operations in the port have a common prefix, it
612             is also assumed that when the prefix is removed, the remaining string
613             can be used as a subroutine name (i.e.: it should not contain
614             additional slashes).
615              
616             __PACKAGE__->config->{soap_action_prefix} = 'http://foo.com/bar/';
617              
618             =item Make sure the soapAction attribute is consistent with the operation name
619              
620             Since the dispatching mechanism is dissociated from the
621             encoding/decoding process, the only way for this to work is to have
622             the soapAction to be consistent with the operation name.
623              
624             <!--- inside the WSDL binding section -->
625             <wsdl:operation name="Greet">
626             <soap:operation soapAction="http://foo.com/bar/Greet" />
627             <wsdl:input>
628             ...
629              
630             Note that the name of the operation is "Greet" and the soapAction
631             attribute is the result of concatenating the soap_action_prefix and
632             the name of the operation.
633              
634             =item Implement the action using the WSDLPortWrapped action attribute
635              
636             Instead of using the standard WSDLPort attribute, use the alternative
637             implementation that will provide the extra dispatching.
638              
639             sub Greet :WSDLPortWrapped('GreetPort') {
640             ...
641             }
642              
643             Note that the name of the sub is consistent with the name of the
644             operation.
645              
646             =back
647              
648             But always try to refactor your WSDL as RPC/Literal instead, which is
649             much more predictable and, in fact, is going to provide you a much
650             more sane WSDL file.
651              
652             =head1 USING WSDL AND Catalyst::Test
653              
654             If you'd like to use the built-in server from Catalyst::Test with your
655             WSDL file (which likely defines an <address location="..."> that differs
656             from the standard test server) you'll need to use the transport_hook
657             option available with $wsdl->compileClient() in your test file.
658              
659              
660             # t/soap_message.t
661             use XML::Compile::WSDL11;
662             use XML::Compile::Transport::SOAPHTTP;
663             use Test::More 'no_plan';
664              
665             BEGIN {
666             use_ok 'Catalyst::Test', 'MyServer';
667             }
668              
669             sub proxy_to_test_app
670             {
671             my ($request, $trace) = @_;
672             # request() is a function inserted by Catalyst::Test which
673             # sends HTTP requests to the just-started test server.
674             return request($request);
675             }
676              
677             my $xml = '/path/to/wsdl/file';
678             my $message = 'YourMessage';
679             my $port_name = 'YourPort';
680             my $wsdl = XML::Compile::WSDL11->new($xml);
681             my $client = $wsdl->compileClient($message,
682             port => $port_name, transport_hook => \&proxy_to_test_app,
683             );
684             $client->(...);
685              
686              
687             =head1 TODO
688              
689             No header features are implemented yet.
690              
691             The SOAP Encoding support is also missing, when that is ready you'll
692             be able to do something like the code below:
693              
694             # available in "/example" as operation "echo"
695             # parsing the arguments as soap-encoded.
696             sub echo : SOAP('RPCEncoded') {
697             my ( $self, $c, @args ) = @_;
698             }
699              
700             =head1 SEE ALSO
701              
702             L<Catalyst::Action::SOAP>, L<XML::LibXML>, L<XML::Compile>
703             L<Catalyst::Action::SOAP::DocumentLiteral>,
704             L<Catalyst::Action::SOAP::RPCLiteral>,
705             L<Catalyst::Action::SOAP::HTTPGet>, L<XML::Compile::WSDL11>,
706             L<XML::Compile::Schema>
707              
708             =head1 AUTHORS
709              
710             Daniel Ruoso C<daniel@ruoso.com>
711              
712             Drew Taylor C<drew@drewtaylor.com>
713              
714             Georg Oechsler C<goe-cpan@space.net>
715              
716             =head1 BUG REPORTS
717              
718             Please submit all bugs regarding C<Catalyst::Controller::SOAP> to
719             C<bug-catalyst-controller-soap@rt.cpan.org>
720              
721             =head1 LICENSE
722              
723             This library is free software, you can redistribute it and/or modify
724             it under the same terms as Perl itself.
725              
726             =cut