Skip to content

Commit e168796

Browse files
Merge pull request #27 from stanley-cheung/update-to-1_38
Update to 1.38
2 parents 6145dd9 + db8f4f9 commit e168796

9 files changed

+385
-116
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"keywords": ["rpc"],
66
"homepage": "https://grpc.io",
77
"license": "Apache-2.0",
8-
"version": "1.36.0",
8+
"version": "1.38.0",
99
"require": {
1010
"php": ">=7.0.0"
1111
},

src/lib/CallInvoker.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
/**
2222
* CallInvoker is used to pass the self defined channel into the stub,
2323
* while intercept each RPC with the channel accessible.
24-
* THIS IS AN EXPERIMENTAL API.
2524
*/
2625
interface CallInvoker
2726
{

src/lib/DefaultCallInvoker.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
/**
2222
* Default call invoker in the gRPC stub.
23-
* THIS IS AN EXPERIMENTAL API.
2423
*/
2524
class DefaultCallInvoker implements CallInvoker
2625
{

src/lib/Interceptor.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
* Represents an interceptor that intercept RPC invocations before call starts.
2424
* There is one proposal related to the argument $deserialize under the review.
2525
* The proposal link is https://github.com/grpc/proposal/pull/86.
26-
* This is an EXPERIMENTAL API.
2726
*/
2827
class Interceptor
2928
{

src/lib/MethodDescriptor.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
20+
namespace 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+
class MethodDescriptor
30+
{
31+
public function __construct(
32+
object $service,
33+
string $method_name,
34+
string $request_type,
35+
int $call_type
36+
) {
37+
$this->service = $service;
38+
$this->method_name = $method_name;
39+
$this->request_type = $request_type;
40+
$this->call_type = $call_type;
41+
}
42+
43+
public const UNARY_CALL = 0;
44+
public const SERVER_STREAMING_CALL = 1;
45+
public const CLIENT_STREAMING_CALL = 2;
46+
public const BIDI_STREAMING_CALL = 3;
47+
48+
public $service;
49+
public $method_name;
50+
public $request_type;
51+
public $call_type;
52+
}

src/lib/RpcServer.php

Lines changed: 95 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -32,137 +32,120 @@
3232
*/
3333
class RpcServer extends Server
3434
{
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;
35+
// [ <String method_full_path> => MethodDescriptor ]
36+
private $paths_map = [];
4237

43-
private function waitForNextEvent() {
38+
private function waitForNextEvent()
39+
{
4440
return $this->requestCall();
4541
}
4642

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-
7843
/**
7944
* Add a service to this server
8045
*
8146
* @param Object $service The service to be added
8247
*/
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-
}
48+
public function handle($service)
49+
{
50+
$methodDescriptors = $service->getMethodDescriptors();
51+
$exist_methods = array_intersect_key($this->paths_map, $methodDescriptors);
52+
if (!empty($exist_methods)) {
53+
fwrite(STDERR, "WARNING: " . 'override already registered methods: ' .
54+
implode(', ', array_keys($exist_methods)) . PHP_EOL);
10055
}
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);
10956

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-
}
57+
$this->paths_map = array_merge($this->paths_map, $methodDescriptors);
58+
return $this->paths_map;
12659
}
12760

128-
public function run() {
61+
public function run()
62+
{
12963
$this->start();
130-
while (true) {
64+
while (true) try {
13165
// This blocks until the server receives a request
13266
$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;
14867

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");
68+
$full_path = $event->method;
69+
$context = new ServerContext($event);
70+
$server_writer = new ServerCallWriter($event->call, $context);
71+
72+
if (!array_key_exists($full_path, $this->paths_map)) {
73+
$context->setStatus(Status::unimplemented());
74+
$server_writer->finish();
75+
continue;
76+
};
77+
78+
$method_desc = $this->paths_map[$full_path];
79+
$server_reader = new ServerCallReader(
80+
$event->call,
81+
$method_desc->request_type
82+
);
83+
84+
try {
85+
$this->processCall(
86+
$method_desc,
87+
$server_reader,
88+
$server_writer,
89+
$context
90+
);
91+
} catch (\Exception $e) {
92+
$context->setStatus(Status::status(
93+
STATUS_INTERNAL,
94+
$e->getMessage()
95+
));
96+
$server_writer->finish();
16097
}
98+
} catch (\Exception $e) {
99+
fwrite(STDERR, "ERROR: " . $e->getMessage() . PHP_EOL);
100+
exit(1);
101+
}
102+
}
161103

162-
// Dispatch to actual server logic
163-
$response = $service->$method($request);
164-
$this->sendOkResponse($response);
165-
$this->call = null;
104+
private function processCall(
105+
MethodDescriptor $method_desc,
106+
ServerCallReader $server_reader,
107+
ServerCallWriter $server_writer,
108+
ServerContext $context
109+
) {
110+
// Dispatch to actual server logic
111+
switch ($method_desc->call_type) {
112+
case MethodDescriptor::UNARY_CALL:
113+
$request = $server_reader->read();
114+
$response =
115+
call_user_func(
116+
array($method_desc->service, $method_desc->method_name),
117+
$request ?? new $method_desc->request_type,
118+
$context
119+
);
120+
$server_writer->finish($response);
121+
break;
122+
case MethodDescriptor::SERVER_STREAMING_CALL:
123+
$request = $server_reader->read();
124+
call_user_func(
125+
array($method_desc->service, $method_desc->method_name),
126+
$request ?? new $method_desc->request_type,
127+
$server_writer,
128+
$context
129+
);
130+
break;
131+
case MethodDescriptor::CLIENT_STREAMING_CALL:
132+
$response = call_user_func(
133+
array($method_desc->service, $method_desc->method_name),
134+
$server_reader,
135+
$context
136+
);
137+
$server_writer->finish($response);
138+
break;
139+
case MethodDescriptor::BIDI_STREAMING_CALL:
140+
call_user_func(
141+
array($method_desc->service, $method_desc->method_name),
142+
$server_reader,
143+
$server_writer,
144+
$context
145+
);
146+
break;
147+
default:
148+
throw new \Exception();
166149
}
167150
}
168151
}

src/lib/ServerCallReader.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
20+
namespace 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+
class ServerCallReader
30+
{
31+
public function __construct($call, string $request_type)
32+
{
33+
$this->call_ = $call;
34+
$this->request_type_ = $request_type;
35+
}
36+
37+
public function read()
38+
{
39+
$event = $this->call_->startBatch([
40+
OP_RECV_MESSAGE => true,
41+
]);
42+
if ($event->message === null) {
43+
return null;
44+
}
45+
$data = new $this->request_type_;
46+
$data->mergeFromString($event->message);
47+
return $data;
48+
}
49+
50+
private $call_;
51+
private $request_type_;
52+
}

0 commit comments

Comments
 (0)