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.02'; |
14
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
21611
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
16
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
22
|
|
17
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
75
|
|
19
|
1
|
|
|
1
|
|
839
|
use Dancer2::Plugin; |
|
1
|
|
|
|
|
150714
|
|
|
1
|
|
|
|
|
7
|
|
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
|
|
1056
|
use HTTP::Headers::ActionPack; |
|
1
|
|
|
|
|
1020
|
|
|
1
|
|
|
|
|
1164
|
|
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
|
0
|
|
|
0
|
|
|
my $dsl = shift; |
184
|
0
|
|
|
|
|
|
my $switch = pop; |
185
|
0
|
0
|
|
|
|
|
my $options = (@_ % 2) ? pop : undef; |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
my $choice_first = ref $_[0] eq 'ARRAY' ? $_[0]->[0] : $_[0]; |
194
|
0
|
0
|
|
|
|
|
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
|
0
|
|
|
|
|
|
my $selected = undef; |
209
|
0
|
|
|
|
|
|
my $method = 'choose' . '_' . $switch; |
210
|
0
|
0
|
|
|
|
|
if ( $dsl->request->header($http_headers{$switch}) ) { |
211
|
|
|
|
|
|
|
$selected = $negotiator->$method ( |
212
|
0
|
|
|
|
|
|
[ map { $_->{selector} } @choices ], |
213
|
0
|
|
|
|
|
|
$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
|
0
|
0
|
|
|
|
|
unless ($selected) { |
220
|
|
|
|
|
|
|
$selected = $negotiator->$method ( |
221
|
0
|
|
|
|
|
|
[ map { $_->{selector} } @choices ], |
222
|
0
|
0
|
|
|
|
|
exists $options->{'default'} ? $options->{'default'} : $choice_first |
223
|
|
|
|
|
|
|
); |
224
|
|
|
|
|
|
|
}; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# if still nothing selected, return 406 error |
227
|
0
|
0
|
|
|
|
|
unless ($selected) { |
228
|
0
|
|
|
|
|
|
$dsl->status(406); # Not Acceptable |
229
|
0
|
|
|
|
|
|
$dsl->halt; |
230
|
|
|
|
|
|
|
}; |
231
|
|
|
|
|
|
|
|
232
|
0
|
|
|
|
|
|
$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
|
0
|
0
|
|
|
|
|
if ($switch eq 'media_type') { |
237
|
0
|
|
|
|
|
|
$dsl->header('Content-Type' => "$selected" ); |
238
|
|
|
|
|
|
|
}; |
239
|
0
|
0
|
|
|
|
|
if ($switch eq 'language') { |
240
|
0
|
|
|
|
|
|
$dsl->header('Content-Language' => "$selected" ); |
241
|
|
|
|
|
|
|
}; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
$dsl->header('Vary' => |
244
|
0
|
0
|
|
|
|
|
join ', ', $http_headers{$switch}, $dsl->header('Vary') |
245
|
|
|
|
|
|
|
) if @choices > 1 ; |
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
|
my @coderefs = grep {$_->{selector} eq $selected} @choices; |
|
0
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
|
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
|
0
|
|
|
0
|
|
|
my $dsl = shift; |
309
|
0
|
|
|
|
|
|
my $switch = pop; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
$dsl->app->log ( error => |
312
|
|
|
|
|
|
|
"http_chosen_$switch does not exist" |
313
|
0
|
0
|
|
|
|
|
) unless exists $dsl->vars->{"http_chosen_$switch"}; |
314
|
|
|
|
|
|
|
|
315
|
0
|
0
|
|
|
|
|
$dsl->app->log( error => |
316
|
|
|
|
|
|
|
"http_chosen_$switch is designed for read-only" |
317
|
|
|
|
|
|
|
) if (@_ >= 1); |
318
|
|
|
|
|
|
|
|
319
|
0
|
0
|
|
|
|
|
return unless exists $dsl->vars->{"http_chosen_$switch"}; |
320
|
0
|
|
|
|
|
|
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
|
0
|
|
|
0
|
|
|
my @choices; |
336
|
0
|
|
|
|
|
|
while ( @_ ) { |
337
|
0
|
|
|
|
|
|
my ($choices, $coderef) = @{[ shift, shift ]}; |
|
0
|
|
|
|
|
|
|
338
|
0
|
0
|
|
|
|
|
last unless $choices; |
339
|
|
|
|
|
|
|
# turn a single value into a ARRAY REF |
340
|
0
|
0
|
|
|
|
|
$choices = [ $choices ] unless ref $choices eq 'ARRAY'; |
341
|
|
|
|
|
|
|
# so we only have ARRAY REFs to deal with |
342
|
0
|
|
|
|
|
|
foreach ( @$choices ) { |
343
|
0
|
0
|
|
|
|
|
if ( ref $coderef ne 'CODE' ) { |
344
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
push @choices, |
354
|
|
|
|
|
|
|
{ |
355
|
|
|
|
|
|
|
selector => $_, |
356
|
|
|
|
|
|
|
coderef => $coderef, |
357
|
|
|
|
|
|
|
}; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
0
|
|
|
|
|
|
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; |