-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathjsonrpc2.class.php
262 lines (200 loc) · 6.82 KB
/
jsonrpc2.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
<?php
/**
*
* This class handles the processing of json-rpc request
* @author Matt Morley (MPCM)
*
*/
class jsonrpc2 {
static function error( $code, $version = '2.0', $id = null ) {
// declare the specification error codes
$errors = array (
-32600 => 'Invalid Request',
-32601 => 'Method not found',
-32602 => 'Invalid params',
-32603 => 'Internal error',
-32700 => 'Parse error'
);
return ( object ) array (
'id'=>$id,
'jsonrpc'=> $version,
'error' => ( object ) array ('code' => $code, 'message'=>$errors[$code] )
);
}
// an array of method routes, set in the constructor
private $method_map = array ();
/**
* create an instance of the jsonrpc2 class, and map in the allowed method strings
* @param array $method_map
*/
public function __construct( array $method_map) {
$this->method_map = $method_map;
}
public function isValidRequestObect( $request ){
// per the 2.0 specification
// a request object must:
// be an object
if( !is_object($request) ) return false;
// contain a jsonrpc member that is a string
if( !isset($request->jsonrpc) || $request->jsonrpc !== '2.0' ) return false;
// contain a method member that is a string
if( !isset($request->method) || !is_string($request->method) ) return false;
// if it contains a params member
// that member must be an array or an object
if( isset($request->params) ){
if(!is_array($request->params) && !is_object($request->params) ){
return false;
}
}
// if it contains an id member
// that member must be a string, number, or null
if( isset( $request->id ) ){
if( !is_string($request->id) && !is_numeric($request->id) && !is_null($request->id) ){
return false;
}
}
// it passes the tests
return true;
}
public function dispatch( $request ) {
try {
// decode the string, if passed as a string
if( is_string($request) ){
$request = json_decode( $request );
}
if ( is_null($request) || is_string( $request ) === TRUE ){
return jsonrpc2::error( -32700 );
}
// if we are passed an array of requests
if (is_array ( $request ) ) {
// make sure it is a numeric array
$is_assoc = array_keys ( $request ) != range ( 0, count ( $request ) - 1 );
if( $is_assoc ){
// this array, but not numeric
return jsonrpc2::error( -32600 );
}
//shuttlfe the request order, to simulate non-sequential processing that *could* occur on other servers
// disabled for ease of output comparison testing
// shuffle ( $request );
//create a holder for all the responses
$return = array ();
//for each request as a request object
foreach ( $request as $request_object ) {
//process each request object
$return[] = $this->dispatch_single( $request_object );
// remove the last request if somehow it is not an object
if( is_object(end($return)) === FALSE){
array_pop($return);
}
}
// if there are no results (all notifications)
if( count($return) == 0){
return null;
}
//return the array of results
return $return;
}
//process the request
return $this->dispatch_single ( $request );
} catch (Exception $e) {
return jsonrpc2::error( -32603 );
}
}
/**
* process a single request object
* @param request object
*/
public function dispatch_single($request) {
// check that the object passes some basic protocal shape tests
if( $this->isValidRequestObect( $request ) === false ){
// invalid request object
return jsonrpc2::error( -32600 );
}
// if the request object does not specify a jsonrpc verison
if (! isset( $request->jsonrpc ) ) {
// assume it is a 1.0 request
$request->jsonrpc = '1.0';
}
// if the request is 2.0 and and no params were sent
// create an empty params entry,
// as 2.0 requests do not need to send an empty array
// later code can now assume that this field will exist
if ($request->jsonrpc == '2.0' && !isset ( $request->params )) {
$request->params = array();
}
// invoke the request object, and store it in the reponse
$response = $this->invoke ( $request );
// if the request id is not set, or if it is null
if (! isset ( $request->id ) || is_null ( $request->id )) {
// return null instead of a response object
return null;
}
// copy the request id into the response object
$response->id = $request->id;
// if it is a 2.0 request
if($request->jsonrpc === '2.0') {
// set the response to 2.0
$response->jsonrpc = $request->jsonrpc;
} else {
// assume it is a 1.0 requrest
// if there is no result in the response
if (! isset ( $response->result )) {
// add one
$response->result = null;
}
// if there is no error in the response
if (! isset ( $response->error )) {
// add one
$response->error = null;
}
}
// return the response object
return $response;
}
// take a more complete request, after processing, and invoke it after checking the parameters align
// extend this function if you need to provide more automatic actions related to methods in classes/instanes
private function invoke( $request ) {
// if the method requested is available
if( isset( $this->method_map[$request->method] ) ) {
try{
// reflect the global function
$reflection = new ReflectionFunction ( $this->method_map[$request->method] );
// check the parameters in the reflection against what was sent in the request
$params = $this->checkParams( $reflection->getParameters(), $request->params );
// return the result as an invoked call
return (object) array ('result' => $reflection->invokeArgs ( $params ) );
} catch ( Exception $e ) {
// if anything abnormal happened, capture the error code thrown
$error = $e->getMessage ();
}
}
// by this point, all we have is errors
return jsonrpc2::error( isset($error) ? $error : -32601 );
}
private function checkParams($real, $sent) {
// was the param list that was provided an object
$is_obj = is_object( $sent );
// if not an object, check if it is an associative array
if(!$is_obj){
// check if the param list is an array
$is_assoc = array_keys ( $sent ) !== range ( 0, count ( $sent ) - 1 );
}
// create the param list we are going to use to invoke the object
$new = array ();
// check every parameter, a
foreach ( $real as $i => $param ) {
$name = $param->getName ();
if ($is_obj && isset( $sent->{$name} )) {
$new[$i] = $sent->{$name};
} elseif ($is_assoc && $sent[$name]) {
$new[$i] = $sent[$name];
} elseif (isset( $sent [$i] )) {
$new[$i] = $sent [$i];
} elseif (! $param->isOptional()) {
throw new Exception( -32602 );
}
}
// return the list of matching params
return $new;
}
}