| 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__ |