| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Dancer2::Plugin::HTTP::ContentNegotiation; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Dancer2::Plugin::HTTP::ContentNegotiation - Server-driven negotiation |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 VERSION |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Version 0.01 |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=cut |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
|
14
|
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
20894
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
30
|
|
|
16
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
22
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
5
|
use Carp; |
|
|
1
|
|
|
|
|
6
|
|
|
|
1
|
|
|
|
|
79
|
|
|
19
|
1
|
|
|
1
|
|
775
|
use Dancer2::Plugin; |
|
|
1
|
|
|
|
|
106022
|
|
|
|
1
|
|
|
|
|
8
|
|
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
HTTP specifies two types of content negotiation. These are server-driven |
|
24
|
|
|
|
|
|
|
negotiation and agent-driven negotiation. Server-driven negotiation uses request |
|
25
|
|
|
|
|
|
|
headers to select a variant, and agent-driven negotiation uses a distinct URI |
|
26
|
|
|
|
|
|
|
for each variant. |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
This plugin handles server-driven negotiation. |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
use Dancer2; |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
use Dancer2::Plugin::HTTP::ContentNegotiation; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
get '/greetings' => sub { |
|
35
|
|
|
|
|
|
|
http_choose_language ( |
|
36
|
|
|
|
|
|
|
'en' => sub { 'Hello World' }, |
|
37
|
|
|
|
|
|
|
'en-GB' => sub { 'Hello London' }, |
|
38
|
|
|
|
|
|
|
'en-US' => sub { 'Hello Washington' }, |
|
39
|
|
|
|
|
|
|
'nl' => sub { 'Hallo Amsterdam' }, |
|
40
|
|
|
|
|
|
|
'de' => sub { 'Hallo Berlin' }, |
|
41
|
|
|
|
|
|
|
# default is first in the list |
|
42
|
|
|
|
|
|
|
); |
|
43
|
|
|
|
|
|
|
}; |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
get '/choose/:id' => sub { |
|
46
|
|
|
|
|
|
|
my $data = SomeResource->find(param('id')); |
|
47
|
|
|
|
|
|
|
http_choose_media_type ( |
|
48
|
|
|
|
|
|
|
'application/json' => sub { to_json $data }, |
|
49
|
|
|
|
|
|
|
'application/xml ' => sub { to_xml $data }, |
|
50
|
|
|
|
|
|
|
{ default => undef }, # default is 406: Not Acceptable |
|
51
|
|
|
|
|
|
|
); |
|
52
|
|
|
|
|
|
|
}; |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
get '/thumbnail/:id' => sub { |
|
55
|
|
|
|
|
|
|
http_choose_media_type ( |
|
56
|
|
|
|
|
|
|
[ 'image/png', 'image/gif', 'image/jpeg' ] |
|
57
|
|
|
|
|
|
|
=> sub { Thumbnail->new(param('id'))->to(http_chosen->minor) }, |
|
58
|
|
|
|
|
|
|
{ default => 'image/png' }, # must be one listed above |
|
59
|
|
|
|
|
|
|
); |
|
60
|
|
|
|
|
|
|
}; |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
dance; |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 HTTP ContentNegotiation |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Clients that make an HTTP request can specify what kind of response they prefer. |
|
67
|
|
|
|
|
|
|
This can be a specific MIME-type, a different language, the text-encoding (if it |
|
68
|
|
|
|
|
|
|
applies to text documents) and wether it should be compressed or not. For this, |
|
69
|
|
|
|
|
|
|
the HTTP specifications in RFC 7231 (HTTP/1.1 Semantics and Content) Section 5.3 |
|
70
|
|
|
|
|
|
|
explains how to use resp. Accept, Accept-Language, Accept-Charset and |
|
71
|
|
|
|
|
|
|
Accept-Encoding header fields. |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
The server can try to send a response that the client would accept, but if there |
|
74
|
|
|
|
|
|
|
is no respresentation avaialbe in that format or language, it has three options. |
|
75
|
|
|
|
|
|
|
Either give a response in a different way, or respond with a status message 406, |
|
76
|
|
|
|
|
|
|
Not Accaptable. Another option would provide a list of available formats. |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=cut |
|
79
|
|
|
|
|
|
|
|
|
80
|
1
|
|
|
1
|
|
609
|
use HTTP::Headers::ActionPack; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# use List::MoreUtils 'first_index'; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
our $negotiator = HTTP::Headers::ActionPack->new->get_content_negotiator; |
|
85
|
|
|
|
|
|
|
our %http_headers = ( |
|
86
|
|
|
|
|
|
|
'media_type' => "Accept", |
|
87
|
|
|
|
|
|
|
'language' => "Accept-Language", |
|
88
|
|
|
|
|
|
|
'charset' => "Accept-Charset", |
|
89
|
|
|
|
|
|
|
'encoding' => "Accept-Encoding", |
|
90
|
|
|
|
|
|
|
); |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head1 DANCER2 KEYWORDS |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Each of the 'http_choose_...' keywords take the following arguments: |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=over |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=item a paired list with 'selectors' and coderefs. |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
those selectors, be it a single one or a anonymous array ref, numerate the |
|
101
|
|
|
|
|
|
|
available choices, the coderef following will be executed if there would be a |
|
102
|
|
|
|
|
|
|
match. |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=item an optional hashref with options. |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
The only option there is at this moment is 'default'. If not present and there |
|
107
|
|
|
|
|
|
|
is no match, it will use the first mentioned selector. If spcified, it will take |
|
108
|
|
|
|
|
|
|
that selector. Set to undef will return a status code of 406, Not Acceptable. |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=back |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
http_choose_selector ( |
|
113
|
|
|
|
|
|
|
selection_1 |
|
114
|
|
|
|
|
|
|
=> sub { ... }, |
|
115
|
|
|
|
|
|
|
[ selction_2, selection_3, selection_4 ] |
|
116
|
|
|
|
|
|
|
=> sub { ... }, |
|
117
|
|
|
|
|
|
|
{ default => selection_3 } |
|
118
|
|
|
|
|
|
|
); |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=cut |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head2 http_choose_media_type |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
This keyword is used to make a selection between different MIME-types. Please |
|
125
|
|
|
|
|
|
|
use this explicit version, as there is also http_choose (there is no |
|
126
|
|
|
|
|
|
|
Accept-MediaType, it's simply Accept) |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=cut |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
register 'http_choose_media_type' => sub { |
|
131
|
|
|
|
|
|
|
return _http_choose ( @_, 'media_type' ); |
|
132
|
|
|
|
|
|
|
}; |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head2 http_choose_language |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
This keyword works in conjunction with the Accept-Language. |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=cut |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
register 'http_choose_language' => sub { |
|
141
|
|
|
|
|
|
|
return _http_choose ( @_, 'language' ); |
|
142
|
|
|
|
|
|
|
}; |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head2 http_choose_charset |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This keyword should only be used with non-binary media-types, like XML or JSON. |
|
147
|
|
|
|
|
|
|
It is used to select in what 'encoding' the representation should be delivered. |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
NOTE: not sure yet how this word with the default UTF-8 Encoding of Dancer2. |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
register 'http_choose_charset' => sub { |
|
154
|
|
|
|
|
|
|
return _http_choose ( @_, 'charset' ); |
|
155
|
|
|
|
|
|
|
}; |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head2 http_choose_encoding |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
Mainly used for specifying compressed or uncompressed content. It has nothing to |
|
160
|
|
|
|
|
|
|
do whith character encoding though! |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
NOTE: not sure if this is the right place to compress files or not - maybe it |
|
163
|
|
|
|
|
|
|
would be better of to do this in Middleware. |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=cut |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
register 'http_choose_encoding' => sub { |
|
168
|
|
|
|
|
|
|
return _http_choose ( @_, 'encoding' ); |
|
169
|
|
|
|
|
|
|
}; |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 http_choose |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Naming compatability with the HTTP Headers, please use te explicit |
|
174
|
|
|
|
|
|
|
'http_choose_media_type' |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=cut |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
register 'http_choose' => sub { |
|
179
|
|
|
|
|
|
|
return _http_choose ( @_, 'media_type' ); |
|
180
|
|
|
|
|
|
|
}; |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub _http_choose { |
|
183
|
|
|
|
|
|
|
my $dsl = shift; |
|
184
|
|
|
|
|
|
|
my $switch = pop; |
|
185
|
|
|
|
|
|
|
my $options = (@_ % 2) ? pop : undef; |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
my @choices = _parse_choices(@_); |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# prepare for default behaviour |
|
190
|
|
|
|
|
|
|
# default ... if none match, pick first in definition list |
|
191
|
|
|
|
|
|
|
# default => 'choice' ... takes this as response, must be defined! |
|
192
|
|
|
|
|
|
|
# default => undef ... do not make assumptions, return 406 |
|
193
|
|
|
|
|
|
|
my $choice_first = ref $_[0] eq 'ARRAY' ? $_[0]->[0] : $_[0]; |
|
194
|
|
|
|
|
|
|
my $choice_default = $options->{'default'} if exists $options->{'default'}; |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# # make sure that a 'default' is actually in the list of choices |
|
197
|
|
|
|
|
|
|
# |
|
198
|
|
|
|
|
|
|
# if ( $choice_default and not exists $choices{$choice_default} ) { |
|
199
|
|
|
|
|
|
|
# $dsl->app->log ( warning => |
|
200
|
|
|
|
|
|
|
# qq|Invallid http_choose usage: | |
|
201
|
|
|
|
|
|
|
# . qq|'$choice_default' does not exist in choices| |
|
202
|
|
|
|
|
|
|
# ); |
|
203
|
|
|
|
|
|
|
# $dsl->status(500); |
|
204
|
|
|
|
|
|
|
# $dsl->halt; |
|
205
|
|
|
|
|
|
|
# } |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# choose from the provided definition |
|
208
|
|
|
|
|
|
|
my $selected = undef; |
|
209
|
|
|
|
|
|
|
my $method = 'choose' . '_' . $switch; |
|
210
|
|
|
|
|
|
|
if ( $dsl->request->header($http_headers{$switch}) ) { |
|
211
|
|
|
|
|
|
|
$selected = $negotiator->$method ( |
|
212
|
|
|
|
|
|
|
[ map { $_->{selector} } @choices ], |
|
213
|
|
|
|
|
|
|
$dsl->request->header($http_headers{$switch}) |
|
214
|
|
|
|
|
|
|
); |
|
215
|
|
|
|
|
|
|
}; |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# if nothing selected, use sensible default |
|
218
|
|
|
|
|
|
|
# $selected ||= exists $options->{'default'} ? $options->{'default'} : $choice_first; |
|
219
|
|
|
|
|
|
|
unless ($selected) { |
|
220
|
|
|
|
|
|
|
$selected = $negotiator->$method ( |
|
221
|
|
|
|
|
|
|
[ map { $_->{selector} } @choices ], |
|
222
|
|
|
|
|
|
|
exists $options->{'default'} ? $options->{'default'} : $choice_first |
|
223
|
|
|
|
|
|
|
); |
|
224
|
|
|
|
|
|
|
}; |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# if still nothing selected, return 406 error |
|
227
|
|
|
|
|
|
|
unless ($selected) { |
|
228
|
|
|
|
|
|
|
$dsl->status(406); # Not Acceptable |
|
229
|
|
|
|
|
|
|
$dsl->halt; |
|
230
|
|
|
|
|
|
|
}; |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
$dsl->vars->{"http_chosen_$switch"} = $selected; |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# set the apropriate headers for Content-Type and Content-Language |
|
235
|
|
|
|
|
|
|
# XXX Content-Type could consist of type PLUS charset if it's text-based |
|
236
|
|
|
|
|
|
|
if ($switch eq 'media_type') { |
|
237
|
|
|
|
|
|
|
$dsl->header('Content-Type' => "$selected" ); |
|
238
|
|
|
|
|
|
|
}; |
|
239
|
|
|
|
|
|
|
if ($switch eq 'language') { |
|
240
|
|
|
|
|
|
|
$dsl->header('Content-Language' => "$selected" ); |
|
241
|
|
|
|
|
|
|
}; |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
$dsl->header('Vary' => |
|
244
|
|
|
|
|
|
|
join ', ', $http_headers{$switch}, $dsl->header('Vary') |
|
245
|
|
|
|
|
|
|
) if @choices > 1 ; |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
my @coderefs = grep {$_->{selector} eq $selected} @choices; |
|
248
|
|
|
|
|
|
|
return $coderefs[0]{coderef}->($dsl); |
|
249
|
|
|
|
|
|
|
}; |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 http_chosen_media_type |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
returns a MediaType object that has been chosen. |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
This feature is experimental, but provides methods like type, major and minor |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=cut |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
register 'http_chosen_media_type' => sub { |
|
260
|
|
|
|
|
|
|
return _http_chosen ( @_, 'media_type' ); |
|
261
|
|
|
|
|
|
|
}; |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=head2 http_chosen_language |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
returns the LanguageTag being chosen from the selectors. |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
Experimental too and should privde methods like language, primary, extlang, |
|
268
|
|
|
|
|
|
|
script, region and variant |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=cut |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
register 'http_chosen_language' => sub { |
|
273
|
|
|
|
|
|
|
return _http_chosen ( @_, 'language' ); |
|
274
|
|
|
|
|
|
|
}; |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=head2 http_chosen_charset |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
returns the chosen Charset. |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=cut |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
register 'http_chosen_charset' => sub { |
|
283
|
|
|
|
|
|
|
return _http_chosen ( @_, 'charset' ); |
|
284
|
|
|
|
|
|
|
}; |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=head2 http_chose_encoding |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
returns wether or not the resouce should be compressed and how. |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=cut |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
register 'http_chosen_encoding' => sub { |
|
293
|
|
|
|
|
|
|
return _http_chosen ( @_, 'encoding' ); |
|
294
|
|
|
|
|
|
|
}; |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head2 http_chosen |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
Naming compatability with the HTTP Headers, please use te explicit |
|
299
|
|
|
|
|
|
|
'http_chosen_media_type' |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=cut |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
register 'http_chosen' => sub { |
|
304
|
|
|
|
|
|
|
return _http_chosen ( @_, 'media_type' ); |
|
305
|
|
|
|
|
|
|
}; |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub _http_chosen { |
|
308
|
|
|
|
|
|
|
my $dsl = shift; |
|
309
|
|
|
|
|
|
|
my $switch = pop; |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
$dsl->app->log ( error => |
|
312
|
|
|
|
|
|
|
"http_chosen_$switch does not exist" |
|
313
|
|
|
|
|
|
|
) unless exists $dsl->vars->{"http_chosen_$switch"}; |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
$dsl->app->log( error => |
|
316
|
|
|
|
|
|
|
"http_chosen_$switch is designed for read-only" |
|
317
|
|
|
|
|
|
|
) if (@_ >= 1); |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
return unless exists $dsl->vars->{"http_chosen_$switch"}; |
|
320
|
|
|
|
|
|
|
return $dsl->vars->{"http_chosen_$switch"}; |
|
321
|
|
|
|
|
|
|
}; |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
on_plugin_import { |
|
324
|
|
|
|
|
|
|
my $dsl = shift; |
|
325
|
|
|
|
|
|
|
my $app = $dsl->app; |
|
326
|
|
|
|
|
|
|
}; |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub _parse_choices { |
|
329
|
|
|
|
|
|
|
# _parse_choices |
|
330
|
|
|
|
|
|
|
# unraffles a paired list into a list of hashes, |
|
331
|
|
|
|
|
|
|
# each hash containin a 'selector' and associated coderef. |
|
332
|
|
|
|
|
|
|
# since the 'key' can be an arrayref too, these are added to the list with |
|
333
|
|
|
|
|
|
|
# seperate values |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
my @choices; |
|
336
|
|
|
|
|
|
|
while ( @_ ) { |
|
337
|
|
|
|
|
|
|
my ($choices, $coderef) = @{[ shift, shift ]}; |
|
338
|
|
|
|
|
|
|
last unless $choices; |
|
339
|
|
|
|
|
|
|
# turn a single value into a ARRAY REF |
|
340
|
|
|
|
|
|
|
$choices = [ $choices ] unless ref $choices eq 'ARRAY'; |
|
341
|
|
|
|
|
|
|
# so we only have ARRAY REFs to deal with |
|
342
|
|
|
|
|
|
|
foreach ( @$choices ) { |
|
343
|
|
|
|
|
|
|
if ( ref $coderef ne 'CODE' ) { |
|
344
|
|
|
|
|
|
|
die |
|
345
|
|
|
|
|
|
|
qq{Invallid http_choose usage: } |
|
346
|
|
|
|
|
|
|
. qq{'$_' needs a CODE ref}; |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
# if ( exists $choices{$_} ) { |
|
349
|
|
|
|
|
|
|
# die |
|
350
|
|
|
|
|
|
|
# qq{Invallid http_choose usage: } |
|
351
|
|
|
|
|
|
|
# . qq{Duplicated choice '$_'}; |
|
352
|
|
|
|
|
|
|
# } |
|
353
|
|
|
|
|
|
|
push @choices, |
|
354
|
|
|
|
|
|
|
{ |
|
355
|
|
|
|
|
|
|
selector => $_, |
|
356
|
|
|
|
|
|
|
coderef => $coderef, |
|
357
|
|
|
|
|
|
|
}; |
|
358
|
|
|
|
|
|
|
} |
|
359
|
|
|
|
|
|
|
} |
|
360
|
|
|
|
|
|
|
return @choices; |
|
361
|
|
|
|
|
|
|
}; # _parse_choices |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
register_plugin; |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head1 CAVEATS |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
the underlying HTTP::ActionPack has it's own bugs - for the time being this |
|
368
|
|
|
|
|
|
|
module uses those modules and will suffer from many of the shortcommings that |
|
369
|
|
|
|
|
|
|
come from using ActionPack. |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=head1 AUTHOR |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
Theo van Hoesel, C<< | >>
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=head1 BUGS |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
|
378
|
|
|
|
|
|
|
C, or through the web |
|
379
|
|
|
|
|
|
|
interface at |
|
380
|
|
|
|
|
|
|
L. |
|
381
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
|
382
|
|
|
|
|
|
|
your bug as I make changes. |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=head1 SUPPORT |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
perldoc Dancer2::Plugin::HTTP::ContentNegotiation |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
You can also look for information at: |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=over 4 |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
L |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
L |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=item * CPAN Ratings |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
L |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=item * Search CPAN |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
L |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=back |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
Copyright 2015 Theo van Hoesel. |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
|
424
|
|
|
|
|
|
|
under the terms of the the Artistic License (2.0). You may obtain a |
|
425
|
|
|
|
|
|
|
copy of the full license at: |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
L |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
Any use, modification, and distribution of the Standard or Modified |
|
430
|
|
|
|
|
|
|
Versions is governed by this Artistic License. By using, modifying or |
|
431
|
|
|
|
|
|
|
distributing the Package, you accept this license. Do not use, modify, |
|
432
|
|
|
|
|
|
|
or distribute the Package, if you do not accept this license. |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
If your Modified Version has been derived from a Modified Version made |
|
435
|
|
|
|
|
|
|
by someone other than you, you are nevertheless required to ensure that |
|
436
|
|
|
|
|
|
|
your Modified Version complies with the requirements of this license. |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
This license does not grant you the right to use any trademark, service |
|
439
|
|
|
|
|
|
|
mark, tradename, or logo of the Copyright Holder. |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
This license includes the non-exclusive, worldwide, free-of-charge |
|
442
|
|
|
|
|
|
|
patent license to make, have made, use, offer to sell, sell, import and |
|
443
|
|
|
|
|
|
|
otherwise transfer the Package with respect to any patent claims |
|
444
|
|
|
|
|
|
|
licensable by the Copyright Holder that are necessarily infringed by the |
|
445
|
|
|
|
|
|
|
Package. If you institute patent litigation (including a cross-claim or |
|
446
|
|
|
|
|
|
|
counterclaim) against any party alleging that the Package constitutes |
|
447
|
|
|
|
|
|
|
direct or contributory patent infringement, then this Artistic License |
|
448
|
|
|
|
|
|
|
to you shall terminate on the date that such litigation is filed. |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER |
|
451
|
|
|
|
|
|
|
AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. |
|
452
|
|
|
|
|
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
453
|
|
|
|
|
|
|
PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY |
|
454
|
|
|
|
|
|
|
YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR |
|
455
|
|
|
|
|
|
|
CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR |
|
456
|
|
|
|
|
|
|
CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, |
|
457
|
|
|
|
|
|
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=cut |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
1; |