File Coverage

blib/lib/HTTP/Request/CurlParameters.pm
Criterion Covered Total %
statement 310 476 65.1
branch 106 202 52.4
condition 29 72 40.2
subroutine 27 32 84.3
pod 5 9 55.5
total 477 791 60.3


line stmt bran cond sub pod time code
1             package HTTP::Request::CurlParameters 0.57;
2 19     19   80990 use 5.020;
  19         58  
3 19     19   1048 use HTTP::Request;
  19         51072  
  19         345  
4 19     19   1258 use HTTP::Request::Common;
  19         5328  
  19         1217  
5 19     19   95 use URI;
  19         27  
  19         337  
6 19     19   56 use File::Spec::Unix;
  19         28  
  19         464  
7 19     19   70 use List::Util 'pairmap';
  19         29  
  19         1985  
8 19     19   7263 use PerlX::Maybe;
  19         41143  
  19         97  
9 19     19   791 use Carp 'croak';
  19         27  
  19         948  
10              
11 19     19   8794 use Moo 2;
  19         140962  
  19         115  
12 19     19   24416 use feature 'signatures';
  19         29  
  19         2592  
13 19     19   127 no warnings 'experimental::signatures';
  19         27  
  19         140425  
14              
15             =head1 NAME
16              
17             HTTP::Request::CurlParameters - container for a Curl-like HTTP request
18              
19             =head1 SYNOPSIS
20              
21             my $ua = LWP::UserAgent->new;
22             my $params = HTTP::Request::CurlParameters->new(argv => \@ARGV);
23             my $response = $ua->request($params->as_request);
24              
25             =head1 DESCRIPTION
26              
27             Objects of this class are mostly created from L. Most
28             likely you want to use that module instead:
29              
30             my $ua = LWP::UserAgent->new;
31             my $params = HTTP::Request::FromCurl->new(command_curl => $cmd);
32             my $response = $ua->request($params->as_request);
33              
34             =head1 METHODS
35              
36             =head2 C<< ->new >>
37              
38             Options:
39              
40             =over 4
41              
42             =item *
43              
44             C
45              
46             method => 'GET'
47              
48             The HTTP method to use.
49              
50             =cut
51              
52             has method => (
53             is => 'ro',
54             default => 'GET',
55             );
56              
57             =item *
58              
59             C
60              
61             uri => 'https://example.com'
62              
63             The URI of the request.
64              
65             =cut
66              
67             has uri => (
68             is => 'ro',
69             default => 'https://example.com',
70             );
71              
72             =item *
73              
74             C
75              
76             headers => {
77             'Content-Type' => 'text/json',
78             'X-Secret' => ['value-1', 'value-2'],
79             }
80              
81             The headers of the request. Multiple headers with the same
82             name can be passed as an arrayref to the header key.
83              
84             =cut
85              
86             has headers => (
87             is => 'ro',
88             default => sub { {} },
89             );
90              
91             =item *
92              
93             C
94              
95             The cookie jar to use.
96              
97             =cut
98              
99             has cookie_jar => (
100             is => 'ro',
101             );
102              
103             =item *
104              
105             C
106              
107             Options for the constructor of the cookie jar.
108              
109             =cut
110              
111             has cookie_jar_options => (
112             is => 'ro',
113             default => sub { {} },
114             );
115              
116             =item *
117              
118             C
119              
120             credentials => 'hunter2:secret'
121              
122             The credentials to use for basic authentication.
123              
124             =cut
125              
126             has credentials => (
127             is => 'ro',
128             );
129              
130             =item *
131              
132             C
133              
134             auth => 'basic'
135              
136             The authentication method to use.
137              
138             =cut
139              
140             has auth => (
141             is => 'ro',
142             );
143              
144             =item *
145              
146             C
147              
148             post_data => ['A string','across multiple','scalars']
149              
150             The POST body to use.
151              
152             =cut
153              
154             has post_data => (
155             is => 'ro',
156             default => sub { [] },
157             );
158              
159             =item *
160              
161             C
162              
163             body => '{"greeting":"Hello"}'
164              
165             The body of the request.
166              
167             =cut
168              
169             has body => (
170             is => 'ro',
171             );
172              
173             =item *
174              
175             C
176              
177             timeout => 50
178              
179             The timeout for the request
180              
181             =cut
182              
183             has timeout => (
184             is => 'ro',
185             );
186              
187             =item *
188              
189             C
190              
191             unix_socket => '/var/run/docker/docker.sock'
192              
193             The timeout for the request
194              
195             =cut
196              
197             has unix_socket => (
198             is => 'ro',
199             );
200              
201             =item *
202              
203             C
204              
205             local_address => '192.0.2.116'
206              
207             The local network address to bind to when making the request
208              
209             =cut
210              
211             has local_address => (
212             is => 'ro',
213             );
214              
215             =item *
216              
217             C
218              
219             The HTML form parameters. These get converted into
220             a body.
221              
222             =cut
223              
224             has form_args => (
225             is => 'ro',
226             default => sub { [] },
227             );
228              
229             =item *
230              
231             C
232              
233             insecure => 1
234              
235             Disable SSL certificate verification
236              
237             =cut
238              
239             has insecure => (
240             is => 'ro',
241             );
242              
243             =item *
244              
245             C
246              
247             cert => '/path/to/certificate',
248              
249             Use the certificate file for SSL
250              
251             =cut
252              
253             has cert => (
254             is => 'ro',
255             );
256              
257             =item *
258              
259             C
260              
261             capath => '/path/to/cadir/',
262              
263             Use the certificate directory for SSL
264              
265             =cut
266              
267             has capath => (
268             is => 'ro',
269             );
270              
271             =item *
272              
273             C
274              
275             Name of the output file
276              
277             =cut
278              
279             has output => (
280             is => 'ro',
281             );
282              
283             =item *
284              
285             C
286              
287             Maximum size (in bytes) of a file to download
288              
289             =cut
290              
291             has max_filesize => (
292             is => 'ro',
293             );
294              
295             =item *
296              
297             C
298              
299             show_error => 0
300              
301             Show error message on HTTP errors
302              
303             =cut
304              
305             has show_error => (
306             is => 'ro',
307             );
308              
309             =item *
310              
311             C
312              
313             fail => 1
314              
315             Let the Perl code C on error
316              
317             =back
318              
319             =cut
320              
321             has fail => (
322             is => 'ro',
323             );
324              
325 179     179   342 sub _build_quoted_body( $self, $body=$self->body ) {
  179         275  
  179         534  
  179         303  
326 179 100       684 if( defined $body ) {
327 26         246 $body =~ s!([\x00-\x1f'"\$\@\%\\])!sprintf '\\x%02x', ord $1!ge;
  88         311  
328 26         222 return sprintf qq{"%s"}, $body
329              
330             } else {
331             # Sluuuurp
332             my @post_data = map {
333 0 0       0 /^\@(.*)/ ? do {
334 0 0       0 open my $fh, '<', $1
335             or die "$1: $!";
336 0         0 local $/; # / for Filter::Simple
337 0         0 binmode $fh;
338             <$fh>
339 0         0 }
340             : $_
341 153         248 } @{ $self->post_data };
  153         576  
342 153         790 return join "", @post_data;
343             }
344             };
345              
346             =head2 C<< ->as_request >>
347              
348             $ua->request( $r->as_request );
349              
350             Returns an equivalent L object
351              
352             =cut
353              
354 261     261   1622 sub _explode_headers( $self ) {
  261         537  
  261         508  
355             my @res =
356 858         1271 map { my $h = $_;
357 858         2224 my $v = $self->headers->{$h};
358 858 100       4352 ref $v ? (map { $h => $_ } @$v)
  8         56  
359             : ($h => $v)
360 261         556 } keys %{ $self->headers };
  261         1714  
361             }
362              
363             =head2 C<< $r->as_request >>
364              
365             my $r = $curl->as_request;
366              
367             Returns a L object that represents
368             the Curl options.
369              
370             =cut
371              
372 81     81 1 5825861 sub as_request( $self ) {
  81         321  
  81         260  
373 81         940 HTTP::Request->new(
374             $self->method => $self->uri,
375             [ $self->_explode_headers() ],
376             $self->body(),
377             )
378             };
379              
380 0     0   0 sub _fill_snippet( $self, $snippet ) {
  0         0  
  0         0  
  0         0  
381             # Doesn't parse parameters, yet
382 0         0 $snippet =~ s!\$self->(\w+)!$self->$1!ge;
  0         0  
383 0         0 $snippet
384             }
385              
386 88     88   162 sub _init_cookie_jar_lwp( $self ) {
  88         225  
  88         120  
387 88 100       463 if( my $fn = $self->cookie_jar ) {
388 2 100       16 my $save = $self->cookie_jar_options->{'write'} ? 1 : 0;
389             return {
390 2         21 preamble => [
391             "use Path::Tiny;",
392             "use HTTP::Cookies;",
393             ],
394             code => \"HTTP::Cookies->new(\n file => path('$fn'),\n autosave => $save,\n)",
395             postamble => [
396             #"path('$fn')->spew(\$ua->cookie_jar->dump_cookies())",
397             ],
398             };
399             }
400             }
401              
402 86     86   189 sub _init_cookie_jar_tiny( $self ) {
  86         183  
  86         207  
403 86 100       564 if( my $fn = $self->cookie_jar ) {
404 2         16 my $save = $self->cookie_jar_options->{'write'};
405             return {
406 2 100       65 preamble => [
407             "use Path::Tiny;",
408             "use HTTP::CookieJar;",
409             ],
410             code => \"HTTP::CookieJar->new->load_cookies(path('$fn')->lines),",
411             postamble => [
412             $save ?
413             ("path('$fn')->spew(\$ua->cookie_jar->dump_cookies())")
414             : (),
415             ],
416             };
417             }
418             }
419              
420 5     5   17 sub _init_cookie_jar_mojolicious( $self ) {
  5         9  
  5         23  
421 5 50       55 if( my $fn = $self->cookie_jar ) {
422 0         0 my $save = $self->cookie_jar_options->{'write'};
423             return {
424 0         0 preamble => [
425             # "use Path::Tiny;",
426             "use Mojo::UserAgent::CookieJar;",
427             ],
428             code => \"Mojo::UserAgent::CookieJar->new,",
429             postamble => [
430             #$save ?
431             # ("path('$fn')->spew(\$ua->cookie_jar->dump_cookies())")
432             # : (),
433             ],
434             };
435             }
436             }
437              
438 540     540   9393 sub _pairlist( $self, $l, $prefix = " " ) {
  540         842  
  540         861  
  540         1247  
  540         714  
439             return join ",\n",
440             pairmap { my $v = ! ref $b ? qq{'$b'}
441             : ref $b eq 'SCALAR' ? $$b
442 6         30 : ref $b eq 'ARRAY' ? '[' . join( ", ", map {qq{'$_'}} @$b ) . ']'
443 611 50   611   1649 : ref $b eq 'HASH' ? '{' . $self->_pairlist([ map { $_ => $b->{$_} } sort keys %$b ]) . '}'
  3 100       16  
    100          
    100          
444             : die "Unknown type of $b";
445 611         4943 qq{$prefix'$a' => $v}
446 540         8903 } @$l
447             }
448              
449 88     88   202 sub _build_lwp_headers( $self, $prefix = " ", %options ) {
  88         164  
  88         331  
  88         321  
  88         131  
450             # This is so we create the standard header order in our output
451 88         217 my @h = $self->_explode_headers;
452 88         449 my $h = HTTP::Headers->new( @h );
453 88         8387 $h->remove_header( @{$options{implicit_headers}} );
  88         500  
454              
455             # also skip the Host: header if it derives from $uri
456 88         1093 my $val = $h->header('Host');
457 88 100 66     3964 if( $val and ($val eq $self->uri->host_port
      66        
458             or $val eq $self->uri->host )) {
459             # trivial host header
460 81         3263 $h->remove_header('Host');
461             };
462              
463 88         2759 $self->_pairlist([ $h->flatten ], $prefix);
464             }
465              
466 86     86   137 sub _build_tiny_headers( $self, $prefix = " ", %options ) {
  86         135  
  86         300  
  86         232  
  86         135  
467 86         261 my @h = $self->_explode_headers;
468 86         863 my $h = HTTP::Headers->new( @h );
469 86         9905 $h->remove_header( @{$options{implicit_headers}} );
  86         369  
470              
471             # HTTP::Tiny does not like overriding the Host: header :-/
472 86         1456 $h->remove_header('Host');
473              
474 86         1193 @h = $h->flatten;
475 86         7443 my %h;
476             my @order;
477 86         265 while( @h ) {
478 203         432 my ($k,$v) = splice(@h,0,2);
479 203 100       410 if( ! exists $h{ $k }) {
    50          
480             # Fresh value
481 202         392 $h{ $k } = $v;
482 202         480 push @order, $k;
483             } elsif( ! ref $h{$k}) {
484             # Second value
485 1         10 $h{ $k } = [$h{$k}, $v];
486             } else {
487             # Multiple values
488 0         0 push @{$h{ $k }}, $v;
  0         0  
489             }
490             };
491              
492 86         203 $self->_pairlist([ map { $_ => $h{ $_ } } @order ], $prefix);
  202         521  
493             }
494              
495 5     5   9 sub _build_mojolicious_headers( $self, $prefix = " ", %options ) {
  5         9  
  5         28  
  5         15  
  5         10  
496             # This is so we create the standard header order in our output
497 5         24 my @h = $self->_explode_headers;
498 5         166 my $h = HTTP::Headers->new( @h );
499 5         316 $h->remove_header( @{$options{implicit_headers}} );
  5         30  
500              
501             # also skip the Host: header if it derives from $uri
502 5         98 my $val = $h->header('Host');
503 5 0 0     352 if( $val and ($val eq $self->uri->host_port
      33        
504             or $val eq $self->uri->host )) {
505             # trivial host header
506 0         0 $h->remove_header('Host');
507             };
508              
509 5         38 @h = $h->flatten;
510 5         319 my %h;
511             my @order;
512 5         18 while( @h ) {
513 4         30 my ($k,$v) = splice(@h,0,2);
514 4 50       20 if( ! exists $h{ $k }) {
    0          
515             # Fresh value
516 4         14 $h{ $k } = $v;
517 4         173 push @order, $k;
518             } elsif( ! ref $h{$k}) {
519             # Second value
520 0         0 $h{ $k } = [$h{$k}, $v];
521             } else {
522             # Multiple values
523 0         0 push @{$h{ $k }}, $v;
  0         0  
524             }
525             };
526              
527 5         17 $self->_pairlist([ map { $_ => $h{ $_ } } @order ], $prefix);
  4         21  
528             }
529              
530 0     0   0 sub _build_rest_client_headers( $self, $prefix = " ", %options ) {
  0         0  
  0         0  
  0         0  
  0         0  
531             # This is so we create the standard header order in our output
532 0         0 my @h = $self->_explode_headers;
533 0         0 my $h = HTTP::Headers->new( @h );
534 0         0 $h->remove_header( @{$options{implicit_headers}} );
  0         0  
535              
536             # also skip the Host: header if it derives from $uri
537 0         0 my $val = $h->header('Host');
538 0 0 0     0 if( $val and ($val eq $self->uri->host_port
      0        
539             or $val eq $self->uri->host )) {
540             # trivial host header
541 0         0 $h->remove_header('Host');
542             };
543              
544 0         0 @h = $h->flatten;
545 0         0 my %h;
546             my @order;
547 0         0 while( @h ) {
548 0         0 my ($k,$v) = splice(@h,0,2);
549 0 0       0 if( ! exists $h{ $k }) {
    0          
550             # Fresh value
551 0         0 $h{ $k } = $v;
552 0         0 push @order, $k;
553             } elsif( ! ref $h{$k}) {
554             # Second value
555 0         0 $h{ $k } = [$h{$k}, $v];
556             } else {
557             # Multiple values
558 0         0 push @{$h{ $k }}, $v;
  0         0  
559             }
560             };
561              
562 0         0 $self->_pairlist([ map { $_ => $h{ $_ } } @order ], $prefix);
  0         0  
563             }
564              
565             =head2 C<< $r->as_snippet( %options ) >>
566              
567             print $r->as_snippet( type => 'LWP' );
568              
569             Returns a code snippet that returns code to create an equivalent
570             L object and to perform the request using L.
571              
572             This is mostly intended as a convenience function for creating Perl demo
573             snippets from C examples.
574              
575             =head3 Options
576              
577             =over 4
578              
579             =item B
580              
581             Arrayref of headers that will not be output.
582              
583             Convenient values are ['Content-Length']
584              
585             =item B
586              
587             type => 'Tiny',
588              
589             Type of snippet. Valid values are C for L,
590             C for L
591             and C for L.
592              
593             =back
594              
595             =cut
596              
597 179     179 1 37460173 sub as_snippet( $self, %options ) {
  179         776  
  179         1708  
  179         395  
598 179   100     1029 my $type = delete $options{ type } || 'LWP';
599 179 100       932 if( 'LWP' eq $type ) {
    100          
    50          
600 88         610 $self->as_lwp_snippet( %options )
601             } elsif( 'Tiny' eq $type ) {
602 86         754 $self->as_http_tiny_snippet( %options )
603             } elsif( 'Mojolicious' eq $type ) {
604 5         70 $self->as_mojolicious_snippet( %options )
605             } else {
606 0         0 croak "Unknown type '$type'.";
607             }
608             }
609              
610 88     88 0 233 sub as_lwp_snippet( $self, %options ) {
  88         193  
  88         260  
  88         166  
611 88   50     1388 $options{ prefix } ||= '';
612 88   100     797 $options{ implicit_headers } ||= [];
613              
614 88         315 my @preamble;
615             my @postamble;
616 88         0 my %ssl_options;
617 88 100       305 push @preamble, @{ $options{ preamble } } if $options{ preamble };
  86         367  
618 88 50       344 push @postamble, @{ $options{ postamble } } if $options{ postamble };
  0         0  
619 88         479 my @setup_ua = ('');
620              
621 88         875 my $request_args = join ", ",
622             '$r',
623             $self->_pairlist([
624             maybe ':content_file', $self->output
625             ], '')
626             ;
627 88         1062 my $init_cookie_jar = $self->_init_cookie_jar_lwp();
628 88 100       354 if( my $p = $init_cookie_jar->{preamble}) {
629 2         4 push @preamble, @{$p}
  2         4  
630             };
631              
632 88 100       366 if( $self->insecure ) {
633 1         12 push @preamble, 'use IO::Socket::SSL;';
634 1         8 $ssl_options{ SSL_verify_mode } = \'IO::Socket::SSL::SSL_VERIFY_NONE';
635 1         6 $ssl_options{ SSL_hostname } = '';
636 1         8 $ssl_options{ verify_hostname } = '';
637             };
638              
639 88 50       282 if( $self->cert ) {
640 0         0 push @preamble, 'use IO::Socket::SSL;';
641 0         0 $ssl_options{ SSL_ca_file } = $self->cert;
642             };
643 88 50       310 if( $self->capath ) {
644 0         0 push @preamble, 'use IO::Socket::SSL;';
645 0         0 $ssl_options{ SSL_ca_path } = $self->capath;
646             };
647             my $constructor_args = join ",",
648             $self->_pairlist([
649             send_te => 0,
650             maybe local_address => $self->local_address,
651             maybe max_size => $self->max_filesize,
652             maybe timeout => $self->timeout,
653             maybe cookie_jar => $init_cookie_jar->{code},
654 88 100       1198 maybe SSL_opts => keys %ssl_options ? \%ssl_options : undef,
655             ], '')
656             ;
657 88 50       847 if( defined( my $credentials = $self->credentials )) {
658 0         0 my( $user, $pass ) = split /:/, $credentials, 2;
659 0         0 my $setup_credentials = sprintf qq{\$ua->credentials("%s","%s");},
660             quotemeta $user,
661             quotemeta $pass;
662 0         0 push @setup_ua, $setup_credentials;
663             };
664 88 100       472 if( $self->show_error ) {
    50          
665 9         29 push @postamble,
666             ' die $res->message if $res->is_error;',
667             } elsif( $self->fail ) {
668 0         0 push @postamble,
669             ' exit 1 if !$res->{success};',
670             };
671              
672 88 50       312 @setup_ua = ()
673             if @setup_ua == 1;
674              
675 88         154 my $request_constructor;
676              
677 88 100 100     484 if( $self->method ne 'GET' and @{ $self->form_args }) {
  19         114  
678 2         5 push @preamble, 'use HTTP::Request::Common;';
679 2         3 push @{$options{ implicit_headers }}, 'Content-Type';
  2         7  
680 2         4 $request_constructor = <
681 2         12 my \$r = HTTP::Request::Common::@{[$self->method]}(
682 2         32 '@{[$self->uri]}',
683             Content_Type => 'form-data',
684             Content => [
685 2         20 @{[$self->_pairlist($self->form_args, ' ')]}
686             ],
687 2         39 @{[$self->_build_lwp_headers(' ', %options)]}
688             );
689             SNIPPET
690             } else {
691 86         279 $request_constructor = <
692             my \$r = HTTP::Request->new(
693 86         473 '@{[$self->method]}' => '@{[$self->uri]}',
  86         1070  
694             [
695 86         829 @{[$self->_build_lwp_headers(' ', %options)]}
696             ],
697 86         337 @{[$self->_build_quoted_body()]}
698             );
699             SNIPPET
700             }
701              
702 88         353 @preamble = map { "$options{prefix} $_\n" } @preamble;
  179         549  
703 88         193 @postamble = map { "$options{prefix} $_\n" } @postamble;
  9         43  
704 88         174 @setup_ua = map { "$options{prefix} $_\n" } @setup_ua;
  0         0  
705              
706 88         1128 return <
707             @preamble
708             my \$ua = LWP::UserAgent->new($constructor_args);@setup_ua
709             $request_constructor
710             my \$res = \$ua->request( $request_args );
711             @postamble
712             SNIPPET
713             };
714              
715 86     86 0 201 sub as_http_tiny_snippet( $self, %options ) {
  86         248  
  86         264  
  86         245  
716 86   50     1378 $options{ prefix } ||= '';
717 86   50     767 $options{ implicit_headers } ||= [];
718              
719 86         180 push @{ $options{ implicit_headers }}, 'Host'; # HTTP::Tiny dislikes that header
  86         458  
720              
721 86         367 my @preamble;
722             my @postamble;
723 86         0 my %ssl_options;
724 86 50       326 push @preamble, @{ $options{ preamble } } if $options{ preamble };
  86         289  
725 86 50       340 push @postamble, @{ $options{ postamble } } if $options{ postamble };
  0         0  
726 86         379 my @setup_ua = ('');
727              
728 86         1148 my $request_args = join ", ",
729             '$r',
730             $self->_pairlist([
731             maybe ':content_file', $self->output
732             ], '')
733             ;
734 86         1287 my $init_cookie_jar = $self->_init_cookie_jar_tiny();
735 86 100       420 if( my $p = $init_cookie_jar->{preamble}) {
736 2         10 push @preamble, @{$p}
  2         8  
737             };
738              
739 86         296 my @ssl;
740 86 100       437 if( $self->insecure ) {
741             } else {
742 85         355 push @ssl, verify_SSL => 1;
743             };
744 86 50       425 if( $self->cert ) {
745 0         0 push @preamble, 'use IO::Socket::SSL;';
746 0         0 $ssl_options{ SSL_cert_file } = $self->cert;
747             };
748 86 100       523 if( $self->show_error ) {
    50          
749 9         55 push @postamble,
750             ' die $res->{reason} if !$res->{success};',
751             } elsif( $self->fail ) {
752 0         0 push @postamble,
753             ' exit 1 if !$res->{success};',
754             };
755             my $constructor_args = join ",",
756             $self->_pairlist([
757             @ssl,
758             maybe timeout => $self->timeout,
759             maybe local_address => $self->local_address,
760             maybe max_size => $self->max_filesize,
761             maybe cookie_jar => $init_cookie_jar->{code},
762 86 50       1386 maybe SSL_options => keys %ssl_options ? \%ssl_options : undef,
763             ], '')
764             ;
765 86 50       928 if( defined( my $credentials = $self->credentials )) {
766 0         0 my( $user, $pass ) = split /:/, $credentials, 2;
767 0         0 my $setup_credentials = sprintf qq{\$ua->credentials("%s","%s");},
768             quotemeta $user,
769             quotemeta $pass;
770 0         0 push @setup_ua, $setup_credentials;
771             };
772              
773 86 50       332 @setup_ua = ()
774             if @setup_ua == 1;
775              
776 86         238 @preamble = map { "$options{prefix} $_\n" } @preamble;
  176         654  
777 86         198 @postamble = map { "$options{prefix} $_\n" } @postamble;
  9         36  
778 86         182 @setup_ua = map { "$options{prefix} $_\n" } @setup_ua;
  0         0  
779              
780 86         2354 my @content = $self->_build_quoted_body();
781 86 100       165 if( grep {/\S/} @content ) {
  86 100       465  
782 11         56 unshift @content, 'content => ',
783 75         462 } elsif( @{ $self->form_args }) {
784 2         18 my $req = HTTP::Request::Common::POST(
785             'https://example.com',
786             Content_Type => 'form-data',
787             Content => $self->form_args,
788             );
789 2         2250 @content = ('content => ', $self->_build_quoted_body( $req->content ));
790 2         19 $self->headers->{ 'Content-Type' } = join "; ", $req->headers->content_type;
791             }
792              
793 86         513 return <
794             @preamble
795             my \$ua = HTTP::Tiny->new($constructor_args);@setup_ua
796             my \$res = \$ua->request(
797 86         528 '@{[$self->method]}' => '@{[$self->uri]}',
  86         786  
798             {
799             headers => {
800 86         766 @{[$self->_build_tiny_headers(' ', %options)]}
801             },
802             @content
803             },
804             );
805             @postamble
806             SNIPPET
807             };
808              
809 5     5 0 42 sub as_mojolicious_snippet( $self, %options ) {
  5         16  
  5         16  
  5         12  
810 5   50     105 $options{ prefix } ||= '';
811 5   50     54 $options{ implicit_headers } ||= [];
812              
813 5         21 my @preamble;
814             my @postamble;
815 5         0 my %ssl_options;
816 5 50       21 push @preamble, @{ $options{ preamble } } if $options{ preamble };
  5         30  
817 5 50       27 push @postamble, @{ $options{ postamble } } if $options{ postamble };
  0         0  
818 5         32 my @setup_ua = ('');
819              
820 5         77 my $request_args = join ", ",
821             '$r',
822             $self->_pairlist([
823             maybe ':content_file', $self->output
824             ], '')
825             ;
826 5         221 my $init_cookie_jar = $self->_init_cookie_jar_mojolicious();
827 5 50       20 if( my $p = $init_cookie_jar->{preamble}) {
828 0         0 push @preamble, @{$p}
  0         0  
829             };
830              
831 5         11 my @ssl;
832 5 50       28 if( $self->insecure ) {
833 0         0 push @ssl, insecure => 1,
834             };
835 5 50       22 if( $self->cert ) {
836 0         0 push @ssl, cert => $self->cert,
837             };
838 5 50       78 if( $self->show_error ) {
    50          
839 0         0 push @postamble,
840             ' die $res->message if $res->is_error;',
841             } elsif( $self->fail ) {
842 0         0 push @postamble,
843             ' exit 1 if !$res->is_error;',
844             };
845 5         13 my $socket_options = {};
846 5 50       26 if( my $host = $self->local_address ) {
847 0         0 $socket_options->{ LocalAddr } = $host;
848             }
849             my $constructor_args = join ",",
850             $self->_pairlist([
851             @ssl,
852             keys %$socket_options ? ( socket_options => $socket_options ) : (),
853             maybe request_timeout => $self->timeout,
854             maybe max_response_size => $self->max_filesize,
855             maybe cookie_jar => $init_cookie_jar->{code},
856 5 50       106 maybe SSL_options => keys %ssl_options ? \%ssl_options : undef,
    50          
857             ], '')
858             ;
859 5 50       54 if( defined( my $credentials = $self->credentials )) {
860 0         0 my( $user, $pass ) = split /:/, $credentials, 2;
861 0         0 my $setup_credentials = sprintf qq{\$ua->userinfo("%s","%s");},
862             quotemeta $user,
863             quotemeta $pass;
864 0         0 push @setup_ua, $setup_credentials;
865             };
866              
867 5 50       31 @setup_ua = ()
868             if @setup_ua == 1;
869              
870 5         42 @preamble = map { "$options{prefix} $_\n" } @preamble;
  10         87  
871 5         12 @postamble = map { "$options{prefix} $_\n" } @postamble;
  0         0  
872 5         14 @setup_ua = map { "$options{prefix} $_\n" } @setup_ua;
  0         0  
873              
874 5         81 my $content = $self->_build_quoted_body();
875 5 50       10 if( @{ $self->form_args }) {
  5         31  
876 0         0 my $req = HTTP::Request::Common::POST(
877             'https://example.com',
878             Content_Type => 'form-data',
879             Content => $self->form_args,
880             );
881 0   0     0 $content ||= $self->_build_quoted_body( $req->content );
882 0         0 $self->headers->{ 'Content-Type' } = join "; ", $req->headers->content_type;
883             }
884              
885 5         20 return <
886             @preamble
887             my \$ua = Mojo::UserAgent->new($constructor_args);@setup_ua
888             my \$tx = \$ua->build_tx(
889 5         38 '@{[$self->method]}' => '@{[$self->uri]}',
  5         30  
890             {
891 5         24 @{[$self->_build_mojolicious_headers(' ', %options)]}
892             },
893             $content
894             );
895             my \$res = \$ua->start(\$tx)->result;
896             @postamble
897             SNIPPET
898             };
899              
900 0     0 0 0 sub as_rest_client_snippet( $self, %options ) {
  0         0  
  0         0  
  0         0  
901 0   0     0 $options{ prefix } ||= '';
902 0   0     0 $options{ implicit_headers } ||= [];
903              
904 0         0 my @preamble;
905             my @postamble;
906 0         0 my %ssl_options;
907 0 0       0 push @preamble, @{ $options{ preamble } } if $options{ preamble };
  0         0  
908 0 0       0 push @postamble, @{ $options{ postamble } } if $options{ postamble };
  0         0  
909 0         0 my @setup_ua = ('');
910              
911 0         0 my $request_args = join ", ",
912             '$r',
913             $self->_pairlist([
914             maybe ':content_file', $self->output
915             ], '')
916             ;
917 0         0 my $init_cookie_jar = $self->_init_cookie_jar_tiny();
918 0 0       0 if( my $p = $init_cookie_jar->{preamble}) {
919 0         0 push @preamble, @{$p}
  0         0  
920             };
921              
922 0 0       0 if( $self->show_error ) {
    0          
923 0         0 push @postamble,
924             ' die $res->{reason} if !$res->{success};',
925             } elsif( $self->fail ) {
926 0         0 push @postamble,
927             ' exit 1 if !$res->{success};',
928             };
929 0         0 my $constructor_args = join ",",
930             $self->_pairlist([
931             maybe timeout => $self->timeout,
932             maybe host => $self->host,
933             maybe cert => $self->cert,
934             maybe timeout => $self->timeout,
935             ], '')
936             ;
937 0 0       0 if( defined( my $credentials = $self->credentials )) {
938 0         0 my( $user, $pass ) = split /:/, $credentials, 2;
939 0         0 my $setup_credentials = sprintf qq{\$ua->credentials("%s","%s");},
940             quotemeta $user,
941             quotemeta $pass;
942 0         0 push @setup_ua, $setup_credentials;
943             };
944              
945 0 0       0 @setup_ua = ()
946             if @setup_ua == 1;
947              
948 0         0 @preamble = map { "$options{prefix} $_\n" } @preamble;
  0         0  
949 0         0 @postamble = map { "$options{prefix} $_\n" } @postamble;
  0         0  
950 0         0 @setup_ua = map { "$options{prefix} $_\n" } @setup_ua;
  0         0  
951              
952 0         0 my @content = $self->_build_quoted_body();
953 0 0       0 if( grep {/\S/} @content ) {
  0 0       0  
954 0         0 unshift @content, 'content => ',
955 0         0 } elsif( @{ $self->form_args }) {
956 0         0 my $req = HTTP::Request::Common::POST(
957             'https://example.com',
958             Content_Type => 'form-data',
959             Content => $self->form_args,
960             );
961 0         0 @content = ('content => ', $self->_build_quoted_body( $req->content ));
962 0         0 $self->headers->{ 'Content-Type' } = join "; ", $req->headers->content_type;
963             }
964              
965 0         0 return <
966             @preamble
967             my \$ua = REST::Client->new($constructor_args);@setup_ua
968             my \$res = \$ua->request(
969 0         0 '@{[$self->method]}' => '@{[$self->uri]}',
  0         0  
970             {
971             headers => {
972 0         0 @{[$self->_build_rest_client_headers(' ', %options)]}
973             },
974             @content
975             },
976             );
977             @postamble
978             SNIPPET
979             };
980              
981             =head2 C<< $r->as_curl >>
982              
983             print $r->as_curl;
984              
985             Returns a curl command line representing the request
986              
987             This is convenient if you started out from something else or want a canonical
988             representation of a curl command line.
989              
990             =over 4
991              
992             =item B
993              
994             The curl command to be used. Default is C.
995              
996             =back
997              
998             =cut
999              
1000             # These are what curl uses as defaults, not what Perl should use as default!
1001             our %curl_header_defaults = (
1002             'Accept' => '*/*',
1003             #'Accept-Encoding' => 'deflate, gzip',
1004             # For Perl, use HTTP::Message::decodable() instead of the above list
1005             );
1006              
1007 73     73 1 1430 sub as_curl($self,%options) {
  73         305  
  73         315  
  73         120  
1008             $options{ curl } = 'curl'
1009 73 50       244 if ! exists $options{ curl };
1010             $options{ long_options } = 1
1011 73 50       435 if ! exists $options{ long_options };
1012              
1013 73         197 my @request_commands;
1014              
1015 73 100       557 if( $self->method eq 'HEAD' ) {
    100          
1016             push @request_commands,
1017 1 50       11 $options{ long_options } ? '--head' : '-I';
1018              
1019             } elsif( $self->method ne 'GET' ) {
1020             push @request_commands,
1021 16 50       115 $options{ long_options } ? '--request' : '-X',
1022             $self->method;
1023             };
1024              
1025 73 50       114 if( scalar keys %{ $self->headers }) {
  73         312  
1026 73         117 for my $h (sort keys %{$self->headers}) {
  73         655  
1027 253         2490 my $v = $self->headers->{$h};
1028              
1029 253         390 my $default;
1030 253 100       654 if( exists $curl_header_defaults{ $h }) {
1031 72         352 $default = $curl_header_defaults{ $h };
1032             };
1033              
1034 253 100       444 if( ! ref $v ) {
1035 252         445 $v = [$v];
1036             };
1037 253         455 for my $val (@$v) {
1038 254 100 100     979 if( !defined $default or $val ne $default ) {
1039             # also skip the Host: header if it derives from $uri
1040 183 100 66     1253 if( $h eq 'Host' and ($val eq $self->uri->host_port
    100 100        
    100 66        
      66        
1041             or $val eq $self->uri->host )) {
1042             # trivial host header
1043              
1044             # also skip the Content-Length header if it derives from the body
1045             } elsif( $h eq 'Content-Length' and
1046             (
1047             ($self->post_data and $val == length $self->post_data)
1048             or ($self->body and $val == length $self->body)
1049             )) {
1050             # trivial content-length header
1051              
1052             } elsif( $h eq 'User-Agent' ) {
1053             push @request_commands,
1054 73 50       535 $options{ long_options } ? '--user-agent' : '-A',
1055             $val;
1056             } else {
1057             push @request_commands,
1058 28 50       339 $options{ long_options } ? '--header' : '-h',
1059             "$h: $val";
1060             };
1061             };
1062             };
1063             };
1064             };
1065              
1066 73 100       143 if( @{$self->form_args} ) {
  73 100       560  
1067 2         7 my $form_args = $self->form_args;
1068 2         4 for (0..(@{$form_args}/2-1)) {
  2         9  
1069 4         12 my( $name, $val) = ($form_args->[$_*2], $form_args->[$_*2+1]);
1070 4         14 push @request_commands,
1071             '--form-string',
1072             "$name=$val";
1073             }
1074              
1075             } elsif( defined( my $body = $self->body )) {
1076             # Can we collapse stuff into --json or other nicer representations
1077             # here ?!
1078 11         61 push @request_commands,
1079             '--data-raw',
1080             $body;
1081             };
1082              
1083 73         206 push @request_commands, $self->uri;
1084              
1085             return
1086             #(defined $options{ curl } ? $options{curl} : () ),
1087 73         427 @request_commands;
1088             }
1089              
1090             =head2 C<< $r->as_wget >>
1091              
1092             print $r->as_wget;
1093              
1094             Returns a curl command line representing the request
1095              
1096             This is convenient if you started out from something else or want a canonical
1097             representation of a curl command line.
1098              
1099             =over 4
1100              
1101             =item B
1102              
1103             The curl command to be used. Default is C.
1104              
1105             =back
1106              
1107             =cut
1108              
1109             # These are what wget uses as defaults, not what Perl should use as default!
1110             our %wget_header_defaults = (
1111             'Accept' => '*/*',
1112             'Accept-Encoding' => 'identity',
1113             'User-Agent' => 'Wget/1.21',
1114             'Connection' => 'Keep-Alive',
1115             );
1116              
1117 0     0 1   sub as_wget($self,%options) {
  0            
  0            
  0            
1118             $options{ wget } = 'wget'
1119 0 0         if ! exists $options{ wget };
1120             $options{ long_options } = 1
1121 0 0         if ! exists $options{ long_options };
1122              
1123 0           my @request_commands;
1124              
1125 0 0         if( $self->method ne 'GET' ) {
1126 0 0 0       if( $self->method eq 'POST' and $self->body ) {
1127             # This is implied by '--post-data', below
1128             } else {
1129 0           push @request_commands,
1130             '--method' => $self->method;
1131             };
1132             };
1133              
1134 0 0         if( scalar keys %{ $self->headers }) {
  0            
1135 0           my %h = %{ $self->headers };
  0            
1136              
1137             # "--no-cache" implies two headers, Cache-Control and Pragma
1138             my $is_cache = exists $h{ 'Pragma' }
1139             && exists $h{ 'Cache-Control' }
1140             && $h{ 'Cache-Control' } =~ /^no-cache\b/
1141 0   0       && $h{ 'Pragma' } eq 'no-cache'
1142             ;
1143 0 0         if( $is_cache ) {
1144 0           delete $h{ 'Pragma' };
1145 0           delete $h{ 'Cache-Control' };
1146 0           push @request_commands, '--no-cache';
1147             };
1148              
1149 0           for my $name (sort keys %h) {
1150 0           my $v = $h{ $name };
1151              
1152 0           my $default;
1153 0 0         if( exists $wget_header_defaults{ $name }) {
1154 0           $default = $wget_header_defaults{ $name };
1155             };
1156              
1157 0 0         if( ! ref $v ) {
1158 0           $v = [$v];
1159             };
1160 0           for my $val (@$v) {
1161 0 0 0       if( !defined $default or $val ne $default ) {
1162             # also skip the Host: header if it derives from $uri
1163 0 0 0       if( $name eq 'Host' and ($val eq $self->uri->host_port
    0 0        
1164             or $val eq $self->uri->host )) {
1165             # trivial host header, ignore
1166             } elsif( $name eq 'User-Agent' ) {
1167 0           push @request_commands,
1168             '--user-agent',
1169             $val;
1170             } else {
1171 0           push @request_commands,
1172             '--header',
1173             "$name: $val";
1174             };
1175             };
1176             };
1177             };
1178             };
1179              
1180 0 0         if( my $body = $self->body ) {
1181 0 0         if( $self->method eq 'POST' ) {
1182 0           push @request_commands,
1183             '--post-data',
1184             $body;
1185             } else {
1186 0           push @request_commands,
1187             '--body-data',
1188             $body;
1189             };
1190             };
1191              
1192 0           push @request_commands, $self->uri;
1193              
1194             return
1195             #(defined $options{ curl } ? $options{curl} : () ),
1196 0           @request_commands;
1197             }
1198              
1199              
1200             =head2 C<< $r->clone >>
1201              
1202             Returns a shallow copy of the object
1203              
1204             =cut
1205              
1206 0     0 1   sub clone( $self, %options ) {
  0            
  0            
  0            
1207 0           (ref $self)->new( %$self, %options )
1208             }
1209              
1210             1;
1211              
1212             =head1 REPOSITORY
1213              
1214             The public repository of this module is
1215             L.
1216              
1217             =head1 SUPPORT
1218              
1219             The public support forum of this module is
1220             L.
1221              
1222             =head1 BUG TRACKER
1223              
1224             Please report bugs in this module via the Github bug queue at
1225             L
1226              
1227             =head1 AUTHOR
1228              
1229             Max Maischein C
1230              
1231             =head1 COPYRIGHT (c)
1232              
1233             Copyright 2018-2026 by Max Maischein C.
1234              
1235             =head1 LICENSE
1236              
1237             This module is released under the same terms as Perl itself.
1238              
1239             =cut