• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<?php
2/*
3 *
4 * Copyright 2020 gRPC authors.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20namespace Grpc;
21
22/**
23 * This is an experimental and incomplete implementation of gRPC server
24 * for PHP. APIs are _definitely_ going to be changed.
25 *
26 * DO NOT USE in production.
27 */
28
29/**
30 * Class RpcServer
31 * @package Grpc
32 */
33class RpcServer extends Server
34{
35    protected $call;
36    // [ <String method_full_path> => [
37    //   'service' => <Object service>,
38    //   'method'  => <String method_name>,
39    //   'request' => <Object request>,
40    // ] ]
41    protected $paths_map;
42
43    private function waitForNextEvent() {
44        return $this->requestCall();
45    }
46
47    private function loadRequest($request) {
48        if (!$this->call) {
49            throw new Exception("serverCall is not ready");
50        }
51        $event = $this->call->startBatch([
52            OP_RECV_MESSAGE => true,
53        ]);
54        if (!$event->message) {
55            throw new Exception("Did not receive a proper message");
56        }
57        $request->mergeFromString($event->message);
58        return $request;
59    }
60
61    protected function sendOkResponse($response) {
62        if (!$this->call) {
63            throw new Exception("serverCall is not ready");
64        }
65        $this->call->startBatch([
66            OP_SEND_INITIAL_METADATA => [],
67            OP_SEND_MESSAGE => ['message' =>
68                                $response->serializeToString()],
69            OP_SEND_STATUS_FROM_SERVER => [
70                'metadata' => [],
71                'code' => STATUS_OK,
72                'details' => 'OK',
73            ],
74            OP_RECV_CLOSE_ON_SERVER => true,
75        ]);
76    }
77
78    /**
79     * Add a service to this server
80     *
81     * @param Object   $service      The service to be added
82     */
83    public function handle($service) {
84        $rf = new \ReflectionClass($service);
85
86        // If input does not have a parent class, which should be the
87        // generated stub, don't proceeed. This might change in the
88        // future.
89        if (!$rf->getParentClass()) return;
90
91        // The input class name needs to match the service name
92        $service_name = $rf->getName();
93        $namespace = $rf->getParentClass()->getNamespaceName();
94        $prefix = "";
95        if ($namespace) {
96            $parts = explode("\\", $namespace);
97            foreach ($parts as $part) {
98                $prefix .= lcfirst($part) . ".";
99            }
100        }
101        $base_path = "/" . $prefix . $service_name;
102
103        // Right now, assume all the methods in the class are RPC method
104        // implementations. Might change in the future.
105        $methods = $rf->getMethods();
106        foreach ($methods as $method) {
107            $method_name = $method->getName();
108            $full_path = $base_path . "/" . ucfirst($method_name);
109
110            $method_params = $method->getParameters();
111            // RPC should have exactly 1 request param
112            if (count($method_params) != 1) continue;
113            $request_param = $method_params[0];
114            // Method implementation must have type hint for request param
115            if (!$request_param->getType()) continue;
116            $request_type = $request_param->getType()->getName();
117
118            // $full_path needs to match the incoming event->method
119            // from requestCall() for us to know how to handle the request
120            $this->paths_map[$full_path] = [
121                'service' => $service,
122                'method' => $method_name,
123                'request' => new $request_type(),
124            ];
125        }
126    }
127
128    public function run() {
129        $this->start();
130        while (true) {
131            // This blocks until the server receives a request
132            $event = $this->waitForNextEvent();
133            if (!$event) {
134                throw new Exception(
135                    "Unexpected error: server->waitForNextEvent delivers"
136                    . " an empty event");
137            }
138            if (!$event->call) {
139                throw new Exception(
140                    "Unexpected error: server->waitForNextEvent delivers"
141                    . " an event without a call");
142            }
143            $this->call = $event->call;
144            $full_path = $event->method;
145
146            // TODO: Can send a proper UNIMPLEMENTED response in the future
147            if (!array_key_exists($full_path, $this->paths_map)) continue;
148
149            $service = $this->paths_map[$full_path]['service'];
150            $method = $this->paths_map[$full_path]['method'];
151            $request = $this->paths_map[$full_path]['request'];
152
153            $request = $this->loadRequest($request);
154            if (!$request) {
155                throw new Exception("Unexpected error: fail to parse request");
156            }
157            if (!method_exists($service, $method)) {
158                // TODO: Can send a proper UNIMPLEMENTED response in the future
159                throw new Exception("Method not implemented");
160            }
161
162            // Dispatch to actual server logic
163            $response = $service->$method($request);
164            $this->sendOkResponse($response);
165            $this->call = null;
166        }
167    }
168}
169