=> [ // 'service' => , // 'method' => , // 'request' => , // ] ] protected $paths_map; private function waitForNextEvent() { return $this->requestCall(); } private function loadRequest($request) { if (!$this->call) { throw new Exception("serverCall is not ready"); } $event = $this->call->startBatch([ OP_RECV_MESSAGE => true, ]); if (!$event->message) { throw new Exception("Did not receive a proper message"); } $request->mergeFromString($event->message); return $request; } protected function sendOkResponse($response) { if (!$this->call) { throw new Exception("serverCall is not ready"); } $this->call->startBatch([ OP_SEND_INITIAL_METADATA => [], OP_SEND_MESSAGE => ['message' => $response->serializeToString()], OP_SEND_STATUS_FROM_SERVER => [ 'metadata' => [], 'code' => STATUS_OK, 'details' => 'OK', ], OP_RECV_CLOSE_ON_SERVER => true, ]); } /** * Add a service to this server * * @param Object $service The service to be added */ public function handle($service) { $rf = new \ReflectionClass($service); // If input does not have a parent class, which should be the // generated stub, don't proceeed. This might change in the // future. if (!$rf->getParentClass()) return; // The input class name needs to match the service name $service_name = $rf->getName(); $namespace = $rf->getParentClass()->getNamespaceName(); $prefix = ""; if ($namespace) { $parts = explode("\\", $namespace); foreach ($parts as $part) { $prefix .= lcfirst($part) . "."; } } $base_path = "/" . $prefix . $service_name; // Right now, assume all the methods in the class are RPC method // implementations. Might change in the future. $methods = $rf->getMethods(); foreach ($methods as $method) { $method_name = $method->getName(); $full_path = $base_path . "/" . ucfirst($method_name); $method_params = $method->getParameters(); // RPC should have exactly 1 request param if (count($method_params) != 1) continue; $request_param = $method_params[0]; // Method implementation must have type hint for request param if (!$request_param->getType()) continue; $request_type = $request_param->getType()->getName(); // $full_path needs to match the incoming event->method // from requestCall() for us to know how to handle the request $this->paths_map[$full_path] = [ 'service' => $service, 'method' => $method_name, 'request' => new $request_type(), ]; } } public function run() { $this->start(); while (true) { // This blocks until the server receives a request $event = $this->waitForNextEvent(); if (!$event) { throw new Exception( "Unexpected error: server->waitForNextEvent delivers" . " an empty event"); } if (!$event->call) { throw new Exception( "Unexpected error: server->waitForNextEvent delivers" . " an event without a call"); } $this->call = $event->call; $full_path = $event->method; // TODO: Can send a proper UNIMPLEMENTED response in the future if (!array_key_exists($full_path, $this->paths_map)) continue; $service = $this->paths_map[$full_path]['service']; $method = $this->paths_map[$full_path]['method']; $request = $this->paths_map[$full_path]['request']; $request = $this->loadRequest($request); if (!$request) { throw new Exception("Unexpected error: fail to parse request"); } if (!method_exists($service, $method)) { // TODO: Can send a proper UNIMPLEMENTED response in the future throw new Exception("Method not implemented"); } // Dispatch to actual server logic $response = $service->$method($request); $this->sendOkResponse($response); $this->call = null; } } }