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