File Coverage

blib/lib/MToken/Client.pm
Criterion Covered Total %
statement 39 245 15.9
branch 0 78 0.0
condition 0 75 0.0
subroutine 13 33 39.3
pod 18 18 100.0
total 70 449 15.5


line stmt bran cond sub pod time code
1             package MToken::Client; # $Id: Client.pm 107 2021-10-10 20:04:42Z minus $
2 2     2   616 use strict;
  2         5  
  2         78  
3 2     2   21 use feature qw/say/;
  2         3  
  2         176  
4 2     2   14 use utf8;
  2         4  
  2         15  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             MToken::Client - Client for interaction with MToken server
11              
12             =head1 VIRSION
13              
14             Version 1.02
15              
16             =head1 SYNOPSIS
17              
18             use MToken::Client;
19              
20             my $clinet = MToken::Client->new(
21             url => "https://localhost:8642",
22             username => "username", # optional
23             password => "password", # optional
24             pwcache => "/path/to/pwcache.tmp",
25             pwcache_ttl => 300, # 5 min. Default
26             max_redirects => 2, # Default: 10
27             connect_timeout => 3, # Default: 10 sec
28             inactivity_timeout => 5, # Default: 30 sec
29             request_timeout => 10, # Default: 5 min (300 sec)
30             );
31             my $status = $client->check();
32              
33             if ($status) {
34             print STDOUT $client->res->body;
35             } else {
36             print STDERR $clinet->error;
37             }
38              
39             =head1 DESCRIPTION
40              
41             Client for interaction with MToken server
42              
43             =head2 new
44              
45             my $clinet = MToken::Client->new(
46             url => "https://localhost:8642",
47             username => "username", # optional
48             password => "password", # optional
49             pwcache => "/path/to/pwcache.tmp",
50             pwcache_ttl => 300, # 5 min. Default
51             max_redirects => 2, # Default: 10
52             connect_timeout => 3, # Default: 10 sec
53             inactivity_timeout => 5, # Default: 30 sec
54             request_timeout => 10, # Default: 5 min (300 sec)
55             );
56              
57             Returns client
58              
59             =over 8
60              
61             =item B
62              
63             Maximum number of redirects the user agent will follow before it fails. Default - 10
64              
65             =item B
66              
67             Default password for basic authentication
68              
69             =item B
70              
71             Full path to file of password cache
72              
73             =item B
74              
75             Time to Live of pwcache file. Default - 300 sec
76              
77             =item B<*timeout>
78              
79             Timeout for connections, requests and inactivity periods in seconds.
80              
81             =item B
82              
83             The Mojo UserAgent object
84              
85             =item B
86              
87             Full URL of the WEB Server
88              
89             =item B
90              
91             Default username for basic authentication
92              
93             =back
94              
95             =head1 METHODS
96              
97             =head2 check
98              
99             my $status = $client->check;
100             my $status = $client->check(URL);
101              
102             Returns check-status of server. 0 - Error; 1 - Ok
103              
104             =head2 cleanup
105              
106             $client->cleanup;
107              
108             Cleanup all variable data in object and returns client object
109              
110             =head2 code
111              
112             my $code = $clinet->code;
113              
114             Returns HTTP code of the response
115              
116             =head2 credentials
117              
118             my $userinfo = $client->credentials($MOJO_URL_OBJECT, 1)
119              
120             Gets credentials for User Agent
121              
122             =head2 download
123              
124             my $status = $client->download(TOKEN_NAME => TARBALL_FILE_PATH);
125              
126             Request for download file from server by file path.
127             The method returns status of operation: 0 - Error; 1 - Ok
128              
129             =head2 error
130              
131             print $clinet->error;
132              
133             Returns error string
134              
135             =head2 info
136              
137             my $status = $clinet->info();
138             my $status = $clinet->info( TOKEN_NAME );
139              
140             Request for getting information about token storage or about list of stored token tarballs.
141              
142             =head2 remove
143              
144             my $status = $client->remove(TOKEN_NAME => TARBALL_FILE_NAME);
145              
146             Request for deleting of the file on server by filename.
147             The method returns status of operation: 0 - Error; 1 - Ok
148              
149             =head2 req
150              
151             my $request = $clinet->req;
152              
153             Returns Mojo::Message::Request object
154              
155             =head2 request
156              
157             my $json = $clinet->request("METHOD", "PATH", ...ATTRIBUTES...);
158              
159             Send request
160              
161             =head2 res
162              
163             my $response = $clinet->res;
164              
165             Returns Mojo::Message::Response object
166              
167             =head2 status
168              
169             my $status = $clinet->status;
170              
171             Returns object status value. 0 - Error; 1 - Ok
172              
173             =head2 trace
174              
175             my $trace = $client->trace;
176             print $client->trace("New trace record");
177              
178             Gets trace stack or pushes new trace record to trace stack
179              
180             =head2 tx
181              
182             my $status = $clinet->tx($tx);
183              
184             Works with Mojo::Transaction object, interface with it
185              
186             =head2 ua
187              
188             my $ua = $clinet->ua;
189              
190             Returns Mojo::UserAgent object
191              
192             =head2 upload
193              
194             my $status = $client->upload(TOKEN_NAME => TARBALL_FILE_PATH);
195              
196             Request for uploading of tarball on server.
197             The method returns status of operation: 0 - Error; 1 - Ok
198              
199             =head2 url
200              
201             my $url_object = $clinet->url;
202              
203             Returns Mojo::URL object
204              
205             =head1 HISTORY
206              
207             See C file
208              
209             =head1 DEPENDENCIES
210              
211             L
212              
213             =head1 TO DO
214              
215             See C file
216              
217             =head1 SEE ALSO
218              
219             L, L
220              
221             =head1 AUTHOR
222              
223             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
224              
225             =head1 COPYRIGHT
226              
227             Copyright (C) 1998-2021 D&D Corporation. All Rights Reserved
228              
229             =head1 LICENSE
230              
231             This program is free software; you can redistribute it and/or
232             modify it under the same terms as Perl itself.
233              
234             See C file and L
235              
236             =cut
237              
238 2     2   121 use vars qw/ $VERSION /;
  2         5  
  2         121  
239             $VERSION = '1.02';
240              
241 2     2   593 use Mojo::UserAgent;
  2         162009  
  2         21  
242 2     2   73 use Mojo::Asset::File;
  2         7  
  2         16  
243 2     2   58 use Mojo::File qw/path/;
  2         4  
  2         102  
244 2     2   26 use Mojo::URL;
  2         8  
  2         13  
245 2     2   63 use Mojo::Util qw/steady_time/;
  2         5  
  2         139  
246              
247 2     2   16 use CTK::Util qw/dtf lf_normalize touch/;
  2         4  
  2         167  
248              
249 2     2   609 use MToken::Util qw/md5sum parse_credentials tcd_save tcd_load/;
  2         5  
  2         141  
250 2     2   14 use MToken::Const;
  2         13  
  2         326  
251              
252             use constant {
253 2         6709 MAX_REDIRECTS => 10,
254             CONNECT_TIMEOUT => 10,
255             INACTIVITY_TIMEOUT => 30,
256             REQUEST_TIMEOUT => 180,
257             TRANSACTION_MASK => "%s %s >>> %s %s [%s in %s%s]", # GET /auth >>> 200 OK [1.04 KB in 0.0242 seconds (43.1 KB/sec)]
258             CONTENT_TYPE => "application/json",
259             PWCACHE_TTL => 300, # 5 min (Time to Live)
260 2     2   18 };
  2         5  
261              
262              
263             sub new {
264 0     0 1   my $class = shift;
265 0           my %args = @_;
266              
267             # General
268 0   0       $args{url} ||= ""; # base url
269 0           $args{prefix} = "";
270 0 0         if ($args{url}) {
271 0           $args{url} =~ s/\/+$//; $args{url} .= "/"; # Correct slash
  0            
272 0           $args{url} =~ s/mtoken\///; # Delete mtoken prefix
273 0           $args{url} = Mojo::URL->new($args{url});
274 0           $args{prefix} = $args{url}->path;
275             }
276 0           $args{status} = 1; # 0 - error, 1 - ok
277 0           $args{error} = ""; # string
278 0           $args{code} = 0; # integer
279 0           $args{trace} = []; # trace pool
280 0           $args{tx_time} = 0;
281 0           $args{req} = undef;
282 0           $args{res} = undef;
283 0   0       $args{user} ||= "";
284 0   0       $args{password} ||= "";
285 0 0 0       if ($args{url} && $args{user}) { # Set userinfo
    0          
286 0           $args{url}->userinfo(sprintf("%s:%s", $args{user}, $args{password}))
287             } elsif ($args{url}) {
288 0           ($args{user}, $args{password}) = (parse_credentials($args{url}->to_unsafe_string));
289             }
290 0   0       $args{pwcache} ||= ""; # pwcache file
291 0   0       $args{pwcache_ttl} //= PWCACHE_TTL;
292 0 0 0       if ($args{pwcache} && -e $args{pwcache}) { # Set pwcache
293 0           my $pwcache_path = path($args{pwcache});
294 0 0 0       if ($args{pwcache_ttl} && ($pwcache_path->stat->mtime + $args{pwcache_ttl}) < time) { # expired
295 0           $pwcache_path->remove;
296             } else {
297 0           touch($args{pwcache});
298             }
299             }
300              
301             # User Agent
302 0           my $ua = $args{ua};
303 0 0         unless ($ua) {
304             # Create the instance
305             $ua = Mojo::UserAgent->new(
306             max_redirects => $args{max_redirects} || MAX_REDIRECTS,
307             connect_timeout => $args{connect_timeout} || CONNECT_TIMEOUT,
308             inactivity_timeout => $args{inactivity_timeout} || INACTIVITY_TIMEOUT,
309             request_timeout => $args{request_timeout} || REQUEST_TIMEOUT,
310 0   0       insecure => $args{insecure} || 0,
      0        
      0        
      0        
      0        
311             );
312 0           $args{ua} = $ua;
313             }
314              
315 0           my $self = bless {%args}, $class;
316 0           return $self;
317             }
318             sub error {
319 0     0 1   my $self = shift;
320 0           my $e = shift;
321 0 0         $self->{error} = $e if defined $e;
322 0           return $self->{error};
323             }
324             sub status {
325 0     0 1   my $self = shift;
326 0           my $s = shift;
327 0 0         $self->{status} = $s if defined $s;
328 0           return $self->{status};
329             }
330             sub code {
331 0     0 1   my $self = shift;
332 0           my $c = shift;
333 0 0         $self->{code} = $c if defined $c;
334 0           return $self->{code};
335             }
336             sub trace {
337 0     0 1   my $self = shift;
338 0           my $v = shift;
339 0 0         if (defined($v)) {
340 0           my $a = $self->{trace};
341 0           push @$a, lf_normalize($v);
342 0           return lf_normalize($v);
343             }
344 0   0       my $trace = $self->{trace} || [];
345 0           return join("\n",@$trace);
346             }
347             sub cleanup {
348 0     0 1   my $self = shift;
349 0           $self->{status} = 1;
350 0           $self->{error} = "";
351 0           $self->{code} = 0;
352 0           undef $self->{req};
353 0           $self->{req} = undef;
354 0           undef $self->{res};
355 0           $self->{res} = undef;
356 0           undef $self->{trace};
357 0           $self->{trace} = [];
358 0           return $self;
359             }
360             sub req {
361 0     0 1   my $self = shift;
362 0           return $self->{req};
363             }
364             sub res {
365 0     0 1   my $self = shift;
366 0           return $self->{res};
367             }
368             sub url {
369 0     0 1   my $self = shift;
370 0           return $self->{url};
371             }
372             sub ua {
373 0     0 1   my $self = shift;
374 0           return $self->{ua};
375             }
376             sub tx {
377 0     0 1   my $self = shift;
378 0           my $tx = shift;
379              
380             # Check Error
381 0           my $err = $tx->error;
382 0 0 0       unless (!$err || $err->{code}) {
383 0           $self->error($err->{message});
384 0           $self->status(0);
385             }
386 0   0       $self->code($tx->res->code || "000");
387 0 0         $self->status($tx->res->is_success ? 1 : 0);
388 0 0 0       $self->error($tx->res->json("/message") || $err->{message} || "Unknown error" )
      0        
389             if $tx->res->is_error && !$self->error;
390              
391             # Tracing
392 0   0       my $length = $tx->res->body_size || 0;
393 0   0       my $rtime = $self->{tx_time} // 0;
394             $self->trace(sprintf(TRANSACTION_MASK,
395             $tx->req->method, $tx->req->url->to_abs, # Method & URL
396 0 0 0       $self->code, $tx->res->message || $err->{message} || "Unknown error", # Line
397             _fbytes($length), # Length
398             _fduration($rtime), # Duration
399             $rtime ? sprintf(" (%s/sec)", _fbytes($length/$rtime)) : "",
400             ));
401 0           my $req_hdrs = $tx->req->headers->to_string;
402 0 0         if ($req_hdrs) {
403 0           $self->trace(join("\n", map {$_ = "> $_"} split(/\n/, $req_hdrs)));
  0            
404 0           $self->trace(">");
405             }
406 0           my $res_hdrs = $tx->res->headers->to_string;
407 0 0         if ($res_hdrs) {
408 0           $self->trace(join("\n", map {$_ = "< $_"} split(/\n/, $res_hdrs)));
  0            
409 0           $self->trace("<");
410             }
411              
412             # Request And Response
413 0           $self->{req} = $tx->req;
414 0           $self->{res} = $tx->res;
415              
416 0           return $self->status;
417             }
418             sub request {
419 0     0 1   my $self = shift;
420 0           my $meth = shift;
421 0           my $_url = shift;
422 0           my @params = @_;
423 0           $self->cleanup(); # Cleanup first
424              
425             # Set URL + credentials
426             my $url = $_url
427             ? Mojo::URL->new("$_url")
428             : $self->{url}
429 0 0         ? $self->url->clone
    0          
430             : Mojo::URL->new(DEFAULT_URL);
431 0 0         $url->userinfo($self->credentials($url)) if $_url;
432              
433             # Request #1
434 0           my $start_time = steady_time() * 1;
435 0           my $tx = $self->ua->build_tx($meth, $url, @params); # Create transaction (tx) #1
436 0           my $status = $self->tx($self->ua->start($tx)); # Run it and validate!);
437 0           $self->{tx_time} = sprintf("%.*f",4, steady_time()*1 - $start_time) * 1;
438              
439             # Auth test
440 0 0 0       if (!$status && $self->code == 401) {
441 0           $self->cleanup();
442             # Request #2
443 0           $url->userinfo($self->credentials($url, 1));
444 0           $tx = $self->ua->build_tx($meth, $url, @params); # Create transaction (tx) #2
445 0           $status = $self->tx($self->ua->start($tx)); # Run it and validate!);
446 0           $self->{tx_time} = sprintf("%.*f",4, steady_time()*1 - $start_time) * 1;
447 0 0 0       if (!$status && $self->code == 401) {
    0 0        
448 0           $self->{user} = "";
449 0           $self->{password} = "";
450 0 0 0       path($self->{pwcache})->remove if $self->{pwcache} && -e $self->{pwcache};
451             } elsif ($status && $self->{pwcache}) {
452 0           tcd_save($self->{pwcache}, $url->userinfo);
453             }
454             }
455              
456 0           return $status;
457             }
458             sub credentials {
459 0     0 1   my $self = shift;
460 0           my $url = shift;
461 0 0         my $ask = shift(@_) ? 1 : 0;
462 0   0       $url ||= $self->{url};
463 0           my ($user, $password);
464              
465             # return predefined credentials
466 0 0         return sprintf("%s:%s", $self->{user}, $self->{password}) if $self->{user};
467              
468             # return if url contains credentials
469 0           ($user, $password) = (parse_credentials($url->to_unsafe_string));
470 0 0         return sprintf("%s:%s", $user, $password) if $user;
471              
472             # Get from cache
473 0 0 0       if ($self->{pwcache} && -e $self->{pwcache}) {
474 0   0       my $pair = tcd_load($self->{pwcache}) // "";
475 0           ($user, $password) = split(/\:/, $pair);
476 0 0         return sprintf("%s:%s", $user, $password) if $user;
477 0           unlink($self->{pwcache});
478             }
479              
480             # prompt if ask flag is true and is terminal
481 0 0 0       if ($ask && -t STDIN) {
482 0           my $realm = 'server';
483 0           printf STDERR "Enter username for %s at %s: ", $realm, $url->host_port;
484 0           $user = ;
485 0           chomp($user);
486 0 0         if (length($user)) {
487 0           print STDERR "Password: ";
488 0           system("stty -echo") unless IS_MSWIN;
489 0           $password = ;
490 0           system("stty echo") unless IS_MSWIN;
491 0           print STDERR "\n"; # because we disabled echo
492 0           chomp($password);
493 0           $self->{user} = $user;
494 0           $self->{password} = $password;
495             } else {
496 0           return "";
497             }
498 0           return sprintf("%s:%s", $user, $password);
499             }
500 0           return "";
501             }
502             sub check {
503 0     0 1   my $self = shift;
504 0           my $url = shift;
505 0 0 0       if (!$url && $self->{url}) {
506 0           $url = $self->url->clone;
507 0           $url->path("mtoken/");
508             }
509 0           return $self->request(HEAD => $url);
510             }
511             sub upload {
512 0     0 1   my $self = shift;
513 0           my $token = shift;
514 0           my $file = shift;
515 0           my $filepath = path($file);
516 0           my $filename = $filepath->basename;
517 0           my $url = $self->url->clone->path(sprintf("mtoken/%s/%s", $token, $filename));
518              
519 0           my $asset_file = Mojo::Asset::File->new(path => $file);
520 0           $self->request(PUT => $url =>
521             { # Headers
522             'User-Agent' => sprintf("%s/%s", __PACKAGE__, $self->VERSION),
523             'Content-Type' => 'multipart/form-data',
524             },
525             form => {
526             size => $asset_file->size,
527             md5 => md5sum($asset_file->path),
528             tarball => {
529             file => $asset_file,
530             filename => $filename,
531             'Content-Type' => 'application/octet-stream',
532             },
533             },
534             );
535             }
536             sub info {
537 0     0 1   my $self = shift;
538 0           my $token = shift;
539 0 0         my $url = $token
540             ? $self->url->clone->path(sprintf("mtoken/%s", $token))
541             : $self->url->clone->path("mtoken");
542 0           return $self->request(GET => $url);
543             }
544             sub remove {
545 0     0 1   my $self = shift;
546 0           my $token = shift;
547 0           my $file = shift;
548 0           my $filepath = path($file);
549 0           my $filename = $filepath->basename;
550 0           my $url = $self->url->clone->path(sprintf("mtoken/%s/%s", $token, $filename));
551 0           return $self->request(DELETE => $url);
552             }
553             sub download {
554 0     0 1   my $self = shift;
555 0           my $token = shift;
556 0           my $file = shift;
557 0           my $filepath = path($file);
558 0           my $filename = $filepath->basename;
559 0           my $url = $self->url->clone->path(sprintf("mtoken/%s/%s", $token, $filename));
560 0           my $status = $self->request(GET => $url);
561 0 0         return $status unless $status;
562 0           $self->res->save_to($file);
563 0 0         return 1 if $filepath->stat->size;
564 0           $self->error("Can't download file");
565 0           return $self->status(0);
566             }
567              
568             sub _fduration {
569 0   0 0     my $msecs = shift || 0;
570 0           my $secs = int($msecs);
571 0           my $hours = int($secs / (60*60));
572 0           $secs -= $hours * 60*60;
573 0           my $mins = int($secs / 60);
574 0           $secs %= 60;
575 0 0         if ($hours) {
    0          
    0          
576 0           return sprintf("%d hours %d minutes", $hours, $mins);
577             } elsif ($mins >= 2) {
578 0           return sprintf("%d minutes", $mins);
579             } elsif ($secs < 2*60) {
580 0           return sprintf("%.4f seconds", $msecs);
581             } else {
582 0           $secs += $mins * 60;
583 0           return sprintf("%d seconds", $secs);
584             }
585             }
586             sub _fbytes {
587 0     0     my $n = int(shift);
588 0 0         if ($n >= 1024 * 1024) {
    0          
589 0           return sprintf "%.3g MB", $n / (1024.0 * 1024);
590             } elsif ($n >= 1024) {
591 0           return sprintf "%.3g KB", $n / 1024.0;
592             } else {
593 0           return "$n bytes";
594             }
595             }
596              
597             1;
598              
599             __END__