Completed
Pull Request — master (#2)
by Sergey
03:53
created

JsonRpcHandler::getApiExec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Onnov\JsonRpcServer;
6
7
use JsonException;
8
use JsonMapper;
9
use Onnov\JsonRpcServer\Model\RunModel;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\LogLevel;
12
use stdClass;
13
use Throwable;
14
use Onnov\JsonRpcServer\Exception\InvalidAuthorizeException;
15
use Onnov\JsonRpcServer\Exception\InvalidParamsException;
16
use Onnov\JsonRpcServer\Exception\InvalidRequestException;
17
use Onnov\JsonRpcServer\Exception\MethodErrorException;
18
use Onnov\JsonRpcServer\Exception\MethodNotFoundException;
19
use Onnov\JsonRpcServer\Exception\ParseErrorException;
20
use Onnov\JsonRpcServer\Service\ApiExecService;
21
use Onnov\JsonRpcServer\Service\RpcService;
22
use Onnov\JsonRpcServer\Validator\JsonRpcSchema;
23
use Onnov\JsonRpcServer\Validator\JsonSchemaValidator;
24
25
/**
26
 * Class JsonRpcHandler
27
 *
28
 * @package Onnov\JsonRpcServer
29
 */
30
class JsonRpcHandler
31
{
32
    /** @var LoggerInterface|null */
33
    private $logger;
34
35
    /** @var RpcService */
36
    private $rpcService;
37
38
    /** @var ApiExecService */
39
    private $apiExecService;
40
41
    /**
42
     * JsonRpcHandler constructor.
43
     *
44
     * @param LoggerInterface|null $logger
45
     */
46
    public function __construct(LoggerInterface $logger = null)
47
    {
48
        $this->logger = $logger;
49
50
        $validator = new JsonSchemaValidator();
51
        $rpcSchema = new JsonRpcSchema();
52
        $mapper = new JsonMapper();
53
54
        $this->rpcService = new RpcService($validator, $rpcSchema, $mapper);
55
        $this->apiExecService = new ApiExecService($validator, $rpcSchema, $mapper);
56
    }
57
58
    /**
59
     * @param RunModel $model
60
     * @return string
61
     */
62
    public function run(RunModel $model): string
63
    {
64
        $rpcService = $this->getRpcService();
65
66
        /** Парсим */
67
        try {
68
            $data = $rpcService->jsonParse($model->getJson());
69
        } catch (ParseErrorException $e) {
70
            $resErr = '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "'
71
                . 'Parse error: ' . $e->getMessage() . '"}, "id": null}';
72
73
            $this->log(LogLevel::ERROR, [$resErr]);
74
75
            return $resErr;
76
        }
77
78
        $resArr = [];
79
        foreach ($data as $rpc) {
80
            // TODO впилить паралельное выполнение, возможно amphp/amp
81
            $resArr[] = $this->oneRun(
82
                $model,
83
                $rpc
84
            );
85
        }
86
87
        $res = implode(',', $resArr);
88
89
        if ($rpcService->isBatch()) {
90
            $res = '[' . $res . ']';
91
        }
92
93
        return $res;
94
    }
95
96
    /**
97
     * @param RunModel $model
98
     * @param stdClass $rpc
99
     * @return string
100
     */
101
    private function oneRun(
102
        RunModel $model,
103
        stdClass $rpc
104
    ): string {
105
        $res = [
106
            'jsonrpc' => '2.0',
107
        ];
108
109
        $errorLevel = null;
110
111
        try {
112
            /** валидируем и парсим JsonRPC */
113
            $rpcObj = $this->getRpcService()->getRpc($rpc);
114
115
            /** Проверим авторизацию */
116
            $this->authCheck($rpcObj->getMethod(), $model->isResultAuth(), $model->getMethodsWithoutAuth());
117
118
            /** Пытаемся выполнить запрос */
119
            $res['result'] = $this->getApiExeService()->exe(
120
                $model,
121
                $rpcObj
122
            );
123
        } catch (InvalidAuthorizeException $e) {
124
            $res['error'] = [
125
                'code'    => -32001,
126
                'message' => $e->getMessage(),
127
            ];
128
            $errorLevel = LogLevel::INFO;
129
        } catch (ParseErrorException $e) {
130
            $res['error'] = [
131
                'code'    => -32700,
132
                'message' => 'Parse error: ' . $e->getMessage(),
133
            ];
134
            $errorLevel = LogLevel::ERROR;
135
        } catch (InvalidRequestException $e) {
136
            $res['error'] = [
137
                'code'    => -32600,
138
                'message' => 'Invalid Request: ' . $e->getMessage(),
139
            ];
140
            $errorLevel = LogLevel::ERROR;
141
        } catch (MethodNotFoundException $e) {
142
            $res['error'] = [
143
                'code'    => -32601,
144
                'message' => $e->getMessage(), // 'Method not found',
145
            ];
146
            $errorLevel = LogLevel::ERROR;
147
        } catch (InvalidParamsException $e) {
148
            $res['error'] = [
149
                'code'    => -32602,
150
                'message' => $e->getMessage(), // 'Invalid params',
151
                'data'    => $e->getData(),
152
            ];
153
            $errorLevel = LogLevel::ERROR;
154
        } catch (MethodErrorException $e) {
155
            $res['error'] = [
156
                'code'    => $e->getCode(),
157
                'message' => $e->getMessage(),
158
                'data'    => $e->getData(),
159
            ];
160
            $errorLevel = LogLevel::NOTICE;
161
        } catch (Throwable $t) {
162
            $res['error'] = [
163
                'code'    => -32603,
164
                'message' => 'Internal error: ' . $t->getMessage(),
165
                'data'    => [
166
                    'exception' => get_class($t),
167
                    'code'      => $t->getCode(),
168
                    'file'      => $t->getFile(),
169
                    'line'      => $t->getLine(),
170
                ]
171
            ];
172
            $errorLevel = LogLevel::EMERGENCY;
173
        }
174
175
        $this->log($errorLevel, $res);
176
177
        return $this->getJsonResult($rpc, $res);
178
    }
179
180
    /**
181
     * @param string $method
182
     * @param bool $resultAuth
183
     * @param string[] $methodsWithoutAuth
184
     */
185
    private function authCheck(string $method, bool $resultAuth, array $methodsWithoutAuth): void
186
    {
187
        if (
188
            $resultAuth === false
189
            && in_array($method, $methodsWithoutAuth, true) === false
190
        ) {
191
            throw new InvalidAuthorizeException(
192
                'Access denied, you are not authorized', // 'Доступ запрещен, вы не авторизованы'
193
            );
194
        }
195
    }
196
197
    /**
198
     * @param stdClass $rpc
199
     * @param mixed[] $res
200
     * @return string
201
     */
202
    private function getJsonResult(stdClass $rpc, array &$res): string
203
    {
204
        // ???
205
        $strId = 'error';
206
        if (isset($rpc->id)) {
207
            $res['id'] = $rpc->id;
208
            $strId = $rpc->id;
209
        }
210
211
        try {
212
            $result = json_encode($res, JSON_THROW_ON_ERROR);
213
        } catch (JsonException $e) {
214
            $result = '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "'
215
                . $e->getMessage() . '"}, "id": ' . $strId . '}';
216
217
            $this->log(LogLevel::ERROR, [$result]);
218
        }
219
220
        return $result;
221
    }
222
223
    /**
224
     * @param string|null $errorLevel
225
     * @param mixed[]     $res
226
     */
227
    private function log(?string $errorLevel, array $res): void
228
    {
229
        $logger = $this->getLogger();
230
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
231
            $error = $res['error'] ?? [];
232
            $logger->log($errorLevel, $error['message'], $error);
233
        }
234
    }
235
236
    /**
237
     * @return LoggerInterface|null
238
     */
239
    public function getLogger(): ?LoggerInterface
240
    {
241
        return $this->logger;
242
    }
243
244
    /**
245
     * @param LoggerInterface|null $logger
246
     */
247
    public function setLogger(?LoggerInterface $logger): void
248
    {
249
        $this->logger = $logger;
250
    }
251
252
    /**
253
     * @return RpcService
254
     */
255
    public function getRpcService(): RpcService
256
    {
257
        return $this->rpcService;
258
    }
259
260
    /**
261
     * @return ApiExecService
262
     */
263
    public function getApiExeService(): ApiExecService
264
    {
265
        return $this->apiExecService;
266
    }
267
}
268