line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# |
2
|
|
|
|
|
|
|
# Web::DataService::Execute |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# This module provides a role that is used by 'Web::DataService'. It implements |
5
|
|
|
|
|
|
|
# routines for executing requests. |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# Author: Michael McClennen |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
18
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
103
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Web::DataService::Execute; |
12
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
14
|
use Carp 'croak'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
131
|
|
14
|
2
|
|
|
2
|
|
15
|
use Scalar::Util qw(reftype weaken); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
94
|
|
15
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
12
|
use Moo::Role; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
18
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# new_request ( outer, attrs ) |
21
|
|
|
|
|
|
|
# |
22
|
|
|
|
|
|
|
# Generate a new request object, using the given attributes. $outer should be |
23
|
|
|
|
|
|
|
# a reference to an "outer" request object that was generated by the |
24
|
|
|
|
|
|
|
# underlying framework (i.e. Dancer or Mojolicious) or undef if there is |
25
|
|
|
|
|
|
|
# none. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub new_request { |
28
|
|
|
|
|
|
|
|
29
|
0
|
|
|
0
|
0
|
|
my ($ds, $outer, $attrs) = @_; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# First check the arguments to this method. |
32
|
|
|
|
|
|
|
|
33
|
0
|
0
|
0
|
|
|
|
croak "new_request: second argument must be a hashref\n" |
34
|
|
|
|
|
|
|
if defined $attrs && ref $attrs ne 'HASH'; |
35
|
|
|
|
|
|
|
|
36
|
0
|
|
0
|
|
|
|
$attrs ||= {}; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# If this was called as a class method rather than as an instance method, |
39
|
|
|
|
|
|
|
# then call 'select' to figure out the appropriate data service. |
40
|
|
|
|
|
|
|
|
41
|
0
|
0
|
|
|
|
|
unless ( ref $ds eq 'Web::DataService' ) |
42
|
|
|
|
|
|
|
{ |
43
|
0
|
|
|
|
|
|
$ds = Web::DataService->select($outer); |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Grab the request parameters from the foundation plugin. |
47
|
|
|
|
|
|
|
|
48
|
0
|
|
|
|
|
|
my $request_params = $Web::DataService::FOUNDATION->get_params($outer); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# If "path" was not specified as an attribute, determine it from the request |
51
|
|
|
|
|
|
|
# parameters and path. |
52
|
|
|
|
|
|
|
|
53
|
0
|
0
|
|
|
|
|
unless ( defined $attrs->{path} ) |
54
|
|
|
|
|
|
|
{ |
55
|
0
|
|
|
|
|
|
my $request_path = $Web::DataService::FOUNDATION->get_request_path($outer); |
56
|
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
|
$attrs->{path} = $ds->_determine_path($request_path, $request_params); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Now set the other required attributes, and create an object to represent |
61
|
|
|
|
|
|
|
# this request. |
62
|
|
|
|
|
|
|
|
63
|
0
|
|
|
|
|
|
$attrs->{outer} = $outer; |
64
|
0
|
|
|
|
|
|
$attrs->{ds} = $ds; |
65
|
0
|
|
0
|
|
|
|
$attrs->{http_method} = $Web::DataService::FOUNDATION->get_http_method($outer) || 'UNKNOWN'; |
66
|
|
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
my $request = Web::DataService::Request->new($attrs); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# Make sure that the outer object is linked back to this request object. |
70
|
|
|
|
|
|
|
# The link from the "inner" object to the "outer" must be weakened, |
71
|
|
|
|
|
|
|
# so that garbage collection works properly. |
72
|
|
|
|
|
|
|
|
73
|
0
|
0
|
|
|
|
|
weaken($request->{outer}) if ref $request->{outer}; |
74
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->store_inner($outer, $request); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# Return the new request object. |
77
|
|
|
|
|
|
|
|
78
|
0
|
|
|
|
|
|
return $request; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# _determine_path ( url_path, params ) |
83
|
|
|
|
|
|
|
# |
84
|
|
|
|
|
|
|
# Given the request URL path and parameters, determine what the request path |
85
|
|
|
|
|
|
|
# should be. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub _determine_path { |
88
|
|
|
|
|
|
|
|
89
|
0
|
|
|
0
|
|
|
my ($ds, $request_path, $request_params) = @_; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# If the special parameter 'path' is active, then we determine the result |
92
|
|
|
|
|
|
|
# from its value. If this parameter was not specified in the request, it |
93
|
|
|
|
|
|
|
# defaults to ''. |
94
|
|
|
|
|
|
|
|
95
|
0
|
0
|
|
|
|
|
if ( my $path_param = $ds->{special}{path} ) |
|
|
0
|
|
|
|
|
|
96
|
|
|
|
|
|
|
{ |
97
|
0
|
|
0
|
|
|
|
my $path = $request_params->{$path_param} // ''; |
98
|
0
|
|
|
|
|
|
return $path; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# Otherwise, we use the request path. In this case, if the data service |
102
|
|
|
|
|
|
|
# has a path regexp, use it to trim the path. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
elsif ( defined $request_path ) |
105
|
|
|
|
|
|
|
{ |
106
|
0
|
0
|
0
|
|
|
|
if ( defined $ds->{path_re} && $request_path =~ $ds->{path_re} ) |
107
|
|
|
|
|
|
|
{ |
108
|
0
|
|
0
|
|
|
|
return $1 // ''; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
else |
112
|
|
|
|
|
|
|
{ |
113
|
0
|
|
|
|
|
|
return $request_path; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Otherwise, return the empty string. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
else |
120
|
|
|
|
|
|
|
{ |
121
|
0
|
|
|
|
|
|
return ''; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# handle_request ( request ) |
127
|
|
|
|
|
|
|
# |
128
|
|
|
|
|
|
|
# Generate a new request object, match it to a data service node, and then execute |
129
|
|
|
|
|
|
|
# it. This is a convenience routine. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub handle_request { |
132
|
|
|
|
|
|
|
|
133
|
0
|
|
|
0
|
0
|
|
my ($ds, $outer, $attrs) = @_; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# If this was called as a class method rather than as an instance method, |
136
|
|
|
|
|
|
|
# then call 'select' to figure out the appropriate data service. |
137
|
|
|
|
|
|
|
|
138
|
0
|
0
|
|
|
|
|
unless ( ref $ds eq 'Web::DataService' ) |
139
|
|
|
|
|
|
|
{ |
140
|
0
|
|
|
|
|
|
$ds = Web::DataService->select($outer); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# Generate a new request object, then execute it. |
144
|
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
my $request = $ds->new_request($outer, $attrs); |
146
|
0
|
|
|
|
|
|
return $ds->execute_request($request); |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# execute_request ( request ) |
151
|
|
|
|
|
|
|
# |
152
|
|
|
|
|
|
|
# Execute a request. Depending upon the request path, it may either be |
153
|
|
|
|
|
|
|
# interpreted as a request for documentation or a request to execute some |
154
|
|
|
|
|
|
|
# operation and return a result. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub execute_request { |
157
|
|
|
|
|
|
|
|
158
|
0
|
|
|
0
|
0
|
|
my ($ds, $request) = @_; |
159
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
|
my $path = $request->node_path; |
161
|
0
|
|
|
|
|
|
my $format = $request->output_format; |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# Fetch the request method and the hash of allowed methods for this node. If none were |
164
|
|
|
|
|
|
|
# specified, default to GET and HEAD. |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
|
my $http_method = $request->http_method; |
167
|
0
|
|
0
|
|
|
|
my $allow_method = $ds->node_attr($request, 'allow_method') || { GET => 1, HEAD => 1 }; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# If this was called as a class method rather than as an instance method, |
170
|
|
|
|
|
|
|
# then call 'select' to figure out the appropriate data service. |
171
|
|
|
|
|
|
|
|
172
|
0
|
0
|
|
|
|
|
unless ( ref $ds eq 'Web::DataService' ) |
173
|
|
|
|
|
|
|
{ |
174
|
0
|
|
|
|
|
|
$ds = Web::DataService->select($request->outer); |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# Now that we have selected a data service instance, check to see if this |
178
|
|
|
|
|
|
|
# program is in diagnostic mode. If so, then divert this request to the |
179
|
|
|
|
|
|
|
# module Web::DataService::Diagnostic, and then exit the program when it |
180
|
|
|
|
|
|
|
# is done. |
181
|
|
|
|
|
|
|
|
182
|
0
|
0
|
|
|
|
|
if ( Web::DataService->is_mode('diagnostic') ) |
183
|
|
|
|
|
|
|
{ |
184
|
0
|
|
|
|
|
|
$ds->diagnostic_request($request); |
185
|
0
|
|
|
|
|
|
exit; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# If the request HTTP method was 'OPTIONS', then return a list of methods |
189
|
|
|
|
|
|
|
# allowed for this node path. |
190
|
|
|
|
|
|
|
|
191
|
0
|
0
|
|
|
|
|
if ( $http_method eq 'OPTIONS' ) |
192
|
|
|
|
|
|
|
{ |
193
|
0
|
0
|
|
|
|
|
my @methods = ref $allow_method eq 'HASH' ? keys %$allow_method : @Web::DataService::DEFAULT_METHODS; |
194
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
|
$ds->_set_cors_header($request); |
196
|
0
|
|
|
|
|
|
$ds->_set_response_header($request, 'Access-Control-Allow-Methods', join(',', @methods)); |
197
|
0
|
|
|
|
|
|
return; |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# Otherwise, this is a standard request. If a 'before_execute_hook' was |
201
|
|
|
|
|
|
|
# defined for this request, call it now. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'before_execute_hook', $request) |
204
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{before_execute_hook}; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# If the request has been tagged as an invalid path, then return a 404 error |
207
|
|
|
|
|
|
|
# right away. |
208
|
|
|
|
|
|
|
|
209
|
0
|
0
|
|
|
|
|
die "404\n" if $request->{is_invalid_request}; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# If the request has been tagged as a "documentation path", then show the |
212
|
|
|
|
|
|
|
# documentation. The only allowed methods for documentation are GET and HEAD. |
213
|
|
|
|
|
|
|
|
214
|
0
|
0
|
0
|
|
|
|
if ( $request->{is_node_path} && $request->{is_doc_request} && $ds->has_feature('documentation') ) |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
215
|
|
|
|
|
|
|
{ |
216
|
0
|
0
|
0
|
|
|
|
unless ( $http_method eq 'GET' || $http_method eq 'HEAD' ) |
217
|
|
|
|
|
|
|
{ |
218
|
0
|
|
|
|
|
|
die "405 Method Not Allowed\n"; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
0
|
|
|
|
|
|
return $ds->generate_doc($request); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# If the 'is_file_path' attribute is set, we should be sending a file. Figure out the path |
225
|
|
|
|
|
|
|
# and send it. We don't currently allow uploading files, so the only allowed methods are GET |
226
|
|
|
|
|
|
|
# and HEAD. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
elsif ( $request->{is_file_path} && $ds->has_feature('send_files') ) |
229
|
|
|
|
|
|
|
{ |
230
|
0
|
0
|
0
|
|
|
|
unless ( $http_method eq 'GET' || $http_method eq 'HEAD' ) |
231
|
|
|
|
|
|
|
{ |
232
|
0
|
|
|
|
|
|
die "405 Method Not Allowed\n"; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
0
|
|
|
|
|
|
return $ds->send_file($request); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# If the selected node has an operation, execute it and return the result. But we first have |
239
|
|
|
|
|
|
|
# to check if the request method is allowed. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
elsif ( $request->{is_node_path} && $ds->node_has_operation($path) ) |
242
|
|
|
|
|
|
|
{ |
243
|
|
|
|
|
|
|
# Always allow HEAD if GET is allowed. But otherwise reject any request that doesn't have |
244
|
|
|
|
|
|
|
# an allowed method. |
245
|
|
|
|
|
|
|
|
246
|
0
|
0
|
|
|
|
|
my $check_method = $http_method eq 'HEAD' ? 'GET' : $http_method; |
247
|
|
|
|
|
|
|
|
248
|
0
|
0
|
0
|
|
|
|
unless ( $allow_method->{$http_method} || $allow_method->{$check_method} ) |
249
|
|
|
|
|
|
|
{ |
250
|
0
|
|
|
|
|
|
die "405 Method Not Allowed\n"; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# Almost all requests will go through this branch of the code. This leads to the actual |
254
|
|
|
|
|
|
|
# execution of data service operations. |
255
|
|
|
|
|
|
|
|
256
|
0
|
|
|
|
|
|
$ds->configure_request($request); |
257
|
0
|
|
|
|
|
|
return $ds->generate_result($request); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# If the request cannot be satisfied in any of these ways, then return a 404 error. |
261
|
|
|
|
|
|
|
|
262
|
0
|
|
|
|
|
|
die "404\n"; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# send_file ( request ) |
267
|
|
|
|
|
|
|
# |
268
|
|
|
|
|
|
|
# Send a file using the attributes specified in the request node. |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub send_file { |
271
|
|
|
|
|
|
|
|
272
|
0
|
|
|
0
|
0
|
|
my ($ds, $request) = @_; |
273
|
|
|
|
|
|
|
|
274
|
0
|
0
|
|
|
|
|
die "404\n" if $request->{is_invalid_request}; |
275
|
|
|
|
|
|
|
|
276
|
0
|
|
|
|
|
|
my $rest_path = $request->{rest_path}; |
277
|
0
|
|
|
|
|
|
my $file_dir = $ds->node_attr($request, 'file_dir'); |
278
|
0
|
|
|
|
|
|
my $file_path; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# How we handle this depends upon whether 'file_dir' or 'file_path' was |
281
|
|
|
|
|
|
|
# set. With 'file_dir', an empty file name will always return a 404 |
282
|
|
|
|
|
|
|
# error, since the only other logical response would be a list of the base |
283
|
|
|
|
|
|
|
# directory and we don't want to provide that for security reasons. |
284
|
|
|
|
|
|
|
|
285
|
0
|
0
|
|
|
|
|
if ( $file_dir ) |
286
|
|
|
|
|
|
|
{ |
287
|
0
|
0
|
0
|
|
|
|
die "404\n" unless defined $rest_path && $rest_path ne ''; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# Concatenate the path components together, using the foundation plugin so |
290
|
|
|
|
|
|
|
# that this is done in a file-system-independent manner. |
291
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
|
$file_path = $Web::DataService::FOUNDATION->file_path($file_dir, $rest_path); |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Otherwise, $rest_path must be empty or else we send back a 404 error. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
else |
298
|
|
|
|
|
|
|
{ |
299
|
0
|
0
|
0
|
|
|
|
die "404\n" if defined $rest_path && $rest_path ne ''; |
300
|
|
|
|
|
|
|
|
301
|
0
|
|
|
|
|
|
$file_path = $ds->node_attr($request, 'file_path'); |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# If this file does not exist, return a 404 error. This is necessary so |
305
|
|
|
|
|
|
|
# that the error handling will by done by Web::DataService rather than by |
306
|
|
|
|
|
|
|
# Dancer. If the file exists but is not readable, return a 500 error. |
307
|
|
|
|
|
|
|
# This is not a permission error, it is an internal server error. |
308
|
|
|
|
|
|
|
|
309
|
0
|
0
|
|
|
|
|
unless ( $Web::DataService::FOUNDATION->file_readable($file_path) ) |
310
|
|
|
|
|
|
|
{ |
311
|
0
|
0
|
|
|
|
|
die "500" if $Web::DataService::FOUNDATION->file_exists($file_path); |
312
|
0
|
|
|
|
|
|
die "404\n"; # otherwise |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# Otherwise, send the file. |
316
|
|
|
|
|
|
|
|
317
|
0
|
|
|
|
|
|
return $Web::DataService::FOUNDATION->send_file($request->outer, $file_path); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# node_has_operation ( path ) |
322
|
|
|
|
|
|
|
# |
323
|
|
|
|
|
|
|
# If this class has both a role and a method defined, then return the method |
324
|
|
|
|
|
|
|
# name. Return undefined otherwise. This method can be used to determine |
325
|
|
|
|
|
|
|
# whether a particular path is valid for executing a data service operation. |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub node_has_operation { |
328
|
|
|
|
|
|
|
|
329
|
0
|
|
|
0
|
0
|
|
my ($ds, $path) = @_; |
330
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
my $role = $ds->node_attr($path, 'role'); |
332
|
0
|
|
|
|
|
|
my $method = $ds->node_attr($path, 'method'); |
333
|
|
|
|
|
|
|
|
334
|
0
|
0
|
0
|
|
|
|
return $method if $role && $method; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# configure_request ( request ) |
339
|
|
|
|
|
|
|
# |
340
|
|
|
|
|
|
|
# Determine the attributes necessary for executing the data service operation |
341
|
|
|
|
|
|
|
# corresponding to the specified request. |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub configure_request { |
344
|
|
|
|
|
|
|
|
345
|
0
|
|
|
0
|
0
|
|
my ($ds, $request) = @_; |
346
|
|
|
|
|
|
|
|
347
|
0
|
|
|
|
|
|
my $path = $request->node_path; |
348
|
|
|
|
|
|
|
|
349
|
0
|
0
|
0
|
|
|
|
die "404\n" if $request->{is_invalid_request} || $ds->node_attr($path, 'disabled'); |
350
|
|
|
|
|
|
|
|
351
|
0
|
|
|
|
|
|
$request->{_configured} = 1; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# If we are in 'one request' mode, initialize this request's primary |
354
|
|
|
|
|
|
|
# role. If we are not in this mode, then all of the roles will have |
355
|
|
|
|
|
|
|
# been previously initialized. |
356
|
|
|
|
|
|
|
|
357
|
0
|
0
|
|
|
|
|
if ( $Web::DataService::ONE_REQUEST ) |
358
|
|
|
|
|
|
|
{ |
359
|
0
|
|
|
|
|
|
my $role = $ds->node_attr($path, 'role'); |
360
|
0
|
|
|
|
|
|
$ds->initialize_role($role); |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# If a before_config_hook was specified for this node, call it now. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'before_config_hook', $request) |
366
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{before_config_hook}; |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
# Get the raw parameters for this request, if they have not already been gotten. |
369
|
|
|
|
|
|
|
|
370
|
0
|
|
0
|
|
|
|
$request->{raw_params} //= $Web::DataService::FOUNDATION->get_params($request); |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# Check to see if there is a ruleset corresponding to this path. If |
373
|
|
|
|
|
|
|
# so, then validate the parameters according to that ruleset. |
374
|
|
|
|
|
|
|
|
375
|
0
|
|
|
|
|
|
my $rs_name = $ds->node_attr($path, 'ruleset'); |
376
|
|
|
|
|
|
|
|
377
|
0
|
|
0
|
|
|
|
$rs_name //= $ds->determine_ruleset($path); |
378
|
|
|
|
|
|
|
|
379
|
0
|
0
|
|
|
|
|
if ( $rs_name ) |
380
|
|
|
|
|
|
|
{ |
381
|
0
|
|
|
|
|
|
my $context = { ds => $ds, request => $request }; |
382
|
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
|
my $result = $ds->{validator}->check_params($rs_name, $context, $request->{raw_params}); |
384
|
|
|
|
|
|
|
|
385
|
0
|
0
|
|
|
|
|
if ( $result->errors ) |
|
|
0
|
|
|
|
|
|
386
|
|
|
|
|
|
|
{ |
387
|
0
|
|
|
|
|
|
die $result; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
elsif ( $result->warnings ) |
391
|
|
|
|
|
|
|
{ |
392
|
0
|
|
|
|
|
|
$request->add_warning($result->warnings); |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
0
|
|
|
|
|
|
$request->{clean_params} = $result->values; |
396
|
0
|
|
|
|
|
|
$request->{valid} = $result; |
397
|
0
|
|
|
|
|
|
$request->{ruleset} = $rs_name; |
398
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
|
if ( $ds->debug ) |
400
|
|
|
|
|
|
|
{ |
401
|
0
|
|
|
|
|
|
my $dsname = $ds->name; |
402
|
0
|
|
|
|
|
|
print STDERR "---------------\nOperation $dsname '$path'\n"; |
403
|
0
|
|
|
|
|
|
foreach my $p ( $result->keys ) |
404
|
|
|
|
|
|
|
{ |
405
|
0
|
|
|
|
|
|
my $value = $result->value($p); |
406
|
0
|
0
|
|
|
|
|
$value = join(', ', @$value) if ref $value eq 'ARRAY'; |
407
|
0
|
|
0
|
|
|
|
$value ||= '[ NO GOOD VALUES FOUND ]'; |
408
|
0
|
|
|
|
|
|
print STDERR "$p = $value\n"; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# Otherwise, just pass the raw parameters along with no validation or |
414
|
|
|
|
|
|
|
# processing. |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
else |
417
|
|
|
|
|
|
|
{ |
418
|
0
|
0
|
|
|
|
|
print STDERR "No ruleset could be determined for path '$path'\n" if $ds->debug; |
419
|
0
|
|
|
|
|
|
$request->{valid} = undef; |
420
|
0
|
|
|
|
|
|
$request->{clean_params} = $request->{raw_params}; |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# Now that the parameters have been processed, we can configure all of |
424
|
|
|
|
|
|
|
# the settings that might be specified or affected by parameter values: |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# If the output format is not already set, then try to determine what |
427
|
|
|
|
|
|
|
# it should be. |
428
|
|
|
|
|
|
|
|
429
|
0
|
0
|
|
|
|
|
unless ( $request->output_format ) |
430
|
|
|
|
|
|
|
{ |
431
|
|
|
|
|
|
|
# If the special parameter 'format' is enabled, check to see if a |
432
|
|
|
|
|
|
|
# value for that parameter was given. |
433
|
|
|
|
|
|
|
|
434
|
0
|
|
|
|
|
|
my $format; |
435
|
0
|
|
|
|
|
|
my $format_param = $ds->{special}{format}; |
436
|
|
|
|
|
|
|
|
437
|
0
|
0
|
|
|
|
|
if ( $format_param ) |
438
|
|
|
|
|
|
|
{ |
439
|
0
|
|
|
|
|
|
$format = $request->{clean_params}{$format_param}; |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# If we still don't have a format, and there is a default format |
443
|
|
|
|
|
|
|
# specified for this path, use that. |
444
|
|
|
|
|
|
|
|
445
|
0
|
|
0
|
|
|
|
$format //= $ds->node_attr($path, 'default_format'); |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# Otherwise, use the first format defined. |
448
|
|
|
|
|
|
|
|
449
|
0
|
|
0
|
|
|
|
$format //= ${$ds->{format_list}}[0]; |
|
0
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# If we have successfully determined a format, then set the result |
452
|
|
|
|
|
|
|
# object's output format attribute. |
453
|
|
|
|
|
|
|
|
454
|
0
|
0
|
|
|
|
|
$request->output_format($format) if $format; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
# Next, determine the result limit and offset, if any. If the special |
458
|
|
|
|
|
|
|
# parameter 'limit' is active, then see if this request included it. |
459
|
|
|
|
|
|
|
# If we couldn't get a parameter value, see if a default limit was |
460
|
|
|
|
|
|
|
# specified for this node or for the data service as a whole. |
461
|
|
|
|
|
|
|
|
462
|
0
|
|
0
|
|
|
|
my $limit_value = $request->special_value('limit') // |
463
|
|
|
|
|
|
|
$ds->node_attr($path, 'default_limit'); |
464
|
|
|
|
|
|
|
|
465
|
0
|
0
|
|
|
|
|
$request->result_limit($limit_value) if defined $limit_value; |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
# If the special parameter 'offset' is active, then see if this result |
468
|
|
|
|
|
|
|
# included it. |
469
|
|
|
|
|
|
|
|
470
|
0
|
|
|
|
|
|
my $offset_value = $request->special_value('offset'); |
471
|
|
|
|
|
|
|
|
472
|
0
|
0
|
|
|
|
|
$request->result_offset($offset_value) if defined $offset_value; |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# Determine whether we should show the optional header information in |
475
|
|
|
|
|
|
|
# the result. |
476
|
|
|
|
|
|
|
|
477
|
0
|
|
0
|
|
|
|
my $header_value = $request->special_value('header') // |
478
|
|
|
|
|
|
|
$ds->node_attr($path, 'default_header'); |
479
|
|
|
|
|
|
|
|
480
|
0
|
0
|
|
|
|
|
$request->display_header($header_value) if defined $header_value; |
481
|
|
|
|
|
|
|
|
482
|
0
|
|
0
|
|
|
|
my $source_value = $request->special_value('datainfo') // |
483
|
|
|
|
|
|
|
$ds->node_attr($path, 'default_datainfo'); |
484
|
|
|
|
|
|
|
|
485
|
0
|
0
|
|
|
|
|
$request->display_datainfo($source_value) if defined $source_value; |
486
|
|
|
|
|
|
|
|
487
|
0
|
|
0
|
|
|
|
my $count_value = $request->special_value('count') // |
488
|
|
|
|
|
|
|
$ds->node_attr($path, 'default_count'); |
489
|
|
|
|
|
|
|
|
490
|
0
|
0
|
|
|
|
|
$request->display_counts($count_value) if defined $count_value; |
491
|
|
|
|
|
|
|
|
492
|
0
|
|
0
|
|
|
|
my $output_linebreak = $request->special_value('linebreak') || |
493
|
|
|
|
|
|
|
$ds->node_attr($path, 'default_linebreak') || 'crlf'; |
494
|
|
|
|
|
|
|
|
495
|
0
|
|
|
|
|
|
$request->output_linebreak($output_linebreak); |
496
|
|
|
|
|
|
|
|
497
|
0
|
|
|
|
|
|
my $save_specified = $request->special_given('save'); |
498
|
0
|
|
0
|
|
|
|
my $save_value = $request->special_value('save') || ''; |
499
|
|
|
|
|
|
|
|
500
|
0
|
0
|
|
|
|
|
if ( $save_specified ) |
501
|
|
|
|
|
|
|
{ |
502
|
0
|
0
|
|
|
|
|
if ( $save_value =~ qr{ ^ (?: no | off | 0 | false ) $ }xsi ) |
503
|
|
|
|
|
|
|
{ |
504
|
0
|
|
|
|
|
|
$request->save_output(0); |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
else |
508
|
|
|
|
|
|
|
{ |
509
|
0
|
|
|
|
|
|
$request->save_output(1); |
510
|
0
|
0
|
0
|
|
|
|
$request->save_filename($save_value) if $save_value ne '' && |
511
|
|
|
|
|
|
|
$save_value !~ qr{ ^ (?: yes | on | 1 | true ) $ }xsi; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
# Determine which vocabulary to use. If the special parameter 'vocab' is |
516
|
|
|
|
|
|
|
# active, check that first. |
517
|
|
|
|
|
|
|
|
518
|
0
|
|
|
|
|
|
my $vocab_value = $request->special_value('vocab'); |
519
|
|
|
|
|
|
|
|
520
|
0
|
0
|
|
|
|
|
$request->output_vocab($vocab_value) if defined $vocab_value; |
521
|
|
|
|
|
|
|
|
522
|
0
|
|
|
|
|
|
my $a = 1; # we can stop here when debugging |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# generate_result ( request ) |
527
|
|
|
|
|
|
|
# |
528
|
|
|
|
|
|
|
# Execute the operation corresponding to the attributes of the node selected |
529
|
|
|
|
|
|
|
# by the given request, and return the resulting data. This routine is, in |
530
|
|
|
|
|
|
|
# many ways, the core of this entire project. |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub generate_result { |
533
|
|
|
|
|
|
|
|
534
|
0
|
|
|
0
|
0
|
|
my ($ds, $request) = @_; |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
croak "generate_result: you must first call the method 'configure'\n" |
537
|
0
|
0
|
|
|
|
|
unless $request->{_configured}; |
538
|
|
|
|
|
|
|
|
539
|
0
|
|
|
|
|
|
my $path = $request->node_path; |
540
|
0
|
|
|
|
|
|
my $format = $request->output_format; |
541
|
|
|
|
|
|
|
|
542
|
0
|
|
|
|
|
|
my $method = $ds->node_attr($path, 'method'); |
543
|
0
|
|
|
|
|
|
my $arg = $ds->node_attr($path, 'arg'); |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# First determine the class that corresponds to this request's primary role |
546
|
|
|
|
|
|
|
# and bless the request into that class. |
547
|
|
|
|
|
|
|
|
548
|
0
|
|
|
|
|
|
my $role = $ds->node_attr($request, 'role'); |
549
|
0
|
|
|
|
|
|
bless $request, $ds->execution_class($role); |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# If a before_setup_hook is defined for this path, call it. |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'before_setup_hook', $request) |
554
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{before_setup_hook}; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# First check to make sure that the specified format is valid for the |
557
|
|
|
|
|
|
|
# specified path. |
558
|
|
|
|
|
|
|
|
559
|
0
|
0
|
|
|
|
|
unless ( $ds->valid_format_for($path, $format) ) |
560
|
|
|
|
|
|
|
{ |
561
|
0
|
|
|
|
|
|
die "415\n"; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
# defined $format && ref $ds->{format}{$format} && |
565
|
|
|
|
|
|
|
# ! $ds->{format}{$format}{disabled} && |
566
|
|
|
|
|
|
|
# $attrs->{allow_format}{$format} ) |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
# Then we need to make sure that an output vocabulary is selected. If no |
569
|
|
|
|
|
|
|
# vocabulary was explicitly specified, then try the default for the |
570
|
|
|
|
|
|
|
# selected format. As a backup, we use the first vocabulary defined for |
571
|
|
|
|
|
|
|
# the data service, which will be the default vocabulary if none were |
572
|
|
|
|
|
|
|
# explicitly defined. |
573
|
|
|
|
|
|
|
|
574
|
0
|
0
|
|
|
|
|
unless ( my $vocab_value = $request->output_vocab ) |
575
|
|
|
|
|
|
|
{ |
576
|
|
|
|
|
|
|
$vocab_value = $ds->{format}{$format}{default_vocab} || |
577
|
0
|
|
0
|
|
|
|
$ds->{vocab_list}[0]; |
578
|
|
|
|
|
|
|
|
579
|
0
|
|
|
|
|
|
$request->output_vocab($vocab_value); |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
# Now that we know the format, we can set the response headers. |
583
|
|
|
|
|
|
|
|
584
|
0
|
|
|
|
|
|
$ds->_set_cors_header($request); |
585
|
0
|
|
|
|
|
|
$ds->_set_content_type($request); |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
# If the format indicates that the output should be returned as an |
588
|
|
|
|
|
|
|
# attachment (which tells the browser to save it to disk), note this fact. |
589
|
|
|
|
|
|
|
|
590
|
0
|
|
|
|
|
|
my $save_flag = $request->save_output; |
591
|
0
|
|
|
|
|
|
my $disp = $ds->{format}{$format}{disposition}; |
592
|
|
|
|
|
|
|
|
593
|
0
|
0
|
0
|
|
|
|
if ( defined $save_flag && $save_flag eq '0' ) |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
594
|
|
|
|
|
|
|
{ |
595
|
|
|
|
|
|
|
#$ds->_set_content_disposition($request, 'inline'); |
596
|
0
|
0
|
|
|
|
|
$ds->_set_content_type($request, 'text/plain') if $ds->{format}{$format}{is_text}; |
597
|
0
|
|
|
|
|
|
$request->{content_type_is_text} = 1; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
elsif ( ( defined $disp && $disp eq 'attachment' ) || |
601
|
|
|
|
|
|
|
$save_flag ) |
602
|
|
|
|
|
|
|
{ |
603
|
0
|
|
|
|
|
|
$ds->_set_content_disposition($request, 'attachment', $request->save_filename); |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# Then set up the output. This involves constructing a list of |
607
|
|
|
|
|
|
|
# specifiers that indicate which fields will be included in the output |
608
|
|
|
|
|
|
|
# and how they will be processed. |
609
|
|
|
|
|
|
|
|
610
|
0
|
|
|
|
|
|
$ds->_setup_output($request); |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# If a summary block has been specified for this request, configure it as |
613
|
|
|
|
|
|
|
# well. |
614
|
|
|
|
|
|
|
|
615
|
0
|
0
|
|
|
|
|
if ( my $summary_block = $ds->node_attr($request, 'summary') ) |
616
|
|
|
|
|
|
|
{ |
617
|
0
|
0
|
|
|
|
|
if ( $ds->configure_block($request, $summary_block) ) |
618
|
|
|
|
|
|
|
{ |
619
|
0
|
|
|
|
|
|
$request->{summary_field_list} = $request->{block_field_list}{$summary_block}; |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
else |
622
|
|
|
|
|
|
|
{ |
623
|
0
|
|
|
|
|
|
$request->add_warning("Summary block '$summary_block' not found"); |
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# If a before_operation_hook is defined for this path, call it. |
628
|
|
|
|
|
|
|
# Also check for post_configure_hook, for backward compatibility. |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'post_configure_hook', $request) |
631
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{post_configure_hook}; |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'before_operation_hook', $request) |
634
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{before_operation_hook}; |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# Prepare to time the query operation. |
637
|
|
|
|
|
|
|
|
638
|
0
|
|
|
|
|
|
my (@starttime) = Time::HiRes::gettimeofday(); |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# Now execute the query operation. This is the central step of this |
641
|
|
|
|
|
|
|
# entire routine; everything before and after is in support of this call. |
642
|
|
|
|
|
|
|
|
643
|
0
|
|
|
|
|
|
$request->$method($arg); |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# Determine how long the query took. |
646
|
|
|
|
|
|
|
|
647
|
0
|
|
|
|
|
|
my (@endtime) = Time::HiRes::gettimeofday(); |
648
|
0
|
|
|
|
|
|
$request->{elapsed} = Time::HiRes::tv_interval(\@starttime, \@endtime); |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
# If a before_output_hook is defined for this path, call it. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
$ds->_call_hooks($path, 'before_output_hook', $request) |
653
|
0
|
0
|
|
|
|
|
if $ds->{hook_enabled}{before_output_hook}; |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
# Then we use the output configuration and the result of the query |
656
|
|
|
|
|
|
|
# operation to generate the actual output. How we do this depends |
657
|
|
|
|
|
|
|
# upon how the operation method chooses to return its data. It must |
658
|
|
|
|
|
|
|
# set one of the following fields in the request object, as described: |
659
|
|
|
|
|
|
|
# |
660
|
|
|
|
|
|
|
# main_data A scalar, containing data which is to be |
661
|
|
|
|
|
|
|
# returned as-is without further processing. |
662
|
|
|
|
|
|
|
# |
663
|
|
|
|
|
|
|
# main_record A hashref, representing a single record to be |
664
|
|
|
|
|
|
|
# returned according to the output format. |
665
|
|
|
|
|
|
|
# |
666
|
|
|
|
|
|
|
# main_result A list of hashrefs, representing multiple |
667
|
|
|
|
|
|
|
# records to be returned according to the output |
668
|
|
|
|
|
|
|
# format. |
669
|
|
|
|
|
|
|
# |
670
|
|
|
|
|
|
|
# main_sth A DBI statement handle, from which all |
671
|
|
|
|
|
|
|
# records that can be read should be returned |
672
|
|
|
|
|
|
|
# according to the output format. |
673
|
|
|
|
|
|
|
# |
674
|
|
|
|
|
|
|
# It is okay for main_result and main_sth to both be set, in which |
675
|
|
|
|
|
|
|
# case the records in the former will be sent first and then the |
676
|
|
|
|
|
|
|
# latter will be read. |
677
|
|
|
|
|
|
|
|
678
|
0
|
0
|
0
|
|
|
|
if ( ref $request->{main_record} ) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
679
|
|
|
|
|
|
|
{ |
680
|
0
|
|
|
|
|
|
return $ds->_generate_single_result($request); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
elsif ( ref $request->{main_sth} or ref $request->{main_result} ) |
684
|
|
|
|
|
|
|
{ |
685
|
|
|
|
|
|
|
my $threshold = $ds->node_attr($path, 'streaming_threshold') |
686
|
0
|
0
|
|
|
|
|
unless $request->{do_not_stream}; |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
# If the result set requires processing before output, then call |
689
|
|
|
|
|
|
|
# _generate_processed_result. Otherwise, call |
690
|
|
|
|
|
|
|
# _generate_compound_result. One of the conditions that can cause |
691
|
|
|
|
|
|
|
# this to happen is if record counts are requested and generating them |
692
|
|
|
|
|
|
|
# requires processing (i.e. because a 'check' rule was encountered). |
693
|
|
|
|
|
|
|
|
694
|
0
|
0
|
0
|
|
|
|
$request->{preprocess} = 1 if $request->display_counts && $request->{process_before_count}; |
695
|
|
|
|
|
|
|
|
696
|
0
|
0
|
|
|
|
|
if ( $request->{preprocess} ) |
697
|
|
|
|
|
|
|
{ |
698
|
0
|
|
|
|
|
|
return $ds->_generate_processed_result($request, $threshold); |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
else |
702
|
|
|
|
|
|
|
{ |
703
|
0
|
|
|
|
|
|
return $ds->_generate_compound_result($request, $threshold); |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
elsif ( defined $request->{main_data} ) |
708
|
|
|
|
|
|
|
{ |
709
|
0
|
|
|
|
|
|
return $request->{main_data}; |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
# If none of these fields are set, then the result set is empty. |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
else |
715
|
|
|
|
|
|
|
{ |
716
|
0
|
|
|
|
|
|
return $ds->_generate_empty_result($request); |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
} |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
# _call_hooks ( path, hook, request ) |
722
|
|
|
|
|
|
|
# |
723
|
|
|
|
|
|
|
# If the specified hook has been defined for the specified path, call each of |
724
|
|
|
|
|
|
|
# the defined values. If the value is a code reference, call it with the |
725
|
|
|
|
|
|
|
# request as the only parameter. If it is a string, call it as a method of |
726
|
|
|
|
|
|
|
# the request object. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
sub _call_hooks { |
729
|
|
|
|
|
|
|
|
730
|
0
|
|
|
0
|
|
|
my ($ds, $path, $hook_name, $request, @args) = @_; |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
# Look up the list of hooks, if any, defined for this node. |
733
|
|
|
|
|
|
|
|
734
|
0
|
|
0
|
|
|
|
my $hook_list = $ds->node_attr($path, $hook_name) || return; |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# Then call each hook in turn. The return value will be the return value of the hook last |
737
|
|
|
|
|
|
|
# called, which will be the one that is defined furthest down in the hierarchy. |
738
|
|
|
|
|
|
|
|
739
|
0
|
|
|
|
|
|
foreach my $hook ( @$hook_list ) |
740
|
|
|
|
|
|
|
{ |
741
|
0
|
0
|
|
|
|
|
if ( ref $hook eq 'CODE' ) |
|
|
0
|
|
|
|
|
|
742
|
|
|
|
|
|
|
{ |
743
|
0
|
|
|
|
|
|
&$hook($request, @args); |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
elsif ( defined $hook ) |
747
|
|
|
|
|
|
|
{ |
748
|
0
|
|
|
|
|
|
$request->$hook(@args); |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
} |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
sub _call_hook_list { |
755
|
|
|
|
|
|
|
|
756
|
0
|
|
|
0
|
|
|
my ($ds, $hook_list, $request, @args) = @_; |
757
|
|
|
|
|
|
|
|
758
|
0
|
|
|
|
|
|
foreach my $hook ( @$hook_list ) |
759
|
|
|
|
|
|
|
{ |
760
|
0
|
0
|
|
|
|
|
if ( ref $hook eq 'CODE' ) |
|
|
0
|
|
|
|
|
|
761
|
|
|
|
|
|
|
{ |
762
|
0
|
|
|
|
|
|
&$hook($request, @args); |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
elsif ( defined $hook ) |
766
|
|
|
|
|
|
|
{ |
767
|
0
|
|
|
|
|
|
$request->$hook(@args); |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
sub _set_cors_header { |
774
|
|
|
|
|
|
|
|
775
|
0
|
|
|
0
|
|
|
my ($ds, $request, $arg) = @_; |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
# If this is a public-access data service, we add a universal CORS header. |
778
|
|
|
|
|
|
|
# At some point we need to add provision for authenticated access. |
779
|
|
|
|
|
|
|
|
780
|
0
|
0
|
0
|
|
|
|
if ( (defined $arg && $arg eq '*') || $ds->node_attr($request, 'public_access') ) |
|
|
|
0
|
|
|
|
|
781
|
|
|
|
|
|
|
{ |
782
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($request->outer, "Access-Control-Allow-Origin", "*"); |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
sub _set_response_header { |
788
|
|
|
|
|
|
|
|
789
|
0
|
|
|
0
|
|
|
my ($ds, $request, $header, $value) = @_; |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
# Set the specified response header, with the given value. |
792
|
|
|
|
|
|
|
|
793
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($request->outer, $header, $value); |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
sub _set_content_type { |
798
|
|
|
|
|
|
|
|
799
|
0
|
|
|
0
|
|
|
my ($ds, $request, $ct) = @_; |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
# If the content type was not explicitly given, choose it based on the |
802
|
|
|
|
|
|
|
# output format. |
803
|
|
|
|
|
|
|
|
804
|
0
|
0
|
|
|
|
|
unless ( $ct ) |
805
|
|
|
|
|
|
|
{ |
806
|
0
|
|
|
|
|
|
my $format = $request->output_format; |
807
|
0
|
|
0
|
|
|
|
$ct = $ds->{format}{$format}{content_type} || 'text/plain'; |
808
|
|
|
|
|
|
|
} |
809
|
|
|
|
|
|
|
|
810
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_content_type($request->outer, $ct); |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
sub _set_content_disposition { |
815
|
|
|
|
|
|
|
|
816
|
0
|
|
|
0
|
|
|
my ($ds, $request, $disp, $filename) = @_; |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
# If we were given a disposition of 'inline', then set that. |
819
|
|
|
|
|
|
|
|
820
|
0
|
0
|
|
|
|
|
if ( $disp eq 'inline' ) |
821
|
|
|
|
|
|
|
{ |
822
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($request->outer, 'Content-Disposition' => 'inline'); |
823
|
0
|
|
|
|
|
|
return; |
824
|
|
|
|
|
|
|
} |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
# If we weren't given an explicit filename, check to see if one was set |
827
|
|
|
|
|
|
|
# for this node. |
828
|
|
|
|
|
|
|
|
829
|
0
|
|
0
|
|
|
|
$filename //= $ds->node_attr($request, 'default_save_filename'); |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
# If we still don't have a filename, return without doing anything. |
832
|
|
|
|
|
|
|
|
833
|
0
|
0
|
|
|
|
|
return unless $filename; |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
# Otherwise, set the appropriate header. If the filename does not already |
836
|
|
|
|
|
|
|
# include a suffix, add the format. |
837
|
|
|
|
|
|
|
|
838
|
0
|
0
|
|
|
|
|
unless ( $filename =~ qr{ [^.] [.] \w+ $ }xs ) |
839
|
|
|
|
|
|
|
{ |
840
|
0
|
|
|
|
|
|
$filename .= '.' . $request->output_format; |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($request->outer, 'Content-Disposition' => |
844
|
|
|
|
|
|
|
qq{attachment; filename="$filename"}); |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
# valid_format_for ( path, format ) |
849
|
|
|
|
|
|
|
# |
850
|
|
|
|
|
|
|
# Return true if the specified format is valid for the specified path, false |
851
|
|
|
|
|
|
|
# otherwise. |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
sub valid_format_for { |
854
|
|
|
|
|
|
|
|
855
|
0
|
|
|
0
|
0
|
|
my ($ds, $path, $format) = @_; |
856
|
|
|
|
|
|
|
|
857
|
0
|
|
|
|
|
|
my $allow_format = $ds->node_attr($path, 'allow_format'); |
858
|
0
|
0
|
|
|
|
|
return unless ref $allow_format eq 'HASH'; |
859
|
0
|
|
|
|
|
|
return $allow_format->{$format}; |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
# determine_ruleset ( ) |
864
|
|
|
|
|
|
|
# |
865
|
|
|
|
|
|
|
# Determine the ruleset that should apply to this request. If a ruleset name |
866
|
|
|
|
|
|
|
# was explicitly specified for the request path, then use that if it is |
867
|
|
|
|
|
|
|
# defined or throw an exception if not. Otherwise, try the path with slashes |
868
|
|
|
|
|
|
|
# turned into commas and the optional ruleset_prefix applied. |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
sub determine_ruleset { |
871
|
|
|
|
|
|
|
|
872
|
0
|
|
|
0
|
0
|
|
my ($ds, $path) = @_; |
873
|
|
|
|
|
|
|
|
874
|
0
|
|
|
|
|
|
my $validator = $ds->{validator}; |
875
|
0
|
|
|
|
|
|
my $ruleset = $ds->node_attr($path, 'ruleset'); |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
# If a ruleset name was explicitly given, then use that or throw an |
878
|
|
|
|
|
|
|
# exception if not defined. |
879
|
|
|
|
|
|
|
|
880
|
0
|
0
|
0
|
|
|
|
if ( defined $ruleset && $ruleset ne '' ) |
881
|
|
|
|
|
|
|
{ |
882
|
0
|
0
|
|
|
|
|
croak "unknown ruleset '$ruleset' for path $path" |
883
|
|
|
|
|
|
|
unless $validator->ruleset_defined($ruleset); |
884
|
|
|
|
|
|
|
|
885
|
0
|
|
|
|
|
|
return $ruleset; |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
# If the ruleset was explicitly specified as '', do not process the |
889
|
|
|
|
|
|
|
# parameters for this path. |
890
|
|
|
|
|
|
|
|
891
|
0
|
0
|
|
|
|
|
return if defined $ruleset; |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# If the path is either empty or the root node '/', likewise return false. |
894
|
|
|
|
|
|
|
|
895
|
0
|
0
|
0
|
|
|
|
return unless defined $path && $path ne '' && $path ne '/'; |
|
|
|
0
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
# Otherwise, try the path with / replaced by :. If that is not defined, |
898
|
|
|
|
|
|
|
# then return empty. The parameters for this path will not be processed. |
899
|
|
|
|
|
|
|
|
900
|
0
|
|
|
|
|
|
$path =~ s{/}{:}g; |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
$path = $ds->{ruleset_prefix} . $path |
903
|
0
|
0
|
0
|
|
|
|
if defined $ds->{ruleset_prefix} && $ds->{ruleset_prefix} ne ''; |
904
|
|
|
|
|
|
|
|
905
|
0
|
0
|
|
|
|
|
return $path if $validator->ruleset_defined($path); |
906
|
|
|
|
|
|
|
} |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
# determine_output_names { |
910
|
|
|
|
|
|
|
# |
911
|
|
|
|
|
|
|
# Determine the output block(s) and/or map(s) that should be used for this |
912
|
|
|
|
|
|
|
# request. If any output names were explicitly specified for the request |
913
|
|
|
|
|
|
|
# path, then use them or throw an error if any are undefined. Otherwise, try |
914
|
|
|
|
|
|
|
# the path with slashes turned into colons and either ':default' or |
915
|
|
|
|
|
|
|
# ':default_map' appended. |
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
sub determine_output_names { |
918
|
|
|
|
|
|
|
|
919
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
920
|
|
|
|
|
|
|
|
921
|
0
|
|
|
|
|
|
my $ds = $self->{ds}; |
922
|
0
|
|
|
|
|
|
my $path = $self->{path}; |
923
|
0
|
0
|
|
|
|
|
my @output_list = @{$self->{attrs}{output}} if ref $self->{attrs}{output} eq 'ARRAY'; |
|
0
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
# If any output names were explicitly given, then check to make sure each |
926
|
|
|
|
|
|
|
# one corresponds to a known block or set. Otherwise, throw an exception. |
927
|
|
|
|
|
|
|
|
928
|
0
|
|
|
|
|
|
foreach my $output_name ( @output_list ) |
929
|
|
|
|
|
|
|
{ |
930
|
|
|
|
|
|
|
croak "the string '$output_name' does not correspond to a defined output block or map" |
931
|
|
|
|
|
|
|
unless ref $ds->{set}{$output_name} eq 'Web::DataService::Set' || |
932
|
0
|
0
|
0
|
|
|
|
ref $ds->{block}{$output_name} eq 'Web::DataService::Block'; |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
# Return the list. |
936
|
|
|
|
|
|
|
|
937
|
0
|
|
|
|
|
|
return @output_list; |
938
|
|
|
|
|
|
|
} |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
# determine_output_format ( outer, inner ) |
942
|
|
|
|
|
|
|
# |
943
|
|
|
|
|
|
|
# This method is called by the error reporting routine if we do not know the |
944
|
|
|
|
|
|
|
# output format. We are given (possibly) both types of objects and need to |
945
|
|
|
|
|
|
|
# determine the appropriate output format based on the data service |
946
|
|
|
|
|
|
|
# configuration and the request path and parameters. |
947
|
|
|
|
|
|
|
# |
948
|
|
|
|
|
|
|
# This method need only return a value if that value is not 'html', because |
949
|
|
|
|
|
|
|
# that is the default. |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
sub determine_output_format { |
952
|
|
|
|
|
|
|
|
953
|
0
|
|
|
0
|
0
|
|
my ($ds, $outer, $inner) = @_; |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
# If the data service has the feature 'format_suffix', then check the |
956
|
|
|
|
|
|
|
# URL path. If no format is specified, we return the empty string. |
957
|
|
|
|
|
|
|
|
958
|
0
|
0
|
|
|
|
|
if ( $ds->{feature}{format_suffix} ) |
959
|
|
|
|
|
|
|
{ |
960
|
0
|
|
|
|
|
|
my $path = $Web::DataService::FOUNDATION->get_request_path($outer); |
961
|
|
|
|
|
|
|
|
962
|
0
|
|
|
|
|
|
$path =~ qr{ [.] ( [^.]+ ) $ }xs; |
963
|
0
|
|
0
|
|
|
|
return $1 || ''; |
964
|
|
|
|
|
|
|
} |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
# Otherwise, if the special parameter 'format' is enabled, check to see if |
967
|
|
|
|
|
|
|
# a value for that parameter was given. |
968
|
|
|
|
|
|
|
|
969
|
0
|
0
|
|
|
|
|
if ( my $format_param = $ds->{special}{format} ) |
970
|
|
|
|
|
|
|
{ |
971
|
|
|
|
|
|
|
# If the parameters have already been validated, check the cleaned |
972
|
|
|
|
|
|
|
# parameter values. |
973
|
|
|
|
|
|
|
|
974
|
0
|
0
|
0
|
|
|
|
if ( ref $inner && reftype $inner eq 'HASH' && $inner->{clean_params} ) |
|
|
|
0
|
|
|
|
|
975
|
|
|
|
|
|
|
{ |
976
|
|
|
|
|
|
|
return $inner->{clean_params}{$format_param} |
977
|
0
|
0
|
|
|
|
|
if $inner->{clean_params}{$format_param}; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
# Otherwise, check the raw parameter values. |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
else |
983
|
|
|
|
|
|
|
{ |
984
|
0
|
|
|
|
|
|
my $params = $Web::DataService::FOUNDATION->get_params($outer); |
985
|
|
|
|
|
|
|
|
986
|
0
|
0
|
|
|
|
|
return lc $params->{$format_param} if $params->{$format_param}; |
987
|
|
|
|
|
|
|
} |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
# If no parameter value was found, see if we have identified a data |
991
|
|
|
|
|
|
|
# service node for this request. If so, check to see if a default format |
992
|
|
|
|
|
|
|
# was established. |
993
|
|
|
|
|
|
|
|
994
|
0
|
0
|
0
|
|
|
|
if ( ref $inner && $inner->isa('Web::DataService::Request') ) |
995
|
|
|
|
|
|
|
{ |
996
|
0
|
|
|
|
|
|
my $default_format = $ds->node_attr($inner, 'default_format'); |
997
|
|
|
|
|
|
|
|
998
|
0
|
0
|
|
|
|
|
return $default_format if $default_format; |
999
|
|
|
|
|
|
|
} |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
# If we really can't tell, then return the empty string which will cause |
1002
|
|
|
|
|
|
|
# the format to default to 'html'. |
1003
|
|
|
|
|
|
|
|
1004
|
0
|
|
|
|
|
|
return ''; |
1005
|
|
|
|
|
|
|
} |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
my %CODE_STRING = ( 400 => "Bad Request", |
1009
|
|
|
|
|
|
|
401 => "Authentication Required", |
1010
|
|
|
|
|
|
|
404 => "Not Found", |
1011
|
|
|
|
|
|
|
415 => "Invalid Media Type", |
1012
|
|
|
|
|
|
|
422 => "Cannot be processed", |
1013
|
|
|
|
|
|
|
500 => "Server Error" ); |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
# error_result ( error, request ) |
1016
|
|
|
|
|
|
|
# |
1017
|
|
|
|
|
|
|
# Send an error response back to the client. This routine is designed to be |
1018
|
|
|
|
|
|
|
# as flexible as possible about its arguments. At minimum, it only needs a |
1019
|
|
|
|
|
|
|
# request object - either the one generated by the foundation framework or |
1020
|
|
|
|
|
|
|
# the one generated by Web::DataService. |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
sub error_result { |
1023
|
|
|
|
|
|
|
|
1024
|
0
|
|
|
0
|
0
|
|
my ($ds, $error, $request) = @_; |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
# If we are in 'debug' mode, then print out the error message. |
1027
|
|
|
|
|
|
|
|
1028
|
0
|
0
|
|
|
|
|
if ( Web::DataService->is_mode('debug') ) |
1029
|
|
|
|
|
|
|
{ |
1030
|
0
|
0
|
|
|
|
|
unless ( defined $error ) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
{ |
1032
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT UNKNOWN ERROR"); |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
|
1035
|
0
|
|
|
|
|
|
elsif ( ! ref $error ) |
1036
|
|
|
|
|
|
|
{ |
1037
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT ERROR: " . $error); |
1038
|
|
|
|
|
|
|
} |
1039
|
|
|
|
|
|
|
|
1040
|
0
|
|
|
|
|
|
elsif ( $error->isa('HTTP::Validate::Result') ) |
1041
|
|
|
|
|
|
|
{ |
1042
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT HTTP::VALIDATE RESULT"); |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
|
1045
|
0
|
|
|
|
|
|
elsif ( $error->isa('Dancer::Exception::Base') ) |
1046
|
|
|
|
|
|
|
{ |
1047
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT ERROR: " . $error->message); |
1048
|
|
|
|
|
|
|
} |
1049
|
|
|
|
|
|
|
|
1050
|
0
|
|
|
|
|
|
elsif ( $error->isa('Web::DataService::Exception') ) |
1051
|
|
|
|
|
|
|
{ |
1052
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT EXCEPTION: " . $error->{message}); |
1053
|
|
|
|
|
|
|
} |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
else |
1056
|
|
|
|
|
|
|
{ |
1057
|
0
|
|
|
|
|
|
Dancer::debug("CAUGHT OTHER ERROR"); |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
} |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
# Then figure out which kind of request object we have. |
1062
|
|
|
|
|
|
|
|
1063
|
0
|
|
|
|
|
|
my ($inner, $outer); |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# If we were given the 'inner' request object, we can retrieve the 'outer' |
1066
|
|
|
|
|
|
|
# one from that. |
1067
|
|
|
|
|
|
|
|
1068
|
0
|
0
|
0
|
|
|
|
if ( ref $request && $request->isa('Web::DataService::Request') ) |
|
|
0
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
{ |
1070
|
0
|
|
|
|
|
|
$inner = $request; |
1071
|
0
|
|
|
|
|
|
$outer = $request->outer; |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
# If we were given the 'outer' object, ask the foundation framework to |
1075
|
|
|
|
|
|
|
# tell us the corresponding 'inner' one. |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
elsif ( defined $request ) |
1078
|
|
|
|
|
|
|
{ |
1079
|
0
|
|
|
|
|
|
$outer = $request; |
1080
|
0
|
|
|
|
|
|
$inner = $Web::DataService::FOUNDATION->retrieve_inner($outer); |
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
# Otherwise, ask the foundation framework to tell us the current request. |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
else |
1086
|
|
|
|
|
|
|
{ |
1087
|
0
|
|
|
|
|
|
$outer = $Web::DataService::FOUNDATION->retrieve_outer(); |
1088
|
0
|
|
|
|
|
|
$inner = $Web::DataService::FOUNDATION->retrieve_inner($outer); |
1089
|
|
|
|
|
|
|
} |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
# Get the proper data service instance from the inner request, in case we |
1092
|
|
|
|
|
|
|
# were called as a class method. |
1093
|
|
|
|
|
|
|
|
1094
|
0
|
0
|
0
|
|
|
|
$ds = defined $inner && $inner->isa('Web::DataService::Request') ? $inner->ds |
1095
|
|
|
|
|
|
|
: $Web::DataService::WDS_INSTANCES[0]; |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
# Next, try to determine the format of the result |
1098
|
|
|
|
|
|
|
|
1099
|
0
|
|
|
|
|
|
my $format; |
1100
|
0
|
0
|
0
|
|
|
|
$format ||= $inner->output_format if $inner; |
1101
|
0
|
|
0
|
|
|
|
$format ||= $ds->determine_output_format($outer, $inner); |
1102
|
|
|
|
|
|
|
|
1103
|
0
|
|
|
|
|
|
my ($code); |
1104
|
0
|
|
|
|
|
|
my (@errors, @warnings, @cautions); |
1105
|
|
|
|
|
|
|
|
1106
|
0
|
0
|
0
|
|
|
|
if ( ref $inner && $inner->isa('Web::DataService::Request') ) |
1107
|
|
|
|
|
|
|
{ |
1108
|
0
|
|
|
|
|
|
@warnings = $inner->warnings; |
1109
|
0
|
|
|
|
|
|
@errors = $inner->errors; |
1110
|
0
|
|
|
|
|
|
@cautions = $inner->cautions; |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
# If the error is actually a response object from HTTP::Validate, then |
1114
|
|
|
|
|
|
|
# extract the error and warning messages. In this case, the error code |
1115
|
|
|
|
|
|
|
# should be "400 bad request". |
1116
|
|
|
|
|
|
|
|
1117
|
0
|
0
|
|
|
|
|
if ( ref $error eq 'HTTP::Validate::Result' ) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
{ |
1119
|
0
|
|
|
|
|
|
push @errors, $error->errors; |
1120
|
0
|
|
|
|
|
|
push @warnings, $error->warnings; |
1121
|
0
|
|
|
|
|
|
$code = "400"; |
1122
|
|
|
|
|
|
|
} |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
elsif ( ref $error eq 'Web::DataService::Exception' ) |
1125
|
|
|
|
|
|
|
{ |
1126
|
0
|
0
|
|
|
|
|
push @errors, $error->{message} if ! @errors; |
1127
|
0
|
|
|
|
|
|
$code = $error->{code}; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
# If the error message begins with a 3-digit number, then that should be |
1131
|
|
|
|
|
|
|
# used as the code and the rest of the message as the error text. |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
elsif ( $error =~ qr{ ^ (\d\d\d) \s+ (.+) }xs ) |
1134
|
|
|
|
|
|
|
{ |
1135
|
0
|
|
|
|
|
|
$code = $1; |
1136
|
0
|
|
|
|
|
|
my $msg = $2; |
1137
|
0
|
|
|
|
|
|
$msg =~ s/\n$//; |
1138
|
0
|
|
|
|
|
|
push @errors, $msg; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
elsif ( $error =~ qr{ ^ (\d\d\d) }xs ) |
1142
|
|
|
|
|
|
|
{ |
1143
|
0
|
|
|
|
|
|
$code = $1; |
1144
|
|
|
|
|
|
|
|
1145
|
0
|
0
|
|
|
|
|
if ( $code eq '404' ) |
|
|
0
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
{ |
1147
|
0
|
|
|
|
|
|
my $path = $Web::DataService::FOUNDATION->get_request_path($outer); |
1148
|
0
|
0
|
0
|
|
|
|
if ( defined $path && $path ne '' ) |
1149
|
|
|
|
|
|
|
{ |
1150
|
0
|
|
|
|
|
|
push @errors, "The path '$path' was not found on this server."; |
1151
|
|
|
|
|
|
|
} |
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
else |
1154
|
|
|
|
|
|
|
{ |
1155
|
0
|
|
|
|
|
|
push @errors, "This request is invalid."; |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
} |
1158
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
elsif ( $CODE_STRING{$code} ) |
1160
|
|
|
|
|
|
|
{ |
1161
|
0
|
|
|
|
|
|
push @errors, $CODE_STRING{$code}; |
1162
|
|
|
|
|
|
|
} |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
else |
1165
|
|
|
|
|
|
|
{ |
1166
|
0
|
0
|
|
|
|
|
push @errors, "Error" unless @errors; |
1167
|
|
|
|
|
|
|
} |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# Otherwise, this is an internal error and all that we should report to |
1171
|
|
|
|
|
|
|
# the user (for security reasons) is that an error occurred. The actual |
1172
|
|
|
|
|
|
|
# message is written to the server error log. |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
else |
1175
|
|
|
|
|
|
|
{ |
1176
|
0
|
|
|
|
|
|
$code = 500; |
1177
|
0
|
|
|
|
|
|
print STDERR warn $error; |
1178
|
0
|
|
|
|
|
|
@errors = "A server error occurred. Please contact the server administrator."; |
1179
|
|
|
|
|
|
|
} |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
# If we know the format and if the corresponding format class knows how to |
1182
|
|
|
|
|
|
|
# generate error messages, then take advantage of that functionality. |
1183
|
|
|
|
|
|
|
|
1184
|
0
|
0
|
|
|
|
|
my $format_class = $ds->{format}{$format}{package} if $format; |
1185
|
|
|
|
|
|
|
|
1186
|
0
|
0
|
0
|
|
|
|
if ( $format_class && $format_class->can('emit_error') ) |
1187
|
|
|
|
|
|
|
{ |
1188
|
0
|
|
|
|
|
|
my $error_body = $format_class->emit_error($code, \@errors, \@warnings, \@cautions); |
1189
|
0
|
|
0
|
|
|
|
my $content_type = $ds->{format}{$format}{content_type} || 'text/plain'; |
1190
|
|
|
|
|
|
|
|
1191
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_content_type($outer, $content_type); |
1192
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($outer, 'Content-Disposition' => 'inline'); |
1193
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_cors_header($outer, "*"); |
1194
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_status($outer, $code); |
1195
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_body($outer, $error_body); |
1196
|
|
|
|
|
|
|
} |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
# Otherwise, generate a generic HTML response (we'll add template |
1199
|
|
|
|
|
|
|
# capability later...) |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
else |
1202
|
|
|
|
|
|
|
{ |
1203
|
0
|
|
0
|
|
|
|
my $text = $CODE_STRING{$code} || 'Error'; |
1204
|
0
|
|
|
|
|
|
my $error = " |
1205
|
0
|
|
|
|
|
|
my $warning = ''; |
1206
|
|
|
|
|
|
|
|
1207
|
0
|
|
|
|
|
|
$error .= "$_\n" foreach @errors; |
1208
|
0
|
|
|
|
|
|
$error .= "\n"; |
1209
|
|
|
|
|
|
|
|
1210
|
0
|
0
|
|
|
|
|
shift @warnings unless $warnings[0]; |
1211
|
|
|
|
|
|
|
|
1212
|
0
|
0
|
|
|
|
|
if ( @warnings ) |
1213
|
|
|
|
|
|
|
{ |
1214
|
0
|
|
|
|
|
|
$warning .= "Warnings:\n |
1215
|
0
|
|
|
|
|
|
$warning .= "$_\n" foreach @warnings; |
1216
|
0
|
|
|
|
|
|
$warning .= "\n"; |
1217
|
|
|
|
|
|
|
} |
1218
|
|
|
|
|
|
|
|
1219
|
0
|
|
|
|
|
|
my $body = <
|
1220
|
|
|
|
|
|
|
$code $text |
1221
|
|
|
|
|
|
|
$code $text |
1222
|
|
|
|
|
|
|
$error |
1223
|
|
|
|
|
|
|
$warning |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
END_BODY |
1226
|
|
|
|
|
|
|
|
1227
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_content_type($outer, 'text/html'); |
1228
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_header($outer, 'Content-Disposition' => 'inline'); |
1229
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_status($outer, $code); |
1230
|
0
|
|
|
|
|
|
$Web::DataService::FOUNDATION->set_body($outer, $body); |
1231
|
|
|
|
|
|
|
} |
1232
|
|
|
|
|
|
|
} |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
1; |