Skip to content

Commit f0dd2af

Browse files
committed
batch requests fix
1 parent 344197f commit f0dd2af

File tree

4 files changed

+112
-67
lines changed

4 files changed

+112
-67
lines changed

src/RpcServer.php

Lines changed: 54 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use Symfony\Component\HttpFoundation\Request;
1313

1414
/**
15-
* Class Component\RpcServer
15+
* Class RpcServer
1616
*/
1717
class RpcServer
1818
{
@@ -34,12 +34,11 @@ class RpcServer
3434
private $middleware = [];
3535

3636
/**
37-
* @param string $name
38-
* @param \Closure $callback
37+
* @param string $className
38+
* @param \Closure $parametersCallback
3939
*
4040
* @return $this
41-
*
42-
* @throws RpcServiceExistsException
41+
* @throws \Devim\Component\RpcServer\Exception\RpcServiceExistsException
4342
*/
4443
public function addService(string $className, \Closure $parametersCallback)
4544
{
@@ -75,38 +74,57 @@ public function addMiddleware(MiddlewareInterface $middleware)
7574
* @param Request $request
7675
*
7776
* @return JsonResponse
77+
*
78+
* @throws \Devim\Component\RpcServer\Exception\RpcServiceNotFoundException
79+
* @throws \Devim\Component\RpcServer\Exception\RpcParseException
80+
* @throws \Devim\Component\RpcServer\Exception\RpcMethodNotFoundException
81+
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidRequestException
82+
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidParamsException
83+
* @throws \LogicException
7884
*/
7985
public function run(Request $request) : JsonResponse
8086
{
87+
$response = [];
88+
8189
$payload = json_decode($request->getContent(), true);
8290

83-
return JsonResponse::create($this->doRun($payload));
91+
if ($this->isBatchRequest($payload)) {
92+
foreach ($payload as $item) {
93+
$response[] = $this->doRun($item);
94+
}
95+
if (count($response) === 1) {
96+
$response = reset($response);
97+
}
98+
} else {
99+
$response = $this->doRun($payload);
100+
}
101+
102+
return JsonResponse::create($response);
84103
}
85104

86105
/**
87106
* @param mixed $payload
88107
*
89108
* @return mixed
109+
*
110+
* @throws \Devim\Component\RpcServer\Exception\RpcServiceNotFoundException
111+
* @throws \Devim\Component\RpcServer\Exception\RpcParseException
112+
* @throws \Devim\Component\RpcServer\Exception\RpcMethodNotFoundException
113+
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidParamsException
114+
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidRequestException
90115
*/
91116
private function doRun($payload)
92117
{
93118
try {
94119
$this->validatePayload($payload);
120+
} catch (\Throwable $e) {
121+
return $this->handleExceptions($payload, $e);
122+
}
95123

96-
if ($this->isBatchRequest($payload)) {
97-
return $this->parseBatchRequest($payload);
98-
}
99-
124+
try {
100125
return $this->parseRequest($payload);
101-
102126
} catch (\Throwable $e) {
103-
if ($e instanceof RpcInvalidRequestException || $e instanceof RpcParseException) {
104-
return $this->handleExceptions(null, $e);
105-
}
106-
107-
if (!$this->isNotification($payload)) {
108-
return $this->handleExceptions($payload['id'], $e);
109-
}
127+
return $this->handleExceptions($payload, $e);
110128
}
111129
}
112130

@@ -116,9 +134,9 @@ private function doRun($payload)
116134
* @throws RpcInvalidRequestException
117135
* @throws RpcParseException
118136
*/
119-
private function validatePayload($payload)
137+
private function validatePayload(&$payload)
120138
{
121-
if (!is_array($payload)) {
139+
if (null === $payload) {
122140
throw new RpcParseException();
123141
}
124142

@@ -133,35 +151,13 @@ private function validatePayload($payload)
133151
}
134152

135153
/**
136-
* @param array $payload
154+
* @param mixed $payload
137155
*
138156
* @return bool
139157
*/
140-
private function isBatchRequest(array $payload) : bool
141-
{
142-
return array_keys($payload) === range(0, count($payload) - 1);
143-
}
144-
145-
/**
146-
* @param array $payloads
147-
*
148-
* @return array
149-
*
150-
* @throws \Devim\Component\RpcServer\Exception\RpcServiceNotFoundException
151-
* @throws \Devim\Component\RpcServer\Exception\RpcParseException
152-
* @throws \Devim\Component\RpcServer\Exception\RpcMethodNotFoundException
153-
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidRequestException
154-
* @throws \Devim\Component\RpcServer\Exception\RpcInvalidParamsException
155-
*/
156-
private function parseBatchRequest(array $payloads) : array
158+
private function isBatchRequest($payload) : bool
157159
{
158-
$results = [];
159-
160-
foreach ($payloads as $payload) {
161-
$results[] = $this->parseRequest($payload);
162-
}
163-
164-
return array_filter($results);
160+
return is_array($payload) && array_keys($payload) === range(0, count($payload) - 1);
165161
}
166162

167163
/**
@@ -194,11 +190,7 @@ private function parseRequest(array $payload)
194190

195191
$result = $this->invokeMethod($service, $methodName, $params);
196192

197-
if (!$this->isNotification($payload)) {
198-
return ResponseBuilder::build($payload['id'], $result);
199-
}
200-
201-
return '';
193+
return ResponseBuilder::build($this->extractRequestId($payload), $result);
202194
}
203195

204196
/**
@@ -291,19 +283,24 @@ private function invokeMethod($service, string $method, array $params)
291283
return (new \ReflectionMethod($service, $method))->invokeArgs($service, $paramsValue);
292284
}
293285

294-
private function isNotification(array $payload)
286+
/**
287+
* @param $payload
288+
* @param \Throwable $exception
289+
*
290+
* @return array
291+
*/
292+
private function handleExceptions($payload, \Throwable $exception) : array
295293
{
296-
return !isset($payload['id']);
294+
return ResponseBuilder::build($this->extractRequestId($payload), $exception);
297295
}
298296

299297
/**
300-
* @param int|null $id
301-
* @param \Throwable $exception
298+
* @param $payload
302299
*
303-
* @return array
300+
* @return null|int
304301
*/
305-
private function handleExceptions($id, \Throwable $exception) : array
302+
private function extractRequestId($payload)
306303
{
307-
return ResponseBuilder::build($id, $exception);
304+
return $payload['id'] ?? null;
308305
}
309306
}

tests/_data/TestRpcService.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* Class TestRpcService
5+
*/
6+
class TestRpcService
7+
{
8+
/**
9+
* @param $value
10+
*
11+
* @return array
12+
*/
13+
public function method($value)
14+
{
15+
return [$value];
16+
}
17+
18+
}

tests/unit/RpcServerTest.php

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ class RpcServerTest extends \Codeception\Test\Unit
1414
public function specDataProvider()
1515
{
1616
$specTests = [
17+
[
18+
'{"jsonrpc": "2.0", "method": "test.method", "params": [1], "id": "3"}',
19+
'{"jsonrpc": "2.0", "result": [1], "id": "3"}',
20+
],
21+
[
22+
'{"jsonrpc": "2.0", "method": "test.method", "params": {"value": 5}, "id": "3"}',
23+
'{"jsonrpc": "2.0", "result": [5], "id": "3"}',
24+
],
1725
[
1826
'{"jsonrpc": "2.0", "method": "math.subtract", "params": [56, 51], "id": 1}',
1927
'{"jsonrpc": "2.0", "error": {"code":-32601, "message":"Service \u0022math\u0022 not found"}, "id": 1}',
@@ -24,7 +32,11 @@ public function specDataProvider()
2432
],
2533
[
2634
'{"jsonrpc": "2.0", "method": "math.subtract", "params": [42, 23]}',
27-
'{"jsonrpc": "2.0", "data": {}',
35+
'{"jsonrpc": "2.0", "error": {"code":-32601, "message":"Service \u0022math\u0022 not found"}, "id": null}',
36+
],
37+
[
38+
'{"jsonrpc": "2.0", "method": "test.subtract", "params": [42, 23]}',
39+
'{"jsonrpc": "2.0", "error": {"code":-32601,"message":"Method \u0022subtract\u0022 not found in service \u0022test\u0022"}, "id": null}',
2840
],
2941
[
3042
'{"jsonrpc": "2.0", "method": "math.subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 1]',
@@ -46,26 +58,38 @@ public function specDataProvider()
4658
'[1]',
4759
'{"jsonrpc": "2.0", "error": {"code":-32600,"message":"Invalid Request"}, "id": null}',
4860
],
49-
50-
// currently batch requests is not allowed :(
5161
[
5262
'[
63+
{"jsonrpc": "2.0", "method": "test.method", "params": [1], "id": "3"},
64+
{"jsonrpc": "2.0", "method": "test.notExistsMethod", "params": [1], "id": "3"},
5365
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
5466
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
5567
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
5668
{"foo": "boo"},
5769
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
58-
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
70+
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
5971
]',
60-
'{"jsonrpc": "2.0", "error": {"code":-32600,"message":"Invalid Request"}, "id": null}',
72+
'[
73+
{"jsonrpc": "2.0", "result": [1], "id": "3"},
74+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method \u0022notExistsMethod\u0022 not found in service \u0022test\u0022"}, "id":"3"},
75+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Service \u0022\u0022 not found"}, "id":"1"},
76+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Service \u0022\u0022 not found"}, "id":null},
77+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Service \u0022\u0022 not found"}, "id":"2"},
78+
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id":null},
79+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Service \u0022foo\u0022 not found"}, "id":"5"},
80+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Service \u0022\u0022 not found"}, "id":"9"}
81+
]',
6182
],
6283
[
6384
'[
64-
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
65-
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
85+
{"jsonrpc": "2.0", "method": "test.method", "params": {"value":2}, "id": 1 },
86+
{"jsonrpc": "2.0", "method": "test.notExistsMethod", "params": [7], "id": 2}
6687
]',
67-
'{"jsonrpc": "2.0", "error": {"code":-32600,"message":"Invalid Request"}, "id": null}',
68-
]
88+
'[
89+
{"jsonrpc": "2.0", "result": [2], "id": 1},
90+
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method \u0022notExistsMethod\u0022 not found in service \u0022test\u0022"}, "id": 2}
91+
]',
92+
],
6993
];
7094

7195
foreach ($specTests as &$test) {
@@ -85,10 +109,14 @@ public function specDataProvider()
85109
public function testRunSpec($request, $response)
86110
{
87111
$rpcServer = new RpcServer();
112+
$rpcServer->addService(TestRpcService::class, function () {
113+
return [];
114+
});
88115
$this->assertEquals(
89116
$rpcServer->run($request),
90117
$response,
91118
'->run() properly process JSON RPC 2.0 request and achive spec requirements'
92119
);
93120
}
94-
}
121+
}
122+

tests/unit/_bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
<?php
22
// Here you can initialize variables that will be available to your tests
3+
4+
include_once __DIR__ . '/../_data/TestRpcService.php';

0 commit comments

Comments
 (0)