Lines 23-51
C4::Service - functions for JSON webservices.
Link Here
|
23 |
|
23 |
|
24 |
=head1 SYNOPSIS |
24 |
=head1 SYNOPSIS |
25 |
|
25 |
|
26 |
my ( $query, $response) = C4::Service->init( { circulate => 1 } ); |
26 |
my $response = C4::Output::XMLStream->new(...); |
27 |
my ( $borrowernumber) = C4::Service->require_params( 'borrowernumber' ); |
27 |
my $service = C4::Service->new( { needed_flags => { circulate => 1 }, |
|
|
28 |
[ output_stream => $response ], |
29 |
[ query => CGI->new() ] } ); |
30 |
my ( $borrowernumber) = $service->require_params( 'borrowernumber' ); |
28 |
|
31 |
|
29 |
C4::Service->return_error( 'internal', 'Frobnication failed', frobnicator => 'foo' ); |
32 |
$service->return_error( 'internal', 'Frobnication failed', frobnicator => 'foo' ); |
30 |
|
33 |
|
31 |
$response->param( frobnicated => 'You' ); |
34 |
$response->param( frobnicated => 'You' ); |
32 |
|
35 |
|
33 |
C4::Service->return_success( $response ); |
36 |
C4::Service->return_success(); |
34 |
|
37 |
|
35 |
=head1 DESCRIPTION |
38 |
=head1 DESCRIPTION |
36 |
|
39 |
|
37 |
This module packages several useful functions for JSON webservices. |
40 |
This module packages several useful functions for webservices. |
38 |
|
41 |
|
39 |
=cut |
42 |
=cut |
40 |
|
43 |
|
41 |
use strict; |
44 |
use strict; |
42 |
use warnings; |
45 |
use warnings; |
43 |
|
46 |
|
|
|
47 |
use Carp; |
44 |
use CGI qw ( -utf8 ); |
48 |
use CGI qw ( -utf8 ); |
45 |
use C4::Auth qw( check_api_auth ); |
49 |
use C4::Auth qw( check_api_auth ); |
46 |
use C4::Output qw( :ajax ); |
50 |
use C4::Output qw( :ajax ); |
47 |
use C4::Output::JSONStream; |
51 |
use C4::Output::JSONStream; |
48 |
use JSON; |
|
|
49 |
|
52 |
|
50 |
our $debug; |
53 |
our $debug; |
51 |
|
54 |
|
Lines 53-94
BEGIN {
Link Here
|
53 |
$debug = $ENV{DEBUG} || 0; |
56 |
$debug = $ENV{DEBUG} || 0; |
54 |
} |
57 |
} |
55 |
|
58 |
|
56 |
our ( $query, $cookie ); |
|
|
57 |
|
58 |
=head1 METHODS |
59 |
=head1 METHODS |
59 |
|
60 |
|
60 |
=head2 init |
61 |
=head2 new |
61 |
|
62 |
|
62 |
our ( $query, $response ) = C4::Service->init( %needed_flags ); |
63 |
my $service = C4::Service->new({needed_flags => { parameters => 1 }, |
|
|
64 |
[ output_stream => C4::Output::XMLStream->new(...) ], |
65 |
[ query => CGI->new() ]}); |
63 |
|
66 |
|
64 |
Initialize the service and check for the permissions in C<%needed_flags>. |
67 |
Creates a new instance of C4::Service. It verifies that the provided flags |
|
|
68 |
are met by the current session, and aborts with an exit() call if they're |
69 |
not. It also accepts an instance of C4::Output::* (or something with the |
70 |
same interface) to use to generate the output. If none is provided, then |
71 |
a new instance of L<C4::Output::JSONStream> is created. Similarly, a query |
72 |
may also be provided. If it's not, a new CGI one will be created. |
65 |
|
73 |
|
66 |
Also, check that the user is authorized and has a current session, and return an |
74 |
This call can't be used to log a user in by providing a userid parameter, it |
67 |
'auth' error if not. |
75 |
can only be used to check an already existing session. |
68 |
|
76 |
|
69 |
init() returns a C<CGI> object and a C<C4::Output::JSONStream>. The latter can |
77 |
TODO: exit sucks, make a better way. |
70 |
be used for both flat scripts and those that use dispatch(), and should be |
|
|
71 |
passed to C<return_success()>. |
72 |
|
78 |
|
73 |
=cut |
79 |
=cut |
74 |
|
80 |
|
75 |
sub init { |
81 |
sub new { |
76 |
my ( $class, %needed_flags ) = @_; |
82 |
my $class = shift; |
|
|
83 |
|
84 |
my %opts = %{shift()}; |
77 |
|
85 |
|
78 |
our $query = new CGI; |
86 |
my $needed_flags = $opts{needed_flags}; |
|
|
87 |
croak "needed_flags is a required option" unless $needed_flags; |
79 |
|
88 |
|
80 |
my ( $status, $cookie_, $sessionID ) = check_api_auth( $query, \%needed_flags ); |
89 |
my $query = $opts{query} || CGI->new(); |
|
|
90 |
# We capture the userid so it doesn't upset the auth check process |
91 |
# (if we don't, the auth code will try to log in with the userid |
92 |
# param value.) |
93 |
my $userid; |
94 |
$userid = $query->param('userid'); |
95 |
$query->delete('userid') if defined($userid); |
81 |
|
96 |
|
82 |
our $cookie = $cookie_; # I have no desire to offend the Perl scoping gods |
97 |
my ( $status, $cookie, $sessionID ) = check_api_auth( $query, $needed_flags ); |
83 |
|
98 |
|
84 |
$class->return_error( 'auth', $status ) if ( $status ne 'ok' ); |
99 |
# Restore userid if needed |
|
|
100 |
$query->param(-name=>'userid', -value=>$userid) if defined($userid); |
85 |
|
101 |
|
86 |
return ( $query, new C4::Output::JSONStream ); |
102 |
my $output_stream = $opts{output_stream} || C4::Output::JSONStream->new(); |
|
|
103 |
my $self = { |
104 |
needed_flags => $needed_flags, |
105 |
query => $query, |
106 |
output_stream => $output_stream, |
107 |
cookie => $cookie, |
108 |
}; |
109 |
bless $self, $class; |
110 |
$self->return_error('auth', $status) if ($status ne 'ok'); |
111 |
|
112 |
return $self; |
87 |
} |
113 |
} |
88 |
|
114 |
|
89 |
=head2 return_error |
115 |
=head2 return_error |
90 |
|
116 |
|
91 |
C4::Service->return_error( $type, $error, %flags ); |
117 |
$service->return_error( $type, $error, %flags ); |
92 |
|
118 |
|
93 |
Exit the script with HTTP status 400, and return a JSON error object. |
119 |
Exit the script with HTTP status 400, and return a JSON error object. |
94 |
|
120 |
|
Lines 106-125
param => value pairs.
Link Here
|
106 |
=cut |
132 |
=cut |
107 |
|
133 |
|
108 |
sub return_error { |
134 |
sub return_error { |
109 |
my ( $class, $type, $error, %flags ) = @_; |
135 |
my ( $self, $type, $error, %flags ) = @_; |
110 |
|
136 |
|
111 |
my $response = new C4::Output::JSONStream; |
137 |
my $response = $self->{output_stream}; |
|
|
138 |
$response->clear(); |
112 |
|
139 |
|
113 |
$response->param( message => $error ) if ( $error ); |
140 |
$response->param( message => $error ) if ( $error ); |
114 |
$response->param( type => $type, %flags ); |
141 |
$response->param( type => $type, %flags ); |
115 |
|
142 |
|
116 |
output_with_http_headers $query, $cookie, $response->output, 'json', '400 Bad Request'; |
143 |
output_with_http_headers $self->{query}, $self->{cookie}, $response->output, $response->content_type, '400 Bad Request'; |
|
|
144 |
|
145 |
# Someone please delete this |
117 |
exit; |
146 |
exit; |
118 |
} |
147 |
} |
119 |
|
148 |
|
120 |
=head2 return_multi |
149 |
=head2 return_multi |
121 |
|
150 |
|
122 |
C4::Service->return_multi( \@responses, %flags ); |
151 |
$service->return_multi( \@responses, %flags ); |
123 |
|
152 |
|
124 |
return_multi is similar to return_success or return_error, but allows you to |
153 |
return_multi is similar to return_success or return_error, but allows you to |
125 |
return different statuses for several requests sent at once (using HTTP status |
154 |
return different statuses for several requests sent at once (using HTTP status |
Lines 139-150
structure verbatim.
Link Here
|
139 |
=cut |
168 |
=cut |
140 |
|
169 |
|
141 |
sub return_multi { |
170 |
sub return_multi { |
142 |
my ( $class, $responses, @flags ) = @_; |
171 |
my ( $self, $responses, @flags ) = @_; |
143 |
|
172 |
|
144 |
my $response = new C4::Output::JSONStream; |
173 |
my $response = $self->{output_stream}; |
|
|
174 |
$response->clear(); |
145 |
|
175 |
|
146 |
if ( !@$responses ) { |
176 |
if ( !@$responses ) { |
147 |
$class->return_success( $response ); |
177 |
$self->return_success( $response ); |
148 |
} else { |
178 |
} else { |
149 |
my @responses_formatted; |
179 |
my @responses_formatted; |
150 |
|
180 |
|
Lines 152-165
sub return_multi {
Link Here
|
152 |
if ( ref( $response ) eq 'ARRAY' ) { |
182 |
if ( ref( $response ) eq 'ARRAY' ) { |
153 |
my ($type, $error, @error_flags) = @$response; |
183 |
my ($type, $error, @error_flags) = @$response; |
154 |
|
184 |
|
155 |
push @responses_formatted, { is_error => JSON::true, type => $type, message => $error, @error_flags }; |
185 |
push @responses_formatted, { is_error => $response->true(), type => $type, message => $error, @error_flags }; |
156 |
} else { |
186 |
} else { |
157 |
push @responses_formatted, $response; |
187 |
push @responses_formatted, $response; |
158 |
} |
188 |
} |
159 |
} |
189 |
} |
160 |
|
190 |
|
161 |
$response->param( 'multi' => JSON::true, responses => \@responses_formatted, @flags ); |
191 |
$response->param( 'multi' => $response->true(), responses => \@responses_formatted, @flags ); |
162 |
output_with_http_headers $query, $cookie, $response->output, 'json', '207 Multi-Status'; |
192 |
output_with_http_headers $self->{query}, $self->{cookie}, $response->output, $response->content_type, '207 Multi-Status'; |
163 |
} |
193 |
} |
164 |
|
194 |
|
165 |
exit; |
195 |
exit; |
Lines 167-188
sub return_multi {
Link Here
|
167 |
|
197 |
|
168 |
=head2 return_success |
198 |
=head2 return_success |
169 |
|
199 |
|
170 |
C4::Service->return_success( $response ); |
200 |
$service->return_success(); |
171 |
|
201 |
|
172 |
Print out the information in the C<C4::Output::JSONStream> C<$response>, then |
202 |
Print out the information in the provided C<output_stream>, then |
173 |
exit with HTTP status 200. |
203 |
exit with HTTP status 200. To get access to the C<output_stream>, you should |
|
|
204 |
either use the one that you provided, or you should use the C<output_stream()> |
205 |
accessor. |
174 |
|
206 |
|
175 |
=cut |
207 |
=cut |
176 |
|
208 |
|
177 |
sub return_success { |
209 |
sub return_success { |
178 |
my ( $class, $response ) = @_; |
210 |
my ( $self ) = @_; |
179 |
|
211 |
|
180 |
output_with_http_headers $query, $cookie, $response->output, 'json'; |
212 |
my $response = $self->{output_stream}; |
|
|
213 |
output_with_http_headers $self->{query}, $self->{cookie}, $response->output, $response->content_type; |
214 |
} |
215 |
|
216 |
=head2 output_stream |
217 |
|
218 |
$service->output_stream(); |
219 |
|
220 |
Provides the output stream object that is in use so that data can be added |
221 |
to it. |
222 |
|
223 |
=cut |
224 |
|
225 |
sub output_stream { |
226 |
my $self = shift; |
227 |
|
228 |
return $self->{output_stream}; |
229 |
} |
230 |
|
231 |
=head2 query |
232 |
|
233 |
$service->query(); |
234 |
|
235 |
Provides the query object that this class is using. |
236 |
|
237 |
=cut |
238 |
|
239 |
sub query { |
240 |
my $self = shift; |
241 |
|
242 |
return $self->{query}; |
181 |
} |
243 |
} |
182 |
|
244 |
|
183 |
=head2 require_params |
245 |
=head2 require_params |
184 |
|
246 |
|
185 |
my @values = C4::Service->require_params( @params ); |
247 |
my @values = $service->require_params( @params ); |
186 |
|
248 |
|
187 |
Check that each of of the parameters specified in @params was sent in the |
249 |
Check that each of of the parameters specified in @params was sent in the |
188 |
request, then return their values in that order. |
250 |
request, then return their values in that order. |
Lines 192-204
If a required parameter is not found, send a 'param' error to the browser.
Link Here
|
192 |
=cut |
254 |
=cut |
193 |
|
255 |
|
194 |
sub require_params { |
256 |
sub require_params { |
195 |
my ( $class, @params ) = @_; |
257 |
my ( $self, @params ) = @_; |
196 |
|
258 |
|
197 |
my @values; |
259 |
my @values; |
198 |
|
260 |
|
199 |
for my $param ( @params ) { |
261 |
for my $param ( @params ) { |
200 |
$class->return_error( 'params', "Missing '$param'" ) if ( !defined( $query->param( $param ) ) ); |
262 |
$self->return_error( 'params', "Missing '$param'" ) if ( !defined( $self->{query}->param( $param ) ) ); |
201 |
push @values, $query->param( $param ); |
263 |
push @values, $self->{query}->param( $param ); |
202 |
} |
264 |
} |
203 |
|
265 |
|
204 |
return @values; |
266 |
return @values; |
Lines 206-212
sub require_params {
Link Here
|
206 |
|
268 |
|
207 |
=head2 dispatch |
269 |
=head2 dispatch |
208 |
|
270 |
|
209 |
C4::Service->dispatch( |
271 |
$service->dispatch( |
210 |
[ $path_regex, \@required_params, \&handler ], |
272 |
[ $path_regex, \@required_params, \&handler ], |
211 |
... |
273 |
... |
212 |
); |
274 |
); |
Lines 233-240
with the argument '123'.
Link Here
|
233 |
=cut |
295 |
=cut |
234 |
|
296 |
|
235 |
sub dispatch { |
297 |
sub dispatch { |
236 |
my $class = shift; |
298 |
my $self = shift; |
237 |
|
299 |
|
|
|
300 |
my $query = $self->{query}; |
238 |
my $path_info = $query->path_info || '/'; |
301 |
my $path_info = $query->path_info || '/'; |
239 |
|
302 |
|
240 |
ROUTE: foreach my $route ( @_ ) { |
303 |
ROUTE: foreach my $route ( @_ ) { |
Lines 251-257
sub dispatch {
Link Here
|
251 |
return; |
314 |
return; |
252 |
} |
315 |
} |
253 |
|
316 |
|
254 |
$class->return_error( 'no_handler', '' ); |
317 |
$self->return_error( 'no_handler', '' ); |
255 |
} |
318 |
} |
256 |
|
319 |
|
257 |
1; |
320 |
1; |
Lines 263-265
__END__
Link Here
|
263 |
Koha Development Team |
326 |
Koha Development Team |
264 |
|
327 |
|
265 |
Jesse Weaver <jesse.weaver@liblime.com> |
328 |
Jesse Weaver <jesse.weaver@liblime.com> |
|
|
329 |
|
330 |
Robin Sheat <robin@catalyst.net.nz> |