1
|
|
|
<?php namespace Comodojo\RpcServer\Request; |
2
|
|
|
|
3
|
|
|
use \Comodojo\RpcServer\Request\Parameters; |
4
|
|
|
use \Comodojo\RpcServer\Util\DataValidator; |
5
|
|
|
use \Comodojo\Exception\RpcException; |
6
|
|
|
use \Exception; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* The JSONRPC processor |
10
|
|
|
* |
11
|
|
|
* @package Comodojo Spare Parts |
12
|
|
|
* @author Marco Giovinazzi <[email protected]> |
13
|
|
|
* @license MIT |
14
|
|
|
* |
15
|
|
|
* LICENSE: |
16
|
|
|
* |
17
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23
|
|
|
* THE SOFTWARE. |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
class JsonProcessor { |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* A parameters object |
30
|
|
|
* |
31
|
|
|
* @var \Comodojo\RpcServer\Request\Parameters |
32
|
|
|
*/ |
33
|
|
|
private $parameters = null; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Array of requests |
37
|
|
|
* |
38
|
|
|
* @var array |
39
|
|
|
*/ |
40
|
|
|
private $requests = array(); |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Array of results |
44
|
|
|
* |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
private $results = array(); |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Internal flag to identify a batch request |
51
|
|
|
* |
52
|
|
|
* @var bool |
53
|
|
|
*/ |
54
|
|
|
private $is_batch_request = false; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Current logger |
58
|
|
|
* |
59
|
|
|
* @var \Psr\Log\LoggerInterface |
60
|
|
|
*/ |
61
|
|
|
private $logger = null; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Class constructor |
65
|
|
|
* |
66
|
|
|
* @param array|object $payload |
67
|
|
|
* @param \Comodojo\RpcServer\Request\Parameters $parameters |
68
|
|
|
* @param \Psr\Log\LoggerInterface $logger |
69
|
|
|
*/ |
70
|
42 |
|
public function __construct($payload, Parameters $parameters, \Psr\Log\LoggerInterface $logger) { |
71
|
|
|
|
72
|
42 |
|
$this->logger = $logger; |
73
|
|
|
|
74
|
42 |
|
$this->logger->notice("Starting JSON processor"); |
75
|
|
|
|
76
|
42 |
|
$this->parameters = $parameters; |
77
|
|
|
|
78
|
42 |
|
list($this->is_batch_request, $this->requests) = self::preprocessJsonPayload($payload); |
79
|
|
|
|
80
|
42 |
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Run the processor and exec callback(s) |
84
|
|
|
* |
85
|
|
|
* @return mixed |
86
|
|
|
* @throws Exception |
87
|
|
|
*/ |
88
|
42 |
|
public function run() { |
89
|
|
|
|
90
|
42 |
|
foreach ( $this->requests as $request ) { |
91
|
|
|
|
92
|
42 |
|
if ( isset($request['ERROR_CODE']) && isset($request['ERROR_MESSAGE']) ) { |
93
|
|
|
|
94
|
|
|
$this->logger->warning("Invalid request ".$request['ID']); |
95
|
|
|
|
96
|
|
|
$result = self::packJsonError($request['ERROR_CODE'], $request['ERROR_MESSAGE'], $request['ID']); |
97
|
|
|
|
98
|
|
|
if ( !is_null($result) ) $this->results[] = $result; |
99
|
|
|
|
100
|
|
|
} else { |
101
|
|
|
|
102
|
|
|
try { |
103
|
|
|
|
104
|
42 |
|
$this->logger->notice("Serving request ".$request['METHOD']."(".$request['ID'].")"); |
105
|
|
|
|
106
|
42 |
|
$response = $this->runSingleRequest($request['METHOD'], $request['PARAMETERS']); |
107
|
|
|
|
108
|
33 |
|
$result = self::packJsonSuccess($response, $request['ID']); |
109
|
|
|
|
110
|
42 |
|
} catch (RpcException $re) { |
111
|
|
|
|
112
|
12 |
|
$this->logger->warning("Error handling request ".$request['ID'].": ".$re->getMessage()); |
113
|
|
|
|
114
|
12 |
|
$result = self::packJsonError($re->getCode(), $re->getMessage(), $request['ID']); |
115
|
|
|
|
116
|
12 |
|
} catch (Exception $e) { |
117
|
|
|
|
118
|
|
|
$this->logger->error($e->getMessage()); |
119
|
|
|
|
120
|
|
|
throw $e; |
121
|
|
|
|
122
|
|
|
} |
123
|
|
|
|
124
|
42 |
|
if ( !is_null($result) ) $this->results[] = $result; |
125
|
|
|
|
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
42 |
|
} |
130
|
|
|
|
131
|
42 |
|
if ( empty($this->results) ) { |
132
|
|
|
|
133
|
3 |
|
return null; |
134
|
|
|
|
135
|
39 |
|
} else if ( $this->is_batch_request ) { |
136
|
|
|
|
137
|
9 |
|
return $this->results; |
138
|
|
|
|
139
|
|
|
} else { |
140
|
|
|
|
141
|
30 |
|
return $this->results[0]; |
142
|
|
|
|
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Static constructor - start processor |
149
|
|
|
* |
150
|
|
|
* @param array|object $payload |
151
|
|
|
* @param \Comodojo\RpcServer\Request\Parameters $parameters |
152
|
|
|
* @param \Psr\Log\LoggerInterface $logger |
153
|
|
|
* |
154
|
|
|
* @return mixed |
155
|
|
|
* @throws Exception |
156
|
|
|
*/ |
157
|
42 |
View Code Duplication |
public static function process($payload, Parameters $parameters, \Psr\Log\LoggerInterface $logger) { |
|
|
|
|
158
|
|
|
|
159
|
|
|
try { |
160
|
|
|
|
161
|
42 |
|
$processor = new JsonProcessor($payload, $parameters, $logger); |
162
|
|
|
|
163
|
42 |
|
$return = $processor->run(); |
164
|
|
|
|
165
|
42 |
|
} catch (Exception $e) { |
166
|
|
|
|
167
|
|
|
throw $e; |
168
|
|
|
|
169
|
|
|
} |
170
|
|
|
|
171
|
42 |
|
return $return; |
172
|
|
|
|
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Preprocess json payload |
177
|
|
|
* |
178
|
|
|
* @param array|object $payload |
179
|
|
|
* |
180
|
|
|
* @return array |
181
|
|
|
*/ |
182
|
42 |
|
private static function preprocessJsonPayload($payload) { |
183
|
|
|
|
184
|
42 |
|
$requests = array(); |
185
|
|
|
|
186
|
42 |
|
$is_batch = false; |
187
|
|
|
|
188
|
42 |
|
if ( is_array($payload) ) { |
189
|
|
|
|
190
|
9 |
|
$is_batch = true; |
191
|
|
|
|
192
|
9 |
|
foreach ( $payload as $request ) $requests[] = self::preprocessJsonRequest($request); |
193
|
|
|
|
194
|
9 |
|
} else { |
195
|
|
|
|
196
|
33 |
|
$requests[] = self::preprocessJsonRequest($payload); |
197
|
|
|
|
198
|
|
|
} |
199
|
|
|
|
200
|
42 |
|
return array($is_batch, $requests); |
201
|
|
|
|
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Preprocess a single json request |
206
|
|
|
* |
207
|
|
|
* @param array|object $request |
208
|
|
|
* |
209
|
|
|
* @return array |
210
|
|
|
*/ |
211
|
42 |
|
private static function preprocessJsonRequest($request) { |
212
|
|
|
|
213
|
|
|
// check for required parameters |
214
|
|
|
|
215
|
|
|
if ( |
216
|
42 |
|
!is_object($request) || |
217
|
42 |
|
!property_exists($request, 'jsonrpc') || |
218
|
42 |
|
!property_exists($request, 'method') || |
219
|
42 |
|
$request->jsonrpc != '2.0' || |
220
|
|
|
empty($request->method) |
221
|
42 |
|
) { |
222
|
|
|
|
223
|
|
|
return array( |
224
|
|
|
'ERROR_CODE' => -32600, |
225
|
|
|
'ERROR_MESSAGE' => 'Invalid Request', |
226
|
|
|
'ID' => !isset($request['id']) ? null : $request['id'] |
227
|
|
|
); |
228
|
|
|
|
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
// parse request's components |
232
|
|
|
|
233
|
|
|
return array( |
234
|
42 |
|
'METHOD' => $request->method, |
235
|
42 |
|
'PARAMETERS' => property_exists($request, 'params') ? $request->params : array(), |
236
|
42 |
|
'ID' => property_exists($request, 'id') ? $request->id : null |
237
|
42 |
|
); |
238
|
|
|
|
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Exec a single request |
243
|
|
|
* |
244
|
|
|
* @param string $request_method |
245
|
|
|
* @param array $parameters |
246
|
|
|
* |
247
|
|
|
* @return mixed |
248
|
|
|
* @throws RpcException |
249
|
|
|
*/ |
250
|
42 |
|
private function runSingleRequest($request_method, $parameters) { |
251
|
|
|
|
252
|
|
|
try { |
253
|
|
|
|
254
|
42 |
|
$registered_method = $this->checkRequestSustainability($request_method); |
255
|
|
|
|
256
|
39 |
|
$selected_signature = $this->checkRequestConsistence($registered_method, $parameters); |
257
|
|
|
|
258
|
39 |
|
if ( is_array($parameters) ) $parameters = self::matchParameters($parameters, $registered_method, $selected_signature); |
259
|
|
|
|
260
|
39 |
|
$this->parameters->setParameters($parameters); |
261
|
|
|
|
262
|
39 |
|
$callback = $registered_method->getCallback(); |
263
|
|
|
|
264
|
39 |
|
$method = $registered_method->getMethod(); |
265
|
|
|
|
266
|
42 |
|
} catch (RpcException $re) { |
267
|
|
|
|
268
|
6 |
|
throw $re; |
269
|
|
|
|
270
|
|
|
} |
271
|
|
|
|
272
|
39 |
|
set_error_handler( |
273
|
|
|
|
274
|
|
View Code Duplication |
function($severity, $message, $file, $line) { |
|
|
|
|
275
|
|
|
|
276
|
|
|
$this->logger->error($message, array( |
277
|
|
|
"FILE" => $file, |
278
|
|
|
"LINE" => $line |
279
|
|
|
)); |
280
|
|
|
|
281
|
|
|
throw new RpcException('Internal error', -32603); |
282
|
|
|
|
283
|
|
|
} |
284
|
|
|
|
285
|
39 |
|
); |
286
|
|
|
|
287
|
|
|
try { |
288
|
|
|
|
289
|
39 |
|
$return = empty($method) ? call_user_func($callback, $this->parameters) : call_user_func(array($callback, $method), $this->parameters); |
290
|
|
|
|
291
|
39 |
|
} catch (RpcException $re) { |
292
|
|
|
|
293
|
6 |
|
restore_error_handler(); |
294
|
|
|
|
295
|
6 |
|
throw $re; |
296
|
|
|
|
297
|
|
|
} catch (Exception $e) { |
298
|
|
|
|
299
|
|
|
restore_error_handler(); |
300
|
|
|
|
301
|
|
|
$this->logger->error($e->getMessage(), array( |
302
|
|
|
"FILE" => $e->getFile(), |
303
|
|
|
"LINE" => $e->getLine() |
304
|
|
|
)); |
305
|
|
|
|
306
|
|
|
throw new RpcException('Internal error', -32603); |
307
|
|
|
|
308
|
|
|
} |
309
|
|
|
|
310
|
33 |
|
restore_error_handler(); |
311
|
|
|
|
312
|
33 |
|
return $return; |
313
|
|
|
|
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Pack a json error response |
318
|
|
|
* |
319
|
|
|
* @param integer $code |
320
|
|
|
* @param string $message |
321
|
|
|
* @param integer $id |
322
|
|
|
* |
323
|
|
|
* @return array|null |
324
|
|
|
*/ |
325
|
12 |
|
private static function packJsonError($code, $message, $id) { |
326
|
|
|
|
327
|
12 |
|
if ( !is_null($id) ) { |
328
|
|
|
|
329
|
|
|
return array( |
330
|
12 |
|
'jsonrpc' => '2.0', |
331
|
|
|
'error' => array( |
332
|
12 |
|
'code' => $code, |
333
|
|
|
'message' => $message |
334
|
12 |
|
), |
335
|
|
|
'id' => $id |
336
|
12 |
|
); |
337
|
|
|
|
338
|
|
|
} else { |
339
|
|
|
|
340
|
|
|
return null; |
341
|
|
|
|
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Pack a json success response |
348
|
|
|
* |
349
|
|
|
* @param mixed $result |
350
|
|
|
* @param integer $id |
351
|
|
|
* |
352
|
|
|
* @return array |
353
|
|
|
*/ |
354
|
33 |
|
private static function packJsonSuccess($result, $id) { |
355
|
|
|
|
356
|
33 |
|
if ( !is_null($id) ) { |
357
|
|
|
|
358
|
|
|
return array( |
359
|
30 |
|
'jsonrpc' => '2.0', |
360
|
30 |
|
'result' => $result, |
361
|
|
|
'id' => $id |
362
|
30 |
|
); |
363
|
|
|
|
364
|
|
|
} else { |
365
|
|
|
|
366
|
6 |
|
return null; |
367
|
|
|
|
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Create an associative array of $name => $parameter from current signature |
374
|
|
|
* |
375
|
|
|
* @param array $provided |
376
|
|
|
* @param \Comodojo\RpcServer\RpcMethod $method |
377
|
|
|
* @param integer $selected_signature |
378
|
|
|
* |
379
|
|
|
* @return array |
380
|
|
|
*/ |
381
|
39 |
View Code Duplication |
private static function matchParameters($provided, $method, $selected_signature) { |
|
|
|
|
382
|
|
|
|
383
|
39 |
|
$parameters = array(); |
384
|
|
|
|
385
|
39 |
|
$requested_parameters = $method->selectSignature($selected_signature)->getParameters(); |
386
|
|
|
|
387
|
39 |
|
$requested_parameters_keys = array_keys($requested_parameters); |
388
|
|
|
|
389
|
39 |
|
foreach ( $provided as $index => $parameter ) { |
390
|
|
|
|
391
|
24 |
|
$parameters[$requested_parameters_keys[$index]] = $parameter; |
392
|
|
|
|
393
|
39 |
|
} |
394
|
|
|
|
395
|
39 |
|
return $parameters; |
396
|
|
|
|
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Check if a request is sustainable (i.e. if method is registered) |
401
|
|
|
* |
402
|
|
|
* @param string $request_method |
403
|
|
|
* |
404
|
|
|
* @return \Comodojo\RpcServer\RpcMethod |
405
|
|
|
* @throws \Comodojo\Exception\RpcException |
406
|
|
|
*/ |
407
|
42 |
View Code Duplication |
private function checkRequestSustainability($request_method) { |
|
|
|
|
408
|
|
|
|
409
|
42 |
|
$method = $this->parameters->methods()->get($request_method); |
410
|
|
|
|
411
|
42 |
|
if ( is_null($method) ) throw new RpcException("Method not found", -32601); |
412
|
|
|
|
413
|
39 |
|
return $method; |
414
|
|
|
|
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Check if a request is consistent (i.e. if it matches one of method's signatures) |
419
|
|
|
* |
420
|
|
|
* @param \Comodojo\RpcServer\RpcMethod $registered_method |
421
|
|
|
* @param array $parameters |
422
|
|
|
* |
423
|
|
|
* @return int |
424
|
|
|
* @throws \Comodojo\Exception\RpcException |
425
|
|
|
*/ |
426
|
39 |
|
private function checkRequestConsistence($registered_method, $parameters) { |
427
|
|
|
|
428
|
39 |
|
$signatures = $registered_method->getSignatures(false); |
429
|
|
|
|
430
|
39 |
|
foreach ( $signatures as $num => $signature ) { |
431
|
|
|
|
432
|
39 |
|
if ( self::checkSignatureMatch($parameters, $signature["PARAMETERS"]) === true ) return $num; |
433
|
|
|
|
434
|
3 |
|
} |
435
|
|
|
|
436
|
|
|
throw new RpcException("Invalid params", -32602); |
437
|
|
|
|
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Check if call match a signature |
442
|
|
|
* |
443
|
|
|
* @param array $provided |
444
|
|
|
* @param array $requested |
445
|
|
|
* |
446
|
|
|
* @return bool |
447
|
|
|
*/ |
448
|
39 |
|
private static function checkSignatureMatch($provided, $requested) { |
449
|
|
|
|
450
|
39 |
|
if ( is_object($provided) ) { |
451
|
|
|
|
452
|
|
|
foreach ( $provided as $parameter=>$value ) { |
453
|
|
|
|
454
|
|
|
if ( |
455
|
|
|
!isset($requested[$parameter]) || |
456
|
|
|
!DataValidator::validate($requested[$parameter], $value) |
457
|
|
|
) return false; |
458
|
|
|
|
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
} else { |
462
|
|
|
|
463
|
39 |
|
$provided_parameters_count = count($provided); |
464
|
|
|
|
465
|
39 |
|
$requested_parameters_count = count($requested); |
466
|
|
|
|
467
|
39 |
|
if ( $provided_parameters_count != $requested_parameters_count ) return false; |
468
|
|
|
|
469
|
39 |
|
$index = 0; |
470
|
|
|
|
471
|
39 |
|
foreach ($requested as $parameter => $type) { |
472
|
|
|
|
473
|
24 |
|
if ( !DataValidator::validate($type, $provided[$index]) ) return false; |
474
|
|
|
|
475
|
24 |
|
$index += 1; |
476
|
|
|
|
477
|
39 |
|
} |
478
|
|
|
|
479
|
|
|
} |
480
|
|
|
|
481
|
39 |
|
return true; |
482
|
|
|
|
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
} |
486
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.