blob: 4c88b2e1849df1ed653df32d8be0cdc2cf0a038f [file] [log] [blame]
<?php
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
namespace Grpc;
/**
* This is an experimental and incomplete implementation of gRPC server
* for PHP. APIs are _definitely_ going to be changed.
*
* DO NOT USE in production.
*/
/**
* Class RpcServer
* @package Grpc
*/
class RpcServer extends Server
{
protected $call;
// [ <String method_full_path> => [
// 'service' => <Object service>,
// 'method' => <String method_name>,
// 'request' => <Object 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;
}
}
}