File Coverage

lib/XML/Compile/Transport/SOAPHTTP.pm
Criterion Covered Total %
statement 145 180 80.5
branch 24 66 36.3
condition 17 41 41.4
subroutine 25 30 83.3
pod 3 5 60.0
total 214 322 66.4


line stmt bran cond sub pod time code
1             # Copyrights 2007-2021 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution XML-Compile-SOAP. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package XML::Compile::Transport::SOAPHTTP;
10 3     3   7386 use vars '$VERSION';
  3         7  
  3         159  
11             $VERSION = '3.27';
12              
13 3     3   20 use base 'XML::Compile::Transport';
  3         10  
  3         986  
14              
15 3     3   21 use warnings;
  3         7  
  3         77  
16 3     3   18 use strict;
  3         7  
  3         61  
17              
18 3     3   14 use Log::Report 'xml-compile-soap';
  3         5  
  3         13  
19              
20 3     3   734 use XML::Compile::SOAP::Util qw/SOAP11ENV SOAP11HTTP/;
  3         5  
  3         156  
21 3     3   21 use XML::Compile ();
  3         5  
  3         74  
22              
23 3     3   1966 use LWP ();
  3         113735  
  3         84  
24 3     3   35 use LWP::UserAgent ();
  3         6  
  3         44  
25 3     3   15 use HTTP::Request ();
  3         6  
  3         39  
26 3     3   13 use HTTP::Headers ();
  3         8  
  3         43  
27 3     3   14 use Encode;
  3         7  
  3         3673  
28              
29             # (Microsofts HTTP Extension Framework)
30             my $http_ext_id = SOAP11ENV;
31              
32             my $mime_xop = 'application/xop+xml';
33              
34             __PACKAGE__->register(SOAP11HTTP);
35              
36              
37             sub init($)
38 2     2 0 6 { my ($self, $args) = @_;
39 2         17 $self->SUPER::init($args);
40              
41             $self->userAgent
42             ( $args->{user_agent}
43             , keep_alive => (exists $args->{keep_alive} ? $args->{keep_alive} : 1)
44 2 50 50     23 , timeout => ($args->{timeout} || 180)
45             );
46 2         5078 $self;
47             }
48              
49             sub initWSDL11($)
50 0     0 0 0 { my ($class, $wsdl) = @_;
51 0         0 trace "initialize SOAPHTTP transporter for WSDL11";
52             }
53              
54             #-------------------------------------------
55              
56              
57             my $default_ua;
58             sub userAgent(;$)
59 4     4 1 13 { my ($self, $agent) = (shift, shift);
60 4 50       14 return $self->{user_agent} = $agent
61             if defined $agent;
62              
63 4   33     84 $self->{user_agent} ||= $default_ua ||= LWP::UserAgent->new
      66        
64             ( requests_redirectable => [ qw/GET HEAD POST M-POST/ ]
65             , protocols_allowed => [ qw/http https/ ]
66             , parse_head => 0
67             , @_
68             );
69             }
70              
71              
72 0     0 1 0 sub defaultUserAgent() { $default_ua }
73              
74             #-------------------------------------------
75              
76              
77             # SUPER::compileClient() calls this method to do the real work
78             sub _prepare_call($)
79 2     2   7 { my ($self, $args) = @_;
80 2   50     15 my $method = $args->{method} || 'POST';
81 2   50     14 my $soap = $args->{soap} || 'SOAP11';
82 2 50       8 my $version = ref $soap ? $soap->version : $soap;
83 2   50     12 my $mpost_id = $args->{mpost_id} || 42;
84 2         4 my $action = $args->{action};
85 2         5 my $mime = $args->{mime};
86 2   50     10 my $kind = $args->{kind} || 'request-response';
87 2   33     14 my $expect = $kind ne 'one-way' && $kind ne 'notification-operation';
88              
89 2         14 my $charset = $self->charset;
90 2         9 my $ua = $self->userAgent;
91              
92             # Prepare header
93 2   33     24 my $header = $args->{header} || HTTP::Headers->new;
94 2         33 $self->headerAddVersions($header);
95              
96             # There is probably never a real HTTP server on the other side, but
97             # HTTP/1.1 requires this.
98             $header->header(Host => $1)
99 2 50 50     126 if +($args->{endpoint} // '') =~ m!^\w+\://([^/:]+)!;
100              
101 2         6 my $content_type;
102 2 50       11 if($version eq 'SOAP11')
    0          
103 2   50     13 { $mime ||= 'text/xml';
104 2         9 $content_type = qq{$mime; charset=$charset};
105             }
106             elsif($version eq 'SOAP12')
107 0   0     0 { $mime ||= 'application/soap+xml';
108 0 0       0 my $sa = defined $action ? qq{; action="$action"} : '';
109 0         0 $content_type = qq{$mime; charset=$charset$sa};
110 0         0 $header->header(Accept => $mime); # not the HTML answer
111             }
112             else
113 0         0 { error "SOAP version {version} not implemented", version => $version;
114             }
115              
116 2 50       12 if($method eq 'POST')
    0          
117             { # should only be used by SOAP11, but you never know. So, SOAP12
118             # will have the action both ways.
119 2 50       11 $header->header(SOAPAction => qq{"$action"})
120             if defined $action;
121             }
122             elsif($method eq 'M-POST')
123 0         0 { $header->header(Man => qq{"$http_ext_id"; ns=$mpost_id});
124 0 0       0 $header->header("$mpost_id-SOAPAction", qq{"$action"})
125             if $version eq 'SOAP11';
126             }
127             else
128 0         0 { error "SOAP method must be POST or M-POST, not {method}"
129             , method => $method;
130             }
131              
132             # Prepare request
133              
134             # Ideally, we should change server when one fails, and stick to that
135             # one as long as possible.
136 2         16 my $server = $self->address;
137              
138             # Create handler
139              
140             my ($create_message, $parse_message)
141 2 100       16 = exists $INC{'XML/Compile/XOP.pm'}
142             ? $self->_prepare_xop_call($content_type)
143             : $self->_prepare_simple_call($content_type);
144              
145 2 50       8 $parse_message = $self->_prepare_for_no_answer($parse_message)
146             unless $expect;
147              
148 2         5 my $hook = $args->{hook};
149              
150             $hook
151             ? sub # hooked code
152 2     2   6 { my $trace = $_[1];
153              
154 2         21 my $request = HTTP::Request->new($method => $server, $header);
155 2         18660 $request->protocol('HTTP/1.1');
156 2         41 $create_message->($request, $_[0], $_[2]);
157            
158 2         58 $trace->{http_request} = $request;
159 2         6 $trace->{action} = $action;
160 2         6 $trace->{soap_version} = $version;
161 2         6 $trace->{server} = $server;
162 2         6 $trace->{user_agent} = $ua;
163 2         6 $trace->{hooked} = 1;
164              
165 2 50       11 my $response = $hook->($request, $trace, $self)
166             or return undef;
167              
168 2 50       9115 UNIVERSAL::isa($response, 'HTTP::Response')
169             or error __x"transport_hook must produce a HTTP::Response, got {resp}"
170             , resp => $response;
171              
172 2         7 $trace->{http_response} = $response;
173 2 50       10 if($response->is_error)
174 0 0       0 { error $response->message
175             if $response->header('Client-Warning');
176              
177 0         0 warning $response->message;
178             # still try to parse the response for Fault blocks
179             }
180              
181 2         28 $parse_message->($response);
182             }
183              
184             : sub # real call
185 0     0   0 { my $trace = $_[1];
186              
187 0         0 my $request = HTTP::Request->new($method => $server, $header);
188 0         0 $request->protocol('HTTP/1.1');
189 0         0 $create_message->($request, $_[0], $_[2]);
190              
191 0         0 $trace->{http_request} = $request;
192              
193 0 0       0 my $response = $ua->request($request)
194             or return undef;
195              
196 0         0 $trace->{http_response} = $response;
197              
198 0 0       0 if($response->is_error)
199 0 0       0 { error $response->message
200             if $response->header('Client-Warning');
201              
202 0         0 warning $response->message;
203             # still try to parse the response for Fault blocks
204             }
205              
206 0         0 $parse_message->($response);
207 2 50       17 };
208             }
209              
210             sub _prepare_simple_call($)
211 2     2   7 { my ($self, $content_type) = @_;
212              
213             my $create = sub
214 1     1   4 { my ($request, $content) = @_;
215 1         16 $request->header(Content_Type => $content_type);
216 1         137 $request->content_ref($content); # already bytes (not utf-8)
217 3     3   29 use bytes; $request->header('Content-Length' => length $$content);
  3         6  
  3         77  
  1         30  
218 2         14 };
219              
220             my $parse = sub
221 1     1   3 { my $response = shift;
222 1 50       5 UNIVERSAL::isa($response, 'HTTP::Response')
223             or error __x"no response object received";
224              
225 1   50     11 my $ct = $response->content_type || '';
226 1 50       60 lc($ct) ne 'multipart/related'
227             or error __x"remote system uses XOP, use XML::Compile::XOP";
228            
229 1         5 trace "received ".$response->status_line;
230              
231 1 50       121 $ct =~ m,[/+]xml$,i
232             or error __x"answer is not xml but `{type}'", type => $ct;
233              
234             # HTTP::Message::decoded_content() does not work for old Perls
235 1   33     7 my $content = $response->decoded_content(ref => 1)
236             || $response->content(ref => 1);
237              
238 1         327 ($content, {});
239 2         19 };
240              
241 2         12 ($create, $parse);
242             }
243              
244             sub _prepare_xop_call($)
245 1     1   16 { my ($self, $content_type) = @_;
246              
247 1         8 my ($simple_create, $simple_parse)
248             = $self->_prepare_simple_call($content_type);
249              
250 1         6 my $charset = $self->charset;
251             my $create = sub
252 1     1   3 { my ($request, $content, $mtom) = @_;
253 1   50     4 $mtom ||= [];
254 1 50       3 @$mtom or return $simple_create->($request, $content);
255              
256 1         52 my $bound = "MIME-boundary-".int rand 10000;
257 1         8 (my $start_cid = $mtom->[0]->cid) =~ s/^.*\@/xml@/;
258              
259 1         3 my $si = "$content_type";
260 1         3 $si =~ s/\"/\\"/g;
261 1         41 $request->header(Content_Type => <<__CT);
262             multipart/related;
263             boundary="$bound";
264             type="$mime_xop";
265             start="<$start_cid>";
266             start-info="$si"
267             __CT
268              
269 1         83 my $base = HTTP::Message->new
270             ( [ Content_Type => qq{$mime_xop; charset="$charset"; type="$si"}
271             , Content_Transfer_Encoding => '8bit'
272             , Content_ID => "<$start_cid>"
273             ] );
274 1         156 $base->content_ref($content); # already bytes (not utf-8)
275              
276 1         23 my @parts = ($base, map $_->mimePart, @$mtom);
277 1         8 $request->parts(@parts); #$base, map $_->mimePart, @$mtom);
278 1         104 $request;
279 1         8 };
280              
281             my $parse = sub
282 1     1   3 { my ($response, $mtom) = @_;
283 1   50     6 my $ct = $response->header('Content-Type') || '';
284 1 50       516 $ct =~ m!^\s*multipart/related\s*\;!i
285             or return $simple_parse->($response);
286              
287 1         3 my (@parts, %parts);
288 1         5 foreach my $part ($response->parts)
289 2 50       20 { my $include = XML::Compile::XOP::Include->fromMime($part)
290             or next;
291 2         7 $parts{$include->cid} = $include;
292 2         5 push @parts, $include;
293             }
294              
295             @parts
296 1 50       4 or error "no parts in response multi-part for XOP";
297              
298 1         3 my $root;
299 1 50       8 if($ct =~ m!start\=(["']?)\<([^"']*)\>\1!)
300 1         3 { my $startid = $2;
301 1         3 $root = delete $parts{$startid};
302 1 50       4 defined $root
303             or warning __x"cannot find root node id in parts `{id}'"
304             , id => $startid;
305             }
306 1 50       3 unless($root)
307 0         0 { $root = shift @parts;
308 0         0 delete $parts{$root->cid};
309             }
310              
311 1         4 ($root->content(1), \%parts);
312 1         6 };
313              
314 1         3 ($create, $parse);
315             }
316              
317             sub _prepare_for_no_answer($)
318 0     0   0 { my $self = shift;
319             sub
320 0     0   0 { my $response = shift;
321 0   0     0 my $ct = $response->content_type || '';
322              
323 0         0 trace "received ".$response->status_line;
324              
325 0         0 my $content = '';
326 0 0       0 if($ct =~ m,[/+]xml$,i)
327             { # HTTP::Message::decoded_content() does not work for old Perls
328 0 0       0 $content = $] >= 5.008 ? $response->decoded_content(ref => 1)
329             : $response->content(ref => 1);
330             }
331              
332 0         0 ($content, {});
333 0         0 };
334             }
335              
336              
337             sub headerAddVersions($)
338 2     2 1 6 { my ($thing, $h) = @_;
339 2         8 foreach my $pkg (qw/XML::Compile XML::Compile::Cache
340             XML::Compile::SOAP XML::LibXML LWP/)
341 3     3   2953 { no strict 'refs';
  3         7  
  3         406  
342 10   50     499 my $version = ${"${pkg}::VERSION"} || 'undef';
343 10         44 (my $field = "X-$pkg-Version") =~ s/\:\:/-/g;
344 10         29 $h->header($field => $version);
345             }
346             }
347              
348             1;