line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Clustericious::Client; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
43466
|
use strict; no strict 'refs'; |
|
3
|
|
|
3
|
|
8
|
|
|
3
|
|
|
|
|
122
|
|
|
3
|
|
|
|
|
23
|
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
72
|
|
4
|
3
|
|
|
3
|
|
15
|
use warnings; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
138
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
# ABSTRACT: Construct command line and perl clients for RESTful services. |
7
|
|
|
|
|
|
|
our $VERSION = '0.85'; # VERSION |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
|
10
|
3
|
|
|
3
|
|
2702
|
use Mojo::Base qw/-base/; |
|
3
|
|
|
|
|
37313
|
|
|
3
|
|
|
|
|
24
|
|
11
|
|
|
|
|
|
|
|
12
|
3
|
|
|
3
|
|
4107
|
use Mojo::UserAgent; |
|
3
|
|
|
|
|
1161711
|
|
|
3
|
|
|
|
|
46
|
|
13
|
3
|
|
|
3
|
|
125
|
use Mojo::ByteStream qw/b/; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
204
|
|
14
|
3
|
|
|
3
|
|
17
|
use Mojo::Parameters; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
22
|
|
15
|
3
|
|
|
3
|
|
4649
|
use JSON::XS; |
|
3
|
|
|
|
|
19301
|
|
|
3
|
|
|
|
|
281
|
|
16
|
3
|
|
|
3
|
|
2818
|
use Clustericious::Config; |
|
3
|
|
|
|
|
623369
|
|
|
3
|
|
|
|
|
119
|
|
17
|
3
|
|
|
3
|
|
1891
|
use Clustericious::Client::Object; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
93
|
|
18
|
3
|
|
|
3
|
|
1637
|
use Clustericious::Client::Meta; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
88
|
|
19
|
3
|
|
|
3
|
|
1789
|
use Clustericious::Client::Meta::Route; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
39
|
|
20
|
3
|
|
|
3
|
|
113
|
use MojoX::Log::Log4perl; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
19
|
|
21
|
3
|
|
|
3
|
|
83
|
use Log::Log4perl qw/:easy/; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
22
|
|
22
|
3
|
|
|
3
|
|
14576
|
use File::Temp; |
|
3
|
|
|
|
|
29948
|
|
|
3
|
|
|
|
|
499
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
has server_url => ''; |
26
|
|
|
|
|
|
|
has [qw(tx res userinfo client)]; |
27
|
|
|
|
|
|
|
has _remote => ''; # Access via remote() |
28
|
|
|
|
|
|
|
has _cache => sub { + {} }; # cache of credentials |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub import |
31
|
|
|
|
|
|
|
{ |
32
|
3
|
|
|
3
|
|
35
|
my $class = shift; |
33
|
3
|
|
|
|
|
12
|
my $caller = caller; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
{ |
36
|
3
|
|
|
3
|
|
26
|
no strict 'refs'; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
4193
|
|
|
3
|
|
|
|
|
8
|
|
37
|
3
|
50
|
|
|
|
103
|
push @{"${caller}::ISA"}, $class unless $caller->isa($class); |
|
3
|
|
|
|
|
53
|
|
38
|
3
|
|
|
|
|
16
|
*{"${caller}::route"} = \&route; |
|
3
|
|
|
|
|
21
|
|
39
|
3
|
|
|
|
|
8
|
*{"${caller}::route_meta"} = \&route_meta; |
|
3
|
|
|
|
|
22
|
|
40
|
3
|
|
|
|
|
10
|
*{"${caller}::route_args"} = \&route_args; |
|
3
|
|
|
|
|
17
|
|
41
|
3
|
|
|
|
|
18
|
*{"${caller}::route_doc"} = sub { |
42
|
3
|
|
|
3
|
|
19
|
Clustericious::Client::Meta->add_route( $caller, @_ ) |
43
|
3
|
|
|
|
|
14
|
}; |
44
|
3
|
|
|
|
|
10
|
*{"${caller}::object"} = \&object; |
|
3
|
|
|
|
|
20
|
|
45
|
3
|
|
|
0
|
|
13
|
*{"${caller}::import"} = sub {}; |
|
3
|
|
|
|
|
4338
|
|
|
0
|
|
|
|
|
0
|
|
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub new |
51
|
|
|
|
|
|
|
{ |
52
|
1
|
|
|
1
|
1
|
4851
|
my $self = shift->SUPER::new(@_); |
53
|
1
|
|
|
|
|
11
|
my %args = @_; |
54
|
|
|
|
|
|
|
|
55
|
1
|
50
|
|
|
|
9
|
if ($self->{app}) |
56
|
|
|
|
|
|
|
{ |
57
|
0
|
|
|
|
|
0
|
my $app = $self->{app}; |
58
|
0
|
0
|
|
|
|
0
|
$app = $app->new() unless ref($app); |
59
|
0
|
|
|
|
|
0
|
my $client = Mojo::UserAgent->new; |
60
|
0
|
0
|
|
|
|
0
|
return undef unless $client; |
61
|
0
|
|
0
|
|
|
0
|
eval { $client->server->app($app) } // $client->app($app); |
|
0
|
|
|
|
|
0
|
|
62
|
|
|
|
|
|
|
|
63
|
0
|
|
|
|
|
0
|
$self->client($client); |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
else |
66
|
|
|
|
|
|
|
{ |
67
|
1
|
|
|
|
|
13
|
$self->client(Mojo::UserAgent->new); |
68
|
1
|
50
|
|
|
|
59
|
if (not length $self->server_url) |
69
|
|
|
|
|
|
|
{ |
70
|
0
|
|
|
|
|
0
|
my $url = $self->_config->url; |
71
|
0
|
|
|
|
|
0
|
$url =~ s{/$}{}; |
72
|
0
|
|
|
|
|
0
|
$self->server_url($url); |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
1
|
|
50
|
|
|
28
|
$self->client->inactivity_timeout($ENV{CLUSTERICIOUS_KEEP_ALIVE_TIMEOUT} || 300); |
77
|
|
|
|
|
|
|
|
78
|
1
|
|
|
|
|
42
|
return $self; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub remote { |
84
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
85
|
0
|
0
|
|
|
|
0
|
return $self->_remote unless @_ > 0; |
86
|
0
|
|
|
|
|
0
|
my $remote = shift; |
87
|
0
|
0
|
|
|
|
0
|
unless ($remote) { # reset to default |
88
|
0
|
|
|
|
|
0
|
$self->{_remote} = ''; |
89
|
0
|
|
|
|
|
0
|
$self->server_url($self->_config->url); |
90
|
0
|
|
|
|
|
0
|
return; |
91
|
|
|
|
|
|
|
} |
92
|
0
|
|
|
|
|
0
|
my $info = $self->_base_config->remotes->$remote; |
93
|
0
|
|
|
|
|
0
|
TRACE "Using remote url : ".$info->{url}; |
94
|
0
|
|
|
|
|
0
|
$self->server_url($info->{url}); |
95
|
0
|
|
|
|
|
0
|
$self->userinfo(''); |
96
|
0
|
|
|
|
|
0
|
$self->_remote($remote); |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub remotes { |
101
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
102
|
0
|
|
|
|
|
0
|
my %found = $self->_base_config->remotes(default => {}); |
103
|
0
|
|
|
|
|
0
|
return keys %found; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub login { |
108
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
109
|
0
|
|
|
|
|
0
|
my %args = @_; |
110
|
0
|
0
|
|
|
|
0
|
my ($user,$pw) = |
|
|
0
|
|
|
|
|
|
111
|
|
|
|
|
|
|
@_==2 ? @_ |
112
|
|
|
|
|
|
|
: @_ ? @args{qw/username password/} |
113
|
|
|
|
|
|
|
: map $self->_config->$_, qw/username password/; |
114
|
0
|
|
|
|
|
0
|
$self->userinfo(join ':', $user,$pw); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub errorstring { |
119
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
120
|
1
|
50
|
|
|
|
26
|
WARN "Missing response in client object" unless $self->res; |
121
|
1
|
50
|
|
|
|
43
|
return unless $self->res; |
122
|
0
|
0
|
0
|
|
|
0
|
return if $self->res->code && $self->res->is_status_class(200); |
123
|
0
|
0
|
|
|
|
0
|
$self->res->error |
124
|
|
|
|
|
|
|
|| sprintf( "(%d) %s", $self->res->code, $self->res->message ); |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub has_error { |
129
|
3
|
|
|
3
|
1
|
6
|
my $c = shift; |
130
|
3
|
50
|
33
|
|
|
73
|
return unless $c->tx || $c->res; |
131
|
0
|
0
|
0
|
|
|
0
|
return 1 if $c->tx && $c->tx->error; |
132
|
0
|
0
|
0
|
|
|
0
|
return 1 if $c->res && !$c->res->is_status_class(200); |
133
|
0
|
|
|
|
|
0
|
return 0; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub route { |
139
|
6
|
|
|
6
|
1
|
35
|
my $subname = shift; |
140
|
6
|
50
|
|
|
|
16
|
my $objclass = ref $_[0] eq 'ARRAY' ? shift->[0] : undef; |
141
|
6
|
50
|
|
|
|
14
|
my $doc = ref $_[-1] eq 'SCALAR' ? ${ pop() } : ""; |
|
0
|
|
|
|
|
0
|
|
142
|
6
|
|
33
|
|
|
14
|
my $url = pop || "/$subname"; |
143
|
6
|
|
100
|
|
|
18
|
my $method = shift || 'GET'; |
144
|
|
|
|
|
|
|
|
145
|
6
|
|
|
|
|
11
|
my $client_class = scalar caller(); |
146
|
6
|
|
|
|
|
47
|
my $meta = Clustericious::Client::Meta::Route->new( |
147
|
|
|
|
|
|
|
client_class => scalar caller(), |
148
|
|
|
|
|
|
|
route_name => $subname |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
6
|
|
|
|
|
65
|
$meta->set(method => $method); |
152
|
6
|
|
|
|
|
21
|
$meta->set(url => $url); |
153
|
6
|
|
|
|
|
64
|
$meta->set_doc($doc); |
154
|
|
|
|
|
|
|
|
155
|
6
|
50
|
|
|
|
15
|
if ($objclass) { |
156
|
0
|
|
|
|
|
0
|
eval "require $objclass"; |
157
|
0
|
0
|
|
|
|
0
|
if ($@) { |
158
|
0
|
0
|
|
|
|
0
|
LOGDIE "Error loading $objclass : $@" unless $@ =~ /Can't locate/i; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
{ |
163
|
3
|
|
|
3
|
|
19
|
no strict 'refs'; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
1715
|
|
|
6
|
|
|
|
|
6
|
|
164
|
6
|
|
|
|
|
46
|
*{caller() . "::$subname"} = sub { |
165
|
4
|
|
|
4
|
|
2756
|
my $self = shift; |
166
|
4
|
|
|
|
|
17
|
my @args = $self->meta_for($subname)->process_args(@_); |
167
|
4
|
|
|
|
|
38
|
my $got = $self->_doit($meta,$method,$url,@args); |
168
|
4
|
50
|
|
|
|
15
|
return $objclass->new($got, $self) if $objclass; |
169
|
4
|
|
|
|
|
25
|
return $got; |
170
|
6
|
|
|
|
|
32
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub route_meta { |
177
|
1
|
|
|
1
|
1
|
8
|
my $name = shift; |
178
|
1
|
|
|
|
|
3
|
my $attrs = shift; |
179
|
1
|
|
|
|
|
5
|
my $meta = Clustericious::Client::Meta::Route->new( |
180
|
|
|
|
|
|
|
client_class => scalar caller(), |
181
|
|
|
|
|
|
|
route_name => $name |
182
|
|
|
|
|
|
|
); |
183
|
|
|
|
|
|
|
|
184
|
1
|
|
|
|
|
13
|
$meta->set($_ => $attrs->{$_}) for keys %$attrs; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub route_args { |
189
|
9
|
|
|
9
|
1
|
112
|
my $name = shift; |
190
|
9
|
|
|
|
|
10
|
my $args = shift; |
191
|
9
|
50
|
|
|
|
32
|
die "args must be an array ref" unless ref $args eq 'ARRAY'; |
192
|
9
|
|
|
|
|
30
|
my $meta = Clustericious::Client::Meta::Route->new( |
193
|
|
|
|
|
|
|
client_class => scalar caller(), |
194
|
|
|
|
|
|
|
route_name => $name |
195
|
|
|
|
|
|
|
); |
196
|
|
|
|
|
|
|
|
197
|
9
|
|
|
|
|
94
|
$meta->set(args => $args); |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub object { |
202
|
0
|
|
|
0
|
1
|
0
|
my $objname = shift; |
203
|
0
|
|
0
|
|
|
0
|
my $url = shift || "/$objname"; |
204
|
0
|
0
|
|
|
|
0
|
my $doc = ref $_[-1] eq 'SCALAR' ? ${ pop() } : ''; |
|
0
|
|
|
|
|
0
|
|
205
|
0
|
|
|
|
|
0
|
my $caller = caller; |
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
0
|
my $objclass = "${caller}::" . |
208
|
0
|
|
|
|
|
0
|
join('', map { ucfirst } split('_', $objname)); # foo_bar => FooBar |
209
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
0
|
eval "require $objclass"; |
211
|
0
|
0
|
|
|
|
0
|
if ($@) { |
212
|
0
|
0
|
|
|
|
0
|
LOGDIE "Error loading $objclass : $@" unless $@ =~ /Can't locate/i; |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
0
|
0
|
|
|
|
0
|
$objclass = 'Clustericious::Client::Object' unless $objclass->can('new'); |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
0
|
Clustericious::Client::Meta->add_object(scalar caller(),$objname,$doc); |
218
|
|
|
|
|
|
|
|
219
|
3
|
|
|
3
|
|
16
|
no strict 'refs'; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
18871
|
|
220
|
0
|
|
|
|
|
0
|
*{"${caller}::$objname"} = sub { |
221
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
222
|
0
|
|
|
|
|
0
|
my $meta = Clustericious::Client::Meta::Route->new( |
223
|
|
|
|
|
|
|
client_class => $caller, |
224
|
|
|
|
|
|
|
route_name => $objname |
225
|
|
|
|
|
|
|
); |
226
|
0
|
|
|
|
|
0
|
$meta->set( quiet_post => 1 ); |
227
|
0
|
|
|
|
|
0
|
my $data = $self->_doit( $meta, GET => $url, @_ ); |
228
|
0
|
|
|
|
|
0
|
$objclass->new( $data, $self ); |
229
|
0
|
|
|
|
|
0
|
}; |
230
|
0
|
|
|
|
|
0
|
*{"${caller}::${objname}_delete"} = sub { |
231
|
0
|
|
|
0
|
|
0
|
my $meta = Clustericious::Client::Meta::Route->new( |
232
|
|
|
|
|
|
|
client_class => $caller, |
233
|
|
|
|
|
|
|
route_name => $objname.'_delete', |
234
|
|
|
|
|
|
|
); |
235
|
0
|
|
|
|
|
0
|
$meta->set(dont_read_files => 1); |
236
|
0
|
|
|
|
|
0
|
shift->_doit( $meta, DELETE => $url, @_ ); |
237
|
0
|
|
|
|
|
0
|
}; |
238
|
0
|
|
|
|
|
0
|
*{"${caller}::${objname}_search"} = sub { |
239
|
0
|
|
|
0
|
|
0
|
my $meta = Clustericious::Client::Meta::Route->new( |
240
|
|
|
|
|
|
|
client_class => $caller, |
241
|
|
|
|
|
|
|
route_name => $objname.'_search' |
242
|
|
|
|
|
|
|
); |
243
|
0
|
|
|
|
|
0
|
$meta->set(dont_read_files => 1); |
244
|
0
|
|
|
|
|
0
|
shift->_doit( $meta, POST => "$url/search", @_ ); |
245
|
0
|
|
|
|
|
0
|
}; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub _doit { |
249
|
4
|
|
|
4
|
|
9
|
my $self = shift; |
250
|
4
|
|
|
|
|
7
|
my $meta; |
251
|
4
|
50
|
|
|
|
14
|
$meta = shift if ref $_[0] eq 'Clustericious::Client::Meta::Route'; |
252
|
4
|
|
|
|
|
14
|
my ($method, $url, @args) = @_; |
253
|
|
|
|
|
|
|
|
254
|
4
|
|
|
|
|
6
|
my $auto_failover; |
255
|
4
|
50
|
33
|
|
|
19
|
$auto_failover = 1 if $meta && $meta->get('auto_failover'); |
256
|
|
|
|
|
|
|
|
257
|
4
|
50
|
33
|
|
|
88
|
$url = $self->server_url . $url if $self->server_url && $url !~ /^http/; |
258
|
4
|
50
|
|
|
|
231
|
return undef if $self->server_url eq 'http://0.0.0.0'; |
259
|
|
|
|
|
|
|
|
260
|
4
|
|
|
|
|
27
|
my $cb; |
261
|
4
|
|
|
|
|
6
|
my $body = ''; |
262
|
4
|
|
|
|
|
9
|
my $headers = {}; |
263
|
|
|
|
|
|
|
|
264
|
4
|
50
|
33
|
|
|
28
|
if ($method eq 'POST' && grep /^--/, @args) { |
265
|
0
|
|
|
|
|
0
|
s/^--// for @args; |
266
|
0
|
|
|
|
|
0
|
@args = ( { @args } ); |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
4
|
50
|
|
|
|
35
|
$url = Mojo::URL->new($url) unless ref $url; |
270
|
4
|
|
|
|
|
948
|
my $parameters = $url->query; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Set up mappings from parameter names to modifier callbacks. |
273
|
4
|
|
|
|
|
40
|
my %url_modifier; |
274
|
|
|
|
|
|
|
my %payload_modifer; |
275
|
|
|
|
|
|
|
my %gen_url_modifier = ( |
276
|
0
|
|
|
0
|
|
0
|
query => sub { my $name = shift; |
277
|
0
|
|
|
|
|
0
|
sub { my ($u,$v) = @_; $u->query({$name => $v}) } }, |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
278
|
13
|
|
|
13
|
|
13
|
append => sub { my $name = shift; |
279
|
13
|
|
|
|
|
63
|
sub { my ($u,$v) = @_; push @{ $u->path->parts } , $v; $u; } }, |
|
13
|
|
|
|
|
19
|
|
|
13
|
|
|
|
|
14
|
|
|
13
|
|
|
|
|
49
|
|
|
13
|
|
|
|
|
649
|
|
280
|
4
|
|
|
|
|
39
|
); |
281
|
|
|
|
|
|
|
my %gen_payload_modifier = ( |
282
|
|
|
|
|
|
|
array => sub { |
283
|
1
|
|
|
1
|
|
4
|
my ( $name, $key ) = @_; |
284
|
1
|
50
|
|
|
|
19
|
LOGDIE "missing key for array payload modifier" unless $key; |
285
|
1
|
|
50
|
|
|
2
|
sub { my $body = shift; $body ||= {}; push @{ $body->{$key} }, ( $name => shift ); $body; } |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
6
|
|
286
|
1
|
|
|
|
|
10
|
}, |
287
|
|
|
|
|
|
|
hash => sub { |
288
|
1
|
|
|
1
|
|
3
|
my $name = shift; |
289
|
1
|
|
50
|
|
|
2
|
sub { my $body = shift; $body ||= {}; $body->{$name} = shift; $body; } |
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
6
|
|
290
|
1
|
|
|
|
|
12
|
}, |
291
|
4
|
|
|
|
|
38
|
); |
292
|
4
|
50
|
33
|
|
|
22
|
if ($meta && (my $arg_spec = $meta->get('args'))) { |
293
|
4
|
|
|
|
|
13
|
for (@$arg_spec) { |
294
|
15
|
|
|
|
|
25
|
my $name = $_->{name}; |
295
|
15
|
100
|
|
|
|
40
|
if (my $modifies_url = $_->{modifies_url}) { |
296
|
13
|
50
|
|
|
|
48
|
$url_modifier{$name} = |
|
|
50
|
|
|
|
|
|
297
|
|
|
|
|
|
|
ref($modifies_url) eq 'CODE' ? $modifies_url |
298
|
|
|
|
|
|
|
: ($a = $gen_url_modifier{$modifies_url}) ? $a->($name) |
299
|
|
|
|
|
|
|
: die "don't understand how to interpret modifies_url=$modifies_url"; |
300
|
|
|
|
|
|
|
} |
301
|
15
|
100
|
|
|
|
45
|
if (my $modifies_payload = $_->{modifies_payload}) { |
302
|
2
|
50
|
|
|
|
18
|
$payload_modifer{$name} = |
|
|
50
|
|
|
|
|
|
303
|
|
|
|
|
|
|
ref($modifies_payload) eq 'CODE' ? $modifies_payload |
304
|
|
|
|
|
|
|
: ($a = $gen_payload_modifier{$modifies_payload}) ? $a->($name,$_->{key}) |
305
|
|
|
|
|
|
|
: LOGDIE "don't understand how to interpret modifies_payload=$modifies_payload"; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
4
|
|
|
|
|
18
|
while (defined(my $arg = shift @args)) { |
311
|
15
|
50
|
0
|
|
|
54
|
if (ref $arg eq 'HASH') { |
|
|
50
|
0
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
312
|
0
|
|
|
|
|
0
|
$method = 'POST'; |
313
|
0
|
0
|
0
|
|
|
0
|
$parameters->append(skip_existing => 1) if $meta && $meta->get("skip_existing"); |
314
|
0
|
|
|
|
|
0
|
$body = encode_json $arg; |
315
|
0
|
|
|
|
|
0
|
$headers = { 'Content-Type' => 'application/json' }; |
316
|
|
|
|
|
|
|
} elsif (ref $arg eq 'CODE') { |
317
|
0
|
|
|
|
|
0
|
$cb = $self->_mycallback($arg); |
318
|
|
|
|
|
|
|
} elsif (my $code = $url_modifier{$arg}) { |
319
|
13
|
|
|
|
|
28
|
$url = $code->($url, shift @args); |
320
|
|
|
|
|
|
|
} elsif (my $code2 = $payload_modifer{$arg}) { |
321
|
2
|
|
|
|
|
7
|
$body = $code2->($body, shift @args); |
322
|
|
|
|
|
|
|
} elsif ($method eq "GET" && $arg =~ s/^--//) { |
323
|
0
|
|
|
|
|
0
|
my $value = shift @args; |
324
|
0
|
|
|
|
|
0
|
$parameters->append($arg => $value); |
325
|
|
|
|
|
|
|
} elsif ($method eq "GET" && $arg =~ s/^-//) { |
326
|
|
|
|
|
|
|
# example: $client->esdt(-range => [1 => 100]); |
327
|
0
|
|
|
|
|
0
|
my $value = shift @args; |
328
|
0
|
0
|
|
|
|
0
|
if (ref $value eq 'ARRAY') { |
329
|
0
|
|
|
|
|
0
|
$value = "items=$value->[0]-$value->[1]"; |
330
|
|
|
|
|
|
|
} |
331
|
0
|
|
|
|
|
0
|
$headers->{$arg} = $value; |
332
|
|
|
|
|
|
|
} elsif ($method eq "POST" && !ref $arg) { |
333
|
0
|
|
|
|
|
0
|
$body = $arg; |
334
|
0
|
0
|
0
|
|
|
0
|
$headers = shift @args if $args[0] && ref $args[0] eq 'HASH'; |
335
|
|
|
|
|
|
|
} else { |
336
|
0
|
|
|
|
|
0
|
push @{ $url->path->parts }, $arg; |
|
0
|
|
|
|
|
0
|
|
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
} |
339
|
4
|
50
|
|
|
|
19
|
$url = $url->to_abs unless $url->is_abs; |
340
|
4
|
50
|
33
|
|
|
131
|
WARN "url $url is not absolute" unless $url =~ /^http/i || $ENV{HARNESS_ACTIVE}; |
341
|
|
|
|
|
|
|
|
342
|
4
|
50
|
|
|
|
1471
|
$url->userinfo($self->userinfo) if $self->userinfo; |
343
|
|
|
|
|
|
|
|
344
|
4
|
|
|
|
|
43
|
DEBUG ( (ref $self)." : $method " ._sanitize_url($url)); |
345
|
4
|
|
50
|
|
|
1364
|
$headers->{Connection} ||= 'Close'; |
346
|
4
|
|
50
|
|
|
24
|
$headers->{Accept} ||= 'application/json'; |
347
|
|
|
|
|
|
|
|
348
|
4
|
100
|
66
|
|
|
32
|
if($body && ref $body eq 'HASH' || ref $body eq 'ARRAY') |
|
|
|
66
|
|
|
|
|
349
|
|
|
|
|
|
|
{ |
350
|
1
|
|
|
|
|
3
|
$headers->{'Content-Type'} = 'application/json'; |
351
|
1
|
|
|
|
|
34
|
$body = encode_json $body; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
4
|
50
|
|
|
|
12
|
return $self->client->build_tx($method, $url, $headers, $body, $cb) if $cb; |
355
|
|
|
|
|
|
|
|
356
|
4
|
|
|
|
|
98
|
my $tx = $self->client->build_tx($method, $url, $headers, $body); |
357
|
|
|
|
|
|
|
|
358
|
4
|
|
|
|
|
3595
|
$tx = $self->client->start($tx); |
359
|
4
|
|
|
|
|
17122
|
my $res = $tx->res; |
360
|
4
|
|
|
|
|
114
|
$self->res($res); |
361
|
4
|
|
|
|
|
109
|
$self->tx($tx); |
362
|
|
|
|
|
|
|
|
363
|
4
|
|
|
|
|
155
|
my $auth_header; |
364
|
4
|
0
|
50
|
|
|
82
|
if (($tx->res->code||0) == 401 && ($auth_header = $tx->res->headers->www_authenticate) |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
365
|
|
|
|
|
|
|
&& !$url->userinfo && ($self->_has_auth || $self->_can_auth)) { |
366
|
0
|
|
|
|
|
0
|
DEBUG "received code 401, trying again with credentials"; |
367
|
0
|
|
|
|
|
0
|
my ($realm) = $auth_header =~ /realm=(.*)$/i; |
368
|
0
|
|
|
|
|
0
|
my $host = $url->host; |
369
|
0
|
0
|
|
|
|
0
|
$self->login( $self->_has_auth ? () : $self->_get_user_pw($host,$realm) ); |
370
|
0
|
0
|
|
|
|
0
|
return $self->_doit($meta ? $meta : (), @_); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
4
|
50
|
|
|
|
147
|
if ($res->is_status_class(200)) { |
374
|
0
|
|
|
|
|
0
|
TRACE "Got response : ".$res->to_string; |
375
|
0
|
|
0
|
|
|
0
|
my $content_type = $res->headers->content_type || do { |
376
|
|
|
|
|
|
|
WARN "No content-type from "._sanitize_url($url); |
377
|
|
|
|
|
|
|
"text/plain"; |
378
|
|
|
|
|
|
|
}; |
379
|
0
|
0
|
|
|
|
0
|
return $method =~ /HEAD|DELETE/ ? 1 |
|
|
0
|
|
|
|
|
|
380
|
|
|
|
|
|
|
: $content_type =~ qr[application/json] ? decode_json($res->body) |
381
|
|
|
|
|
|
|
: $res->body; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# Failed. |
385
|
4
|
|
|
|
|
122
|
my ($msg,$code) = $tx->error; |
386
|
4
|
|
50
|
|
|
271
|
$msg ||= 'unknown error'; |
387
|
4
|
|
|
|
|
17
|
my $s_url = _sanitize_url($url); |
388
|
|
|
|
|
|
|
|
389
|
4
|
50
|
|
|
|
41
|
if ($code) { |
390
|
0
|
0
|
|
|
|
0
|
if ($code == 404) { |
391
|
0
|
0
|
0
|
|
|
0
|
TRACE "$method $url : $code $msg" |
392
|
|
|
|
|
|
|
unless $ENV{ACPS_SUPPRESS_404} |
393
|
|
|
|
|
|
|
&& $url =~ /$ENV{ACPS_SUPPRESS_404}/; |
394
|
|
|
|
|
|
|
} else { |
395
|
0
|
|
|
|
|
0
|
ERROR "Error trying to $method $s_url : $code $msg"; |
396
|
0
|
0
|
|
|
|
0
|
TRACE "Full error body : ".$res->body if $res->body; |
397
|
0
|
|
0
|
|
|
0
|
my $brief = $res->body || ''; |
398
|
0
|
|
|
|
|
0
|
$brief =~ s/\n/ /g; |
399
|
0
|
0
|
|
|
|
0
|
ERROR substr($brief,0,200) if $brief; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
# No failover for legitimate status codes. |
402
|
0
|
|
|
|
|
0
|
return undef; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
4
|
50
|
|
|
|
23
|
unless ($auto_failover) { |
406
|
4
|
|
|
|
|
33
|
ERROR "Error trying to $method $s_url : $msg"; |
407
|
4
|
50
|
|
|
|
1503
|
ERROR $res->body if $res->body; |
408
|
4
|
|
|
|
|
443
|
return undef; |
409
|
|
|
|
|
|
|
} |
410
|
0
|
|
|
|
|
0
|
my $failover_urls = $self->_config->failover_urls(default => []); |
411
|
0
|
0
|
|
|
|
0
|
unless (@$failover_urls) { |
412
|
0
|
|
|
|
|
0
|
ERROR $msg; |
413
|
0
|
|
|
|
|
0
|
return undef; |
414
|
|
|
|
|
|
|
} |
415
|
0
|
|
|
|
|
0
|
INFO "$msg but will try up to ".@$failover_urls." failover urls"; |
416
|
0
|
|
|
|
|
0
|
TRACE "Failover urls : @$failover_urls"; |
417
|
0
|
|
|
|
|
0
|
for my $url (@$failover_urls) { |
418
|
0
|
|
|
|
|
0
|
DEBUG "Trying $url"; |
419
|
0
|
|
|
|
|
0
|
$self->server_url($url); |
420
|
0
|
|
|
|
|
0
|
my $got = $self->_doit(@_); |
421
|
0
|
0
|
|
|
|
0
|
return $got if $got; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
0
|
|
|
|
|
0
|
return undef; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
sub _mycallback |
428
|
|
|
|
|
|
|
{ |
429
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
430
|
0
|
|
|
|
|
0
|
my $cb = shift; |
431
|
|
|
|
|
|
|
sub |
432
|
|
|
|
|
|
|
{ |
433
|
0
|
|
|
0
|
|
0
|
my ($client, $tx) = @_; |
434
|
|
|
|
|
|
|
|
435
|
0
|
|
|
|
|
0
|
$self->res($tx->res); |
436
|
0
|
|
|
|
|
0
|
$self->tx($tx); |
437
|
|
|
|
|
|
|
|
438
|
0
|
0
|
|
|
|
0
|
if ($tx->res->is_status_class(200)) |
439
|
|
|
|
|
|
|
{ |
440
|
0
|
0
|
|
|
|
0
|
my $body = $tx->res->headers->content_type =~ qr[application/json] |
441
|
|
|
|
|
|
|
? decode_json($tx->res->body) : $tx->res->body; |
442
|
|
|
|
|
|
|
|
443
|
0
|
0
|
|
|
|
0
|
$cb->($body ? $body : 1); |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
else |
446
|
|
|
|
|
|
|
{ |
447
|
0
|
|
|
|
|
0
|
$cb->(); |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
} |
450
|
0
|
|
|
|
|
0
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub _sanitize_url { |
453
|
|
|
|
|
|
|
# Remove passwords from urls for displaying |
454
|
8
|
|
|
8
|
|
19
|
my $url = shift; |
455
|
8
|
50
|
|
|
|
30
|
$url = Mojo::URL->new($url) unless ref $url eq "Mojo::URL"; |
456
|
8
|
50
|
|
|
|
176
|
return $url unless $url->userinfo; |
457
|
0
|
|
|
|
|
0
|
my $c = $url->clone; |
458
|
0
|
|
|
|
|
0
|
$c->userinfo("user:*****"); |
459
|
0
|
|
|
|
|
0
|
return $c; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
sub _appname { |
463
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
464
|
0
|
|
|
|
|
0
|
(my $appname = ref $self) =~ s/:.*$//; |
465
|
0
|
|
|
|
|
0
|
return $appname; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
sub _config { |
469
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
470
|
0
|
|
|
|
|
0
|
my $conf = $self->_base_config; |
471
|
0
|
0
|
|
|
|
0
|
if (my $remote = $self->_remote) { |
472
|
0
|
|
|
|
|
0
|
return $conf->remotes->$remote; |
473
|
|
|
|
|
|
|
} |
474
|
0
|
|
|
|
|
0
|
return $conf; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub _base_config { |
478
|
|
|
|
|
|
|
# Independent of remotes |
479
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
480
|
0
|
0
|
|
|
|
0
|
return $self->{_base_config} if defined($self->{_base_config}); |
481
|
0
|
|
|
|
|
0
|
$self->{_base_config} = Clustericious::Config->new($self->_appname); |
482
|
0
|
|
|
|
|
0
|
return $self->{_base_config}; |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
sub _has_auth { |
486
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
487
|
0
|
0
|
|
|
|
0
|
return 0 unless $self->_config->username(default => ''); |
488
|
0
|
0
|
|
|
|
0
|
return 0 unless $self->_config->password(default => ''); |
489
|
0
|
|
|
|
|
0
|
return 1; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
sub _can_auth { |
493
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
494
|
0
|
0
|
|
|
|
0
|
return -t STDIN ? 1 : 0; |
495
|
|
|
|
|
|
|
} |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub _get_user_pw { |
498
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
499
|
0
|
|
|
|
|
0
|
my $host = shift; |
500
|
0
|
|
|
|
|
0
|
my $realm = shift; |
501
|
0
|
0
|
|
|
|
0
|
$realm = '' unless defined $realm; |
502
|
0
|
0
|
|
|
|
0
|
return @{ $self->_cache->{$host}{$realm} } if exists($self->_cache->{$host}{$realm}); |
|
0
|
|
|
|
|
0
|
|
503
|
|
|
|
|
|
|
# "use"ing causes too many warnings; load on demand. |
504
|
0
|
|
|
|
|
0
|
require Term::Prompt; |
505
|
0
|
|
0
|
|
|
0
|
my $user = Term::Prompt::prompt('x', "Username for $realm at $host : ", '', $ENV{USER} // $ENV{USERNAME}); |
506
|
0
|
|
|
|
|
0
|
my $pw = Term::Prompt::prompt('p', 'Password:', '', ''); |
507
|
0
|
|
|
|
|
0
|
$self->_cache->{$host}{$realm} = [ $user, $pw ]; |
508
|
0
|
|
|
|
|
0
|
return ($user,$pw); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
sub meta_for { |
513
|
16
|
|
|
16
|
1
|
3473
|
my $self = shift; |
514
|
16
|
|
66
|
|
|
122
|
my $route_name = shift || [ caller 1 ]->[3]; |
515
|
16
|
100
|
|
|
|
143
|
if ( $route_name =~ /::([^:]+)$/ ){ |
516
|
10
|
|
|
|
|
22
|
$route_name = $1; |
517
|
|
|
|
|
|
|
} |
518
|
16
|
|
|
|
|
96
|
my $meta = Clustericious::Client::Meta::Route->new( |
519
|
|
|
|
|
|
|
route_name => $route_name, |
520
|
|
|
|
|
|
|
client_class => ref $self |
521
|
|
|
|
|
|
|
); |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
sub version { |
526
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
527
|
0
|
|
|
|
|
|
my $meta = $self->meta_for("version"); |
528
|
0
|
|
|
|
|
|
$meta->set(auto_failover => 1); |
529
|
0
|
|
|
|
|
|
$self->_doit($meta, GET => '/version'); |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
sub status { |
534
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
535
|
0
|
|
|
|
|
|
my $meta = $self->meta_for("status"); |
536
|
0
|
|
|
|
|
|
$meta->set(auto_failover => 1); |
537
|
0
|
|
|
|
|
|
$self->_doit($meta, GET => '/status'); |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub api { |
542
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
543
|
0
|
|
|
|
|
|
my $meta = $self->meta_for("api"); |
544
|
0
|
|
|
|
|
|
$meta->set( auto_failover => 1 ); |
545
|
0
|
|
|
|
|
|
$self->_doit( $meta, GET => '/api' ); |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub logtail { |
550
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
551
|
0
|
|
|
|
|
|
my $got = $self->_doit(GET => '/log', @_); |
552
|
0
|
|
|
|
|
|
return { text => $got }; |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
1; |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
__END__ |