line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WebService::Zaqar; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
215342
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
63
|
|
4
|
2
|
|
|
2
|
|
9
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
46
|
|
5
|
2
|
|
|
2
|
|
32
|
use 5.010; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
58
|
|
6
|
2
|
|
|
2
|
|
6
|
use Carp; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
136
|
|
7
|
2
|
|
|
2
|
|
10
|
use autodie; |
|
2
|
|
|
|
|
1
|
|
|
2
|
|
|
|
|
14
|
|
8
|
2
|
|
|
2
|
|
7119
|
use utf8; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
19
|
|
9
|
|
|
|
|
|
|
|
10
|
2
|
|
|
2
|
|
1169
|
use Moo; |
|
2
|
|
|
|
|
23637
|
|
|
2
|
|
|
|
|
10
|
|
11
|
2
|
|
|
2
|
|
4241
|
use HTTP::Request; |
|
2
|
|
|
|
|
36371
|
|
|
2
|
|
|
|
|
66
|
|
12
|
2
|
|
|
2
|
|
1232
|
use JSON; |
|
2
|
|
|
|
|
17958
|
|
|
2
|
|
|
|
|
6
|
|
13
|
2
|
|
|
2
|
|
610
|
use Net::HTTP::Spore; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use List::Util qw/first/; |
15
|
|
|
|
|
|
|
use Scalar::Util qw/blessed/; |
16
|
|
|
|
|
|
|
use Data::UUID; |
17
|
|
|
|
|
|
|
use Try::Tiny; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our $VERSION = '0.007'; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
has 'base_url' => (is => 'ro', |
22
|
|
|
|
|
|
|
writer => '_set_base_url'); |
23
|
|
|
|
|
|
|
has 'token' => (is => 'ro', |
24
|
|
|
|
|
|
|
writer => '_set_token', |
25
|
|
|
|
|
|
|
clearer => '_clear_token', |
26
|
|
|
|
|
|
|
predicate => 'has_token'); |
27
|
|
|
|
|
|
|
has 'spore_client' => (is => 'ro', |
28
|
|
|
|
|
|
|
lazy => 1, |
29
|
|
|
|
|
|
|
builder => '_build_spore_client'); |
30
|
|
|
|
|
|
|
has 'spore_description_file' => (is => 'ro', |
31
|
|
|
|
|
|
|
required => 1); |
32
|
|
|
|
|
|
|
has 'client_uuid' => (is => 'ro', |
33
|
|
|
|
|
|
|
lazy => 1, |
34
|
|
|
|
|
|
|
builder => '_build_uuid'); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
has 'wants_auth' => (is => 'ro', |
37
|
|
|
|
|
|
|
default => sub { 0 }); |
38
|
|
|
|
|
|
|
has 'rackspace_keystone_endpoint' => (is => 'ro', |
39
|
|
|
|
|
|
|
predicate => 1); |
40
|
|
|
|
|
|
|
has 'rackspace_username' => (is => 'ro', |
41
|
|
|
|
|
|
|
predicate => 1); |
42
|
|
|
|
|
|
|
has 'rackspace_api_key' => (is => 'ro', |
43
|
|
|
|
|
|
|
predicate => 1); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub _build_uuid { |
46
|
|
|
|
|
|
|
return Data::UUID->new->create_str; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub _build_spore_client { |
50
|
|
|
|
|
|
|
my $self = shift; |
51
|
|
|
|
|
|
|
my $client = Net::HTTP::Spore->new_from_spec($self->spore_description_file, |
52
|
|
|
|
|
|
|
base_url => $self->base_url); |
53
|
|
|
|
|
|
|
# all payloads serialized/deserialized to/from JSON -- except if |
54
|
|
|
|
|
|
|
# you're receiving 401 or 403 |
55
|
|
|
|
|
|
|
$client->enable('+WebService::Zaqar::Middleware::Format::JSONSometimes'); |
56
|
|
|
|
|
|
|
# set X-Auth-Token header to the Cloud Identity token, if |
57
|
|
|
|
|
|
|
# available (local instances don't use that, for instance) |
58
|
|
|
|
|
|
|
$client->enable('+WebService::Zaqar::Middleware::Auth::DynamicHeader', |
59
|
|
|
|
|
|
|
header_name => 'X-Auth-Token', |
60
|
|
|
|
|
|
|
header_value_callback => sub { |
61
|
|
|
|
|
|
|
# HTTP::Headers says, if the value of the |
62
|
|
|
|
|
|
|
# header is undef, the field is removed |
63
|
|
|
|
|
|
|
return $self->has_token ? $self->token : undef |
64
|
|
|
|
|
|
|
}); |
65
|
|
|
|
|
|
|
# all requests should contain a Date header with an RFC 1123 date |
66
|
|
|
|
|
|
|
$client->enable('+WebService::Zaqar::Middleware::DateHeader'); |
67
|
|
|
|
|
|
|
# each client using the queue should provide an UUID; the docs |
68
|
|
|
|
|
|
|
# recommend that for a given client it should persist between |
69
|
|
|
|
|
|
|
# restarts |
70
|
|
|
|
|
|
|
$client->enable('Header', |
71
|
|
|
|
|
|
|
header_name => 'Client-ID', |
72
|
|
|
|
|
|
|
header_value => $self->client_uuid); |
73
|
|
|
|
|
|
|
$client->enable('+WebService::Zaqar::Middleware::JustCallIt'); |
74
|
|
|
|
|
|
|
return $client; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub do_request { |
78
|
|
|
|
|
|
|
my ($self, $coderef, $options, @rest) = @_; |
79
|
|
|
|
|
|
|
# here undef retries means retry until it works, 0 retries means |
80
|
|
|
|
|
|
|
# don't retry, other integers mean retry that many times |
81
|
|
|
|
|
|
|
my $max_retries = $options->{retries}; |
82
|
|
|
|
|
|
|
my $current_retries = 0; |
83
|
|
|
|
|
|
|
RETRY: { |
84
|
|
|
|
|
|
|
my $return_value; |
85
|
|
|
|
|
|
|
try { |
86
|
|
|
|
|
|
|
$return_value = $coderef->($self, @rest); |
87
|
|
|
|
|
|
|
} catch { |
88
|
|
|
|
|
|
|
my $exception = $_; |
89
|
|
|
|
|
|
|
if (blessed($exception) |
90
|
|
|
|
|
|
|
and $exception->isa('Net::HTTP::Spore::Response')) { |
91
|
|
|
|
|
|
|
if ($exception->code == 401) { |
92
|
|
|
|
|
|
|
if (defined $max_retries and $current_retries >= $max_retries) { |
93
|
|
|
|
|
|
|
croak('Server returned 401 Unauthorized but we already retried too many times'); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
$current_retries++; |
96
|
|
|
|
|
|
|
# re-authentication needed |
97
|
|
|
|
|
|
|
if ($self->wants_auth) { |
98
|
|
|
|
|
|
|
$self->rackspace_authenticate( |
99
|
|
|
|
|
|
|
$self->rackspace_keystone_endpoint, |
100
|
|
|
|
|
|
|
$self->rackspace_username, |
101
|
|
|
|
|
|
|
$self->rackspace_api_key); |
102
|
|
|
|
|
|
|
goto RETRY; |
103
|
|
|
|
|
|
|
} else { |
104
|
|
|
|
|
|
|
# ... but not wanted! |
105
|
|
|
|
|
|
|
croak('Server returned 401 Unauthorized but we are not planning on authenticating!'); |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
# rethrow the contents of the exception instead of just |
109
|
|
|
|
|
|
|
# the unhelpful HTTP 400 |
110
|
|
|
|
|
|
|
if ($exception->code == 599) { |
111
|
|
|
|
|
|
|
croak($exception->body->{error}); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
# some other SPORE exception, try to display useful stuff |
114
|
|
|
|
|
|
|
my $body = $exception->body; |
115
|
|
|
|
|
|
|
croak(sprintf(q{HTTP %s: %s}, |
116
|
|
|
|
|
|
|
$exception->code, |
117
|
|
|
|
|
|
|
ref($exception->body) ? JSON::encode_json($exception->body) |
118
|
|
|
|
|
|
|
: $exception->body || '(no response contents)')) |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
# wasn't a Spore exception, rethrow |
121
|
|
|
|
|
|
|
croak $exception; |
122
|
|
|
|
|
|
|
}; |
123
|
|
|
|
|
|
|
return $return_value; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub rackspace_authenticate { |
128
|
|
|
|
|
|
|
my ($self, $cloud_identity_uri, $username, $apikey) = @_; |
129
|
|
|
|
|
|
|
my $request = HTTP::Request->new('POST', $cloud_identity_uri, |
130
|
|
|
|
|
|
|
[ 'Content-Type' => 'application/json' ], |
131
|
|
|
|
|
|
|
JSON::encode_json({ |
132
|
|
|
|
|
|
|
auth => { |
133
|
|
|
|
|
|
|
'RAX-KSKEY:apiKeyCredentials' => { |
134
|
|
|
|
|
|
|
username => $username, |
135
|
|
|
|
|
|
|
apiKey => $apikey } } })); |
136
|
|
|
|
|
|
|
my $response = $self->spore_client->api_useragent->request($request); |
137
|
|
|
|
|
|
|
my $content = $response->decoded_content; |
138
|
|
|
|
|
|
|
my $structure = JSON::decode_json($content); |
139
|
|
|
|
|
|
|
my $token = $structure->{access}->{token}->{id}; |
140
|
|
|
|
|
|
|
$self->_set_token($token); |
141
|
|
|
|
|
|
|
# the doc says we should read the catalog to determine the |
142
|
|
|
|
|
|
|
# endpoint... |
143
|
|
|
|
|
|
|
# my $catalog = first { $_->{name} eq 'cloudQueues' |
144
|
|
|
|
|
|
|
# and $_->{type} eq 'rax:queues' } @{$structure->{serviceCatalog}}; |
145
|
|
|
|
|
|
|
return $token; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub BUILD { |
149
|
|
|
|
|
|
|
my $self = shift; |
150
|
|
|
|
|
|
|
if ($self->wants_auth |
151
|
|
|
|
|
|
|
and (not $self->has_rackspace_keystone_endpoint |
152
|
|
|
|
|
|
|
or not $self->has_rackspace_username |
153
|
|
|
|
|
|
|
or not $self->has_rackspace_api_key)) { |
154
|
|
|
|
|
|
|
croak('Authentication required but not all Rackspace attributes provided'); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
# if ($self->has_rackspace_username) { |
157
|
|
|
|
|
|
|
# # uhhh, ok, so SOME Rackspace docs say this header is |
158
|
|
|
|
|
|
|
# # necessary, but others don't mention it; when I add it to a |
159
|
|
|
|
|
|
|
# # request it always 403s and without it it seems to work, so |
160
|
|
|
|
|
|
|
# # uh, yeah. |
161
|
|
|
|
|
|
|
# $self->spore_client->enable('Header', |
162
|
|
|
|
|
|
|
# header_name => 'X-Project-Id', |
163
|
|
|
|
|
|
|
# header_value => '921182'); |
164
|
|
|
|
|
|
|
# } |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
our $AUTOLOAD; |
168
|
|
|
|
|
|
|
sub AUTOLOAD { |
169
|
|
|
|
|
|
|
my $method_name = $AUTOLOAD; |
170
|
|
|
|
|
|
|
my ($self, @rest) = @_; |
171
|
|
|
|
|
|
|
my $current_class = ref $self; |
172
|
|
|
|
|
|
|
$method_name =~ s/^${current_class}:://; |
173
|
|
|
|
|
|
|
$self->spore_client->$method_name(@rest); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
1; |
177
|
|
|
|
|
|
|
__END__ |
178
|
|
|
|
|
|
|
=pod |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 NAME |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
WebService::Zaqar -- Wrapper around the Zaqar (aka Marconi) message queue API |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head1 SYNOPSIS |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
use WebService::Zaqar; |
187
|
|
|
|
|
|
|
my $client = WebService::Zaqar->new( |
188
|
|
|
|
|
|
|
# base_url => 'https://dfw.queues.api.rackspacecloud.com/', |
189
|
|
|
|
|
|
|
base_url => 'http://localhost:8888', |
190
|
|
|
|
|
|
|
spore_description_file => 'share/marconi.spore.json'); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# for Rackspace only |
193
|
|
|
|
|
|
|
my $token = $client->rackspace_authenticate('https://identity.api.rackspacecloud.com/v2.0/tokens', |
194
|
|
|
|
|
|
|
$rackspace_account, |
195
|
|
|
|
|
|
|
$rackspace_key); |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
$client->create_queue(queue_name => 'pets'); |
198
|
|
|
|
|
|
|
$client->post_messages(queue_name => 'pets', |
199
|
|
|
|
|
|
|
payload => [ |
200
|
|
|
|
|
|
|
{ ttl => 120, |
201
|
|
|
|
|
|
|
body => [ 'pony', 'horse', 'warhorse' ] }, |
202
|
|
|
|
|
|
|
{ ttl => 120, |
203
|
|
|
|
|
|
|
body => [ 'little dog', 'dog', 'large dog' ] } ]); |
204
|
|
|
|
|
|
|
$client->post_messages(queue_name => 'pets', |
205
|
|
|
|
|
|
|
payload => [ |
206
|
|
|
|
|
|
|
{ ttl => 120, |
207
|
|
|
|
|
|
|
body => [ 'aleax', 'archon', 'ki-rin' ] } ]); |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=head1 DESCRIPTION |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
This library is a L<Net::HTTP::Spore>-based client for the message |
212
|
|
|
|
|
|
|
queue component of OpenStack, |
213
|
|
|
|
|
|
|
L<Zaqar|https://wiki.openstack.org/wiki/Marconi/specs/api/v1> |
214
|
|
|
|
|
|
|
(previously known as "Marconi"). |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
On top of allowing you to make requests to a Zaqar endpoint, this |
217
|
|
|
|
|
|
|
library also supports Rackspace authentication using their L<Cloud |
218
|
|
|
|
|
|
|
Identity|http://docs.rackspace.com/queues/api/v1.0/cq-gettingstarted/content/Generating_Auth_Token.html> |
219
|
|
|
|
|
|
|
token system; see C<do_request>. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 base_url |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
(read-only string) |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
The base URL for all API queries, except for the Rackspace-specific |
228
|
|
|
|
|
|
|
authentication. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=head2 client_uuid |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
(read-only string, defaults to a new UUID) |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
All API queries B<should> contain a "Client-ID" header (in practice, |
235
|
|
|
|
|
|
|
some appear to work without this header). If you do not provide a |
236
|
|
|
|
|
|
|
value, a new one will be built with L<Data::UUID>. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
The docs recommend reusing the same client UUID between restarts of |
239
|
|
|
|
|
|
|
the client. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head2 rackspace_api_key |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
(read-only optional string) |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
API key for Rackspace authentication endpoints. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=head2 rackspace_keystone_endpoint |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
(read-only optional string) |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
URL for Rackspace authentication endpoints. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 rackspace_username |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
(read-only optional string) |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
Your Rackspace API username. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 spore_client |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
(read-only object) |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
This is the L<Net::HTTP::Spore> client build with the |
264
|
|
|
|
|
|
|
C<spore_description_file> attribute. All API method calls will be |
265
|
|
|
|
|
|
|
delegated to this object. |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head2 spore_description_file |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
(read-only required file path or URL) |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
Path to the SPORE specification file or remote resource. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
A spec file for Zaqar v1.0 is provided in the distribution (see |
274
|
|
|
|
|
|
|
F<share/marconi.spec.json>). |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=head2 token |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
(read-only string with default predicate) |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
The token is automatically set when calling C<rackspace_authenticate> |
281
|
|
|
|
|
|
|
successfully. Once set, it will be sent in the "X-Auth-Token" header |
282
|
|
|
|
|
|
|
with each query. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Rackspace invalidates the token after 24h, at which point all the |
285
|
|
|
|
|
|
|
queries will start returning "401 Unauthorized". Consider using |
286
|
|
|
|
|
|
|
C<do_request> to manage this for you. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head2 wants_auth |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
(read-only boolean, defaults to false) |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
If this attribute is set to true, you are indicating that the endpoint |
293
|
|
|
|
|
|
|
needs authentication. This means that when a request wrapped with |
294
|
|
|
|
|
|
|
C<do_request> fails with "401 Unauthorized", the client will try |
295
|
|
|
|
|
|
|
(re-)authenticating with C<rackspace_authenticate>, using the values |
296
|
|
|
|
|
|
|
in C<rackspace_keystone_endpoint>, C<rackspace_username> and |
297
|
|
|
|
|
|
|
C<rackspace_api_key>. |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head1 METHODS |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=head2 DELEGATED METHODS |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
All methods listed in L<the API |
304
|
|
|
|
|
|
|
docs|https://wiki.openstack.org/wiki/Marconi/specs/api/v1> are |
305
|
|
|
|
|
|
|
implemented by the SPORE client. When a body is required, you must |
306
|
|
|
|
|
|
|
provide it via the C<payload> parameter. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
See the F<share/marconi.spore.json> file for the list of methods and |
309
|
|
|
|
|
|
|
their parameters. |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
All those methods can be called with an instance of |
312
|
|
|
|
|
|
|
L<WebService::Zaqar> as invocant; they will be delegated to the SPORE |
313
|
|
|
|
|
|
|
client. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Unlike "regular" SPORE-based clients, you may use the special |
316
|
|
|
|
|
|
|
C<__url__> parameter to provide an already-built URL directly. This |
317
|
|
|
|
|
|
|
is helpful when trying to follow links provided by the API itself. |
318
|
|
|
|
|
|
|
E.g. when you make a claim on a queue, the server does not return the |
319
|
|
|
|
|
|
|
claim and message IDs; instead it returns URLs to the claim and |
320
|
|
|
|
|
|
|
messages, which you are then supposed to call if you want to release |
321
|
|
|
|
|
|
|
or update the claim, delete a message, etc. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
my $response = $client->claim_messages(queue_name => 'potato'); |
324
|
|
|
|
|
|
|
my $claim_href = $response->header('Location'); |
325
|
|
|
|
|
|
|
$client->release_claim(__url__ => $claim_href); |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=head2 do_request |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
my $response = $client->do_request(sub { $client->list_queues(limit => 20) }, |
330
|
|
|
|
|
|
|
{ retries => 2 }, |
331
|
|
|
|
|
|
|
@etc); |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
This method can be used to manage token generation. The first |
334
|
|
|
|
|
|
|
argument should be a coderef; it will be executing within a C<try { }> |
335
|
|
|
|
|
|
|
statement. If the coderef throws a blessed exception of class |
336
|
|
|
|
|
|
|
L<Net::HTTP::Spore::Response> (or a subclass thereof), that response's |
337
|
|
|
|
|
|
|
status is "401 Unauthorized", and C<wants_auth> was set to a true |
338
|
|
|
|
|
|
|
value, C<rackspace_authenticate> will be called and the coderef will |
339
|
|
|
|
|
|
|
be retried. |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
If the exception has another status code, it will be rethrown as-is, |
342
|
|
|
|
|
|
|
without retrying. This generally leads to a somewhat cryptic "HTTP |
343
|
|
|
|
|
|
|
response: 403" exception, since L<Net::HTTP::Spore::Response> objects |
344
|
|
|
|
|
|
|
stringify to their status code. If the status code was 599 (internal |
345
|
|
|
|
|
|
|
exception), the response's error message will be thrown instead. |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
If the exception is not a L<Net::HTTP::Spore::Response> instance at |
348
|
|
|
|
|
|
|
all, it will be rethrown directly. |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
Otherwise, the coderef's return value is returned. |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
The second argument is a hashref of options. Currently only "retries" |
353
|
|
|
|
|
|
|
is implemented: |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=over 4 |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=item if "retries" is undefined or not provided, C<do_request> will |
358
|
|
|
|
|
|
|
retry indefinitely until successful |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item if "retries" is 0, C<do_request> will not retry |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=item if "retries" is any other integer, C<do_request> will retry up |
363
|
|
|
|
|
|
|
to that many times. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=back |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
The coderef will be called with the original invocant of C<do_request> |
368
|
|
|
|
|
|
|
and the rest of the arguments of C<do_request> as parameters. |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=head2 rackspace_authenticate |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
my $token = $client->rackspace_authenticate('https://identity.api.rackspacecloud.com/v2.0/tokens', |
373
|
|
|
|
|
|
|
$rackspace_account, |
374
|
|
|
|
|
|
|
$rackspace_key); |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Sends an HTTP request to a L<Cloud |
377
|
|
|
|
|
|
|
Identity|http://docs.rackspace.com/queues/api/v1.0/cq-gettingstarted/content/Generating_Auth_Token.html> |
378
|
|
|
|
|
|
|
endpoint (or compatible) and sets the token received. |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
See also L</token>. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=head1 SPORE MIDDLEWARES ENABLED |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
The following modifications are applied to requests before they are |
385
|
|
|
|
|
|
|
made, in order: |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=over 4 |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=item serializing the body to JSON |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=item setting the C<X-Auth-Token> header to the authentication token, |
392
|
|
|
|
|
|
|
if available |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=item setting the C<Date> header to the current date in RFC 1123 |
395
|
|
|
|
|
|
|
format |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item setting the C<Client-ID> header to the value of the |
398
|
|
|
|
|
|
|
C<client_uuid> attribute |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=item if the C<__url__> parameter is provided to the method call, |
401
|
|
|
|
|
|
|
replace the request path and querystring params with its value |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=back |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
The following modifications are applied to responses before they are |
406
|
|
|
|
|
|
|
returned, in order: |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=over 4 |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=item deserializing the body from JSON, except for 401 and 403 |
411
|
|
|
|
|
|
|
responses, which are likely to come from Keystone instead and are |
412
|
|
|
|
|
|
|
plain text. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=back |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=head1 SEE ALSO |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
L<Net::HTTP::Spore> |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head1 AUTHOR |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Fabrice Gabolde <fgabolde@weborama.com> |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
Copyright (C) 2014 Weborama |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify |
429
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
430
|
|
|
|
|
|
|
the Free Software Foundation; either version 2 of the License, or (at |
431
|
|
|
|
|
|
|
your option) any later version. |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but |
434
|
|
|
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of |
435
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
436
|
|
|
|
|
|
|
General Public License for more details. |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
439
|
|
|
|
|
|
|
along with this program; if not, write to the Free Software |
440
|
|
|
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
441
|
|
|
|
|
|
|
02110-1301, USA. |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
=cut |