line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package JRPC::CGI; |
2
|
|
|
|
|
|
|
# Leave this up to the implementor ? |
3
|
|
|
|
|
|
|
#use CGI; |
4
|
|
|
|
|
|
|
#use CGI::Carp qw/fatalsToBrowser warningsToBrowser/; |
5
|
1
|
|
|
1
|
|
879
|
use JSON::XS; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
45
|
|
6
|
1
|
|
|
1
|
|
4
|
use JRPC; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
15
|
|
7
|
1
|
|
|
1
|
|
2
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
21
|
|
8
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
21
|
|
9
|
1
|
|
|
1
|
|
3
|
use Scalar::Util ('reftype'); # Check base types |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
561
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
JRPC::CGI - JSON-RPC 2.0 Processing for CGI and HTTP::Server::Simple::CGI |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 DESCRIPTION |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
This package provides JSON-RPC 2.0 services processor for 2 runtimes based on: |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=over 4 |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=item * CGI (CGI.pm) Plain old CGI scripting (or mod_perl ModPerl::Registry mode) |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=item * HTTP::Server::Simple::CGI - a fast and lightweight runtime with a Perl embedded httpd (web server) module. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=back |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
HTTP::Server::Simple::CGI is especially interesting for doing distributed computation over the http. |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 METHODS |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Because of the rudimentary nature of CGI (in both good and bad), the JRPC::CGI::handle_cgi($cgi) is to be called explicitly in code |
32
|
|
|
|
|
|
|
(as CGI is not hosted by sophisticated server). |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
The service method JRPC::CGI::handle_simple_server_cgi($server, $cgi); for HTTP::Server::Simple::CGI can be aliased to local package's handle_request |
35
|
|
|
|
|
|
|
method, which is the request handling method for HTTP::Server::Simple framework (similar to mod_perl's and Nginx's handler($r) method). |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=cut |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
our $mimetype = 'text/plain'; |
40
|
|
|
|
|
|
|
# Plug for uri-method transparency |
41
|
|
|
|
|
|
|
# Do NOT use CGI::url method for uri purpose ! |
42
|
|
|
|
|
|
|
# Keep this anywhere that may use CGI request object |
43
|
0
|
|
|
0
|
0
|
|
sub CGI::uri {return $_[0]->script_name();} |
44
|
|
|
|
|
|
|
# JSON RPC Response ID for malformed requests. |
45
|
|
|
|
|
|
|
our $naid = 666666666; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head2 JRPC::CGI::handle_cgi($cgi) |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Traditional CGI Handler for JRPC. Example CGI wrapper: |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
#!/usr/bin/perl |
52
|
|
|
|
|
|
|
use CGI; |
53
|
|
|
|
|
|
|
use CGI::Carp qw/fatalsToBrowser warningsToBrowser/; |
54
|
|
|
|
|
|
|
use JRPC::CGI; |
55
|
|
|
|
|
|
|
use SvcTest; # Load Service package |
56
|
|
|
|
|
|
|
my $cgi = CGI->new(); |
57
|
|
|
|
|
|
|
# Process request. Reports all errors to Client as a JSON-RPC error (fault) response. |
58
|
|
|
|
|
|
|
JRPC::CGI::handle_cgi($cgi); |
59
|
|
|
|
|
|
|
exit(0); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# This "Service Package" could (and should) be in a separate file (SvcTest.pm). |
62
|
|
|
|
|
|
|
# It will be called back by JRPC. |
63
|
|
|
|
|
|
|
package SvcTest; |
64
|
|
|
|
|
|
|
use Scalar::Util ('reftype'); |
65
|
|
|
|
|
|
|
# Simpliest possible service: |
66
|
|
|
|
|
|
|
# - reflect/echo 'params' (of request) to 'result' (of response) |
67
|
|
|
|
|
|
|
# - Framework will take care of request parsing and response serialization |
68
|
|
|
|
|
|
|
# - On validation errors, Framework will turn a Perl exception to a JSON-RPC fault. |
69
|
|
|
|
|
|
|
# Call this by: ..., "method": "Test.echo", ... |
70
|
|
|
|
|
|
|
sub echo { |
71
|
|
|
|
|
|
|
my ($p, $jrpc) = @_; |
72
|
|
|
|
|
|
|
# Validate, require $p to be HASH (ref). |
73
|
|
|
|
|
|
|
# Framework will convert exceptions to JSON-RPC Fault |
74
|
|
|
|
|
|
|
if (reftype($p) ne 'HASH') {die("param was not found to be a JSON Object");} |
75
|
|
|
|
|
|
|
return($p); |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
1; |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=cut |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Could do Storable::dclone($p) to be on paranoid side |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub handle_cgi { |
85
|
0
|
|
|
0
|
1
|
|
my ($cgi) = @_; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Early mime output |
88
|
|
|
|
|
|
|
# TODO: Also Include length ...must be later |
89
|
|
|
|
|
|
|
# DEBUG: print("Extra: Math-$Math::VERSION\r\n"); |
90
|
0
|
|
|
|
|
|
print("Content-type: $mimetype\r\n"); # .termheaders() |
91
|
0
|
|
|
|
|
|
my $jresp = {'id' => $naid, 'jsonrpc' => '2.0', }; # Set up dummy |
92
|
0
|
|
|
|
|
|
my $buffer = $cgi->param('POSTDATA'); # POST Body |
93
|
0
|
|
|
|
|
|
my $j; |
94
|
|
|
|
|
|
|
# EVAL ... |
95
|
0
|
|
|
|
|
|
eval { |
96
|
0
|
0
|
|
|
|
|
if (!$buffer) {die("JSON-RPC Request body is Empty (-32700)");} |
|
0
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
#my $req = eval { JSON::XS::decode_json($jstext); }; |
98
|
0
|
|
|
|
|
|
$j = eval { JRPC::parse($buffer); }; |
|
0
|
|
|
|
|
|
|
99
|
0
|
0
|
|
|
|
|
if ($@) {die("Error Parsing Request: $@");} |
|
0
|
|
|
|
|
|
|
100
|
0
|
0
|
0
|
|
|
|
if (defined($JRPC::prelogger) && (ref($JRPC::prelogger) eq 'CODE')) {$JRPC::prelogger->($j);} |
|
0
|
|
|
|
|
|
|
101
|
0
|
|
|
|
|
|
my $p = $j->{'params'}; |
102
|
0
|
|
|
|
|
|
my $m = $j->{'method'}; |
103
|
0
|
|
|
|
|
|
$jresp->{'id'} = $j->{'id'}; |
104
|
0
|
|
|
|
|
|
my $f; # Below: Support both plain-method and dot-notation dispatching. |
105
|
0
|
|
|
|
|
|
my $mid = 0; |
106
|
|
|
|
|
|
|
# TODO: index($m, '.') > 0 # Faster than regex ? |
107
|
0
|
0
|
|
|
|
|
if ($m =~ /\./) {$f = JRPC::methresolve_dotnot($cgi, $m);$mid=1;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
else {$f = JRPC::methresolve($cgi, $m);} # |
109
|
0
|
0
|
|
|
|
|
if (!$f) {die("method '$m' not resolved (-32601) mid=$mid");} |
|
0
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
##### reqinit |
111
|
|
|
|
|
|
|
#if (my $f = $pkg->can('reqinit')) {$f->($p, $j);} |
112
|
|
|
|
|
|
|
# Execute |
113
|
0
|
|
|
|
|
|
my $res = eval { $f->($p); }; # Dispatch (catching any exceptions) |
|
0
|
|
|
|
|
|
|
114
|
0
|
0
|
|
|
|
|
if ($@) {die("Error in processing JSON-RPC method '$m' (-32603): $@");} |
|
0
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Definite Success - serialize response ? |
116
|
0
|
|
|
|
|
|
$jresp->{'result'} = $res; |
117
|
|
|
|
|
|
|
# Output |
118
|
0
|
|
|
|
|
|
my $out = eval { encode_json($jresp); }; # Serialize as a separate step to know length |
|
0
|
|
|
|
|
|
|
119
|
0
|
0
|
|
|
|
|
if ($@) {die("Error Forming the JSON-RPC result response: $@");} |
|
0
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# $hdrs_out->{'content-length'} = length($out); # TODO: |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# Late headers ? |
123
|
0
|
|
|
|
|
|
print(termheaders(length($out)).$out); |
124
|
|
|
|
|
|
|
}; # End processing eval |
125
|
|
|
|
|
|
|
# Formulate a fault |
126
|
|
|
|
|
|
|
# Problem: any output here gets duplicated (literal or function generated). |
127
|
|
|
|
|
|
|
# Info: Service package was missing use strict; use warnings;. Was suing wrong var for forked child PID |
128
|
|
|
|
|
|
|
# $pid instead of $cpid, so was getting wrong info for fork() success. fork() process duplication |
129
|
|
|
|
|
|
|
# seemed to cause output duplication as STDIN,STDOUT were not yet successfully closed. |
130
|
|
|
|
|
|
|
# handle async processing by fork() with care ! |
131
|
0
|
0
|
|
|
|
|
if ($@) { |
132
|
0
|
|
|
|
|
|
my $fault = JRPC::createfault($j, $@, 500); |
133
|
|
|
|
|
|
|
#DEBUG:open(my $fh, ">>", "/tmp/jrpc.$$.out"); |
134
|
|
|
|
|
|
|
#DEBUG:print($fh "\n=====\n$fault\n=====\n"); |
135
|
|
|
|
|
|
|
#DEBUG:close($fh); |
136
|
0
|
|
|
|
|
|
print(termheaders(length($fault)).$fault); |
137
|
|
|
|
|
|
|
#TEST:print("{}"); |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
#return(0); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# Helper sub to terminate HTTP headers with content length passed/ |
143
|
|
|
|
|
|
|
sub termheaders { |
144
|
0
|
0
|
|
0
|
0
|
|
if ($_[0]) {return("Content-length: $_[0]\r\n\r\n");} |
|
0
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
return "\r\n"; |
146
|
|
|
|
|
|
|
#""; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
# TODO: Overload for both signatures: |
149
|
|
|
|
|
|
|
# - ($cgi) |
150
|
|
|
|
|
|
|
# - (HTTP::Server::Simple::CGI, $CGI) |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head2 JRPC::CGI::handle_simple_server_cgi($server, $cgi); |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Wrapper for intercepting a request to HTTP::Server::Simple::CGI. |
155
|
|
|
|
|
|
|
Alias this as a handle_request() in your package implementing |
156
|
|
|
|
|
|
|
HTTP::Server::Simple::CGI. Example: |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
#!/usr/bin/perl |
159
|
|
|
|
|
|
|
{ |
160
|
|
|
|
|
|
|
package MyJRPC; |
161
|
|
|
|
|
|
|
use HTTP::Server::Simple::CGI; |
162
|
|
|
|
|
|
|
use base 'HTTP::Server::Simple::CGI'; |
163
|
|
|
|
|
|
|
# Reuse handle_simple_server_cgi, assign as local alias. |
164
|
|
|
|
|
|
|
*handle_request = \&JRPC::CGI::handle_simple_server_cgi; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
my $port = $ENV{'HTTP_SIMPLE_PORT'} || 8080; |
167
|
|
|
|
|
|
|
my $pid = MyWebServer->new($port); |
168
|
|
|
|
|
|
|
#my $pid = MyWebServer->new($port)->background(); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
print "Use 'kill $pid' to stop server (on port $port).\n"; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head1 RUNNING SERVER IN THREAD |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
To be able to run server in thread and to be able to terminate the thread, use the following idiom: |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Server thread as anonymous sub. Pass port to run at. |
177
|
|
|
|
|
|
|
my $runmyserver = sub { |
178
|
|
|
|
|
|
|
my ($port) = @_; |
179
|
|
|
|
|
|
|
# Use signaling to kill thread |
180
|
|
|
|
|
|
|
$SIG{'KILL'} = sub { threads->exit(); }; |
181
|
|
|
|
|
|
|
# Run in the same process, NOT spawning a sub process. |
182
|
|
|
|
|
|
|
MyServer->new($port)->run(); |
183
|
|
|
|
|
|
|
}; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
my $thr = threads->create($runmyserver, $port); |
186
|
|
|
|
|
|
|
# ... |
187
|
|
|
|
|
|
|
# Much later ... terminate server as no more needed. |
188
|
|
|
|
|
|
|
$thr->kill('KILL')->detach(); |
189
|
|
|
|
|
|
|
# This main thread should continue / survive beyond this point ... |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head1 HINTS |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
JSON-RPC is not a domain for obsessed print(); debugging folks. Printing to STDOUT messes up the JSON-RPC response output. |
194
|
|
|
|
|
|
|
The returned data structure gets automatically converted to a successful JSON-RPC Response (data goes into 'result' member). |
195
|
|
|
|
|
|
|
Any fatal errors thrown as Perl exceptions get automatically converted to a valid JSON-RPC exception / fault |
196
|
|
|
|
|
|
|
(member 'error', and optionally to logs). |
197
|
|
|
|
|
|
|
Any diagnostic messaging goes to response or logs (or both), NOT STDOUT. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head1 TODO |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=over 4 |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=item * Private package (file) for ServerSimple (with direct default handler handle_request())? |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=item * In private package use HTTP::Server::Simple::CGI (and inherit from it) |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=back |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=cut |
210
|
|
|
|
|
|
|
#use JRPC::CGI; # To have the uri() method |
211
|
|
|
|
|
|
|
# NOTE REQUEST_URI (or PATH_INFO) contains |
212
|
|
|
|
|
|
|
our $haveuri = 0; |
213
|
|
|
|
|
|
|
# For testing purposes ONLY. |
214
|
|
|
|
|
|
|
# Note: These should reside in context of serv. pkg. or $server (see below). |
215
|
|
|
|
|
|
|
# Need a nice accessor for this: Pkg->dieaftercnt(3) (Inherit) |
216
|
|
|
|
|
|
|
our $dieaftercnt = 0; |
217
|
|
|
|
|
|
|
our $reqcnt = 0; |
218
|
|
|
|
|
|
|
# sub CGI::uri {return $ENV{'REQUEST_URI'};} |
219
|
|
|
|
|
|
|
sub handle_simple_server_cgi { |
220
|
0
|
|
|
0
|
1
|
|
my ($server, $cgi) = @_; |
221
|
0
|
0
|
|
|
|
|
if (!$haveuri) { |
222
|
|
|
|
|
|
|
#no strict ('subs'); |
223
|
0
|
|
|
|
|
|
eval("sub CGI::uri {return \$ENV{'REQUEST_URI'};}"); |
224
|
0
|
|
|
|
|
|
$haveuri++; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
0
|
0
|
|
|
|
|
if ($cgi->request_method() ne 'POST') { |
228
|
0
|
|
|
|
|
|
print("HTTP/1.0 500 Must Send a POST\r\nContent-type: text/plain\r\n\r\nNeed to POST-the-JSON");return; |
|
0
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
# Too early to say ? It's okay, the message (result/error) will tell the outcome. |
231
|
|
|
|
|
|
|
# We trust in server catching every exception and turning it into error. |
232
|
0
|
|
|
|
|
|
print("HTTP/1.0 200 OK\r\n"); |
233
|
|
|
|
|
|
|
# Use Standard handle_cgi() for the rest |
234
|
0
|
|
|
|
|
|
handle_cgi($cgi); |
235
|
|
|
|
|
|
|
# TODO: Move this to be package specific |
236
|
|
|
|
|
|
|
#$reqcnt++; |
237
|
|
|
|
|
|
|
#DEBUG:print(STDERR "CNT: $reqcnt, vs. $dieaftercnt\n"); |
238
|
|
|
|
|
|
|
#threads->exit(); # This works |
239
|
|
|
|
|
|
|
#print("PASSED\n"); |
240
|
|
|
|
|
|
|
#if ($dieaftercnt && ($reqcnt >= $dieaftercnt)) { |
241
|
|
|
|
|
|
|
# #sleep(3); |
242
|
|
|
|
|
|
|
# my $thr; |
243
|
|
|
|
|
|
|
# my $can = threads->can('exit'); |
244
|
|
|
|
|
|
|
# DEBUG:print(STDERR "Count full, ready to term (threads: $threads::VERSION) $can\n"); |
245
|
|
|
|
|
|
|
# # TODO: Initial Problem - thread does not exit like wanted. It _does_ exit, but join() does not happen!!! |
246
|
|
|
|
|
|
|
# $thr = threads->self(); |
247
|
|
|
|
|
|
|
# #$thr->exit(); |
248
|
|
|
|
|
|
|
# threads->exit(); |
249
|
|
|
|
|
|
|
# print(STDERR "Passed threads->exit() thr=$thr\n"); # |
250
|
|
|
|
|
|
|
#} |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
1; |