4 * Copyright 2015 gRPC authors.
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
23 * Base class for generated client stubs. Stub methods are expected to call
24 * _simpleRequest or _streamRequest and return the result.
29 private $hostname_override;
31 private $call_invoker;
33 // a callback function
34 private $update_metadata;
37 * @param string $hostname
39 * - 'update_metadata': (optional) a callback function which takes in a
40 * metadata array, and returns an updated metadata array
41 * - 'grpc.primary_user_agent': (optional) a user-agent string
42 * @param Channel|InterceptorChannel $channel An already created Channel or InterceptorChannel object (optional)
44 public function __construct($hostname, $opts, $channel = null)
46 $ssl_roots = file_get_contents(
47 dirname(__FILE__).'/../../../../etc/roots.pem'
49 ChannelCredentials::setDefaultRootsPem($ssl_roots);
51 $this->hostname = $hostname;
52 $this->update_metadata = null;
53 if (isset($opts['update_metadata'])) {
54 if (is_callable($opts['update_metadata'])) {
55 $this->update_metadata = $opts['update_metadata'];
57 unset($opts['update_metadata']);
59 if (!empty($opts['grpc.ssl_target_name_override'])) {
60 $this->hostname_override = $opts['grpc.ssl_target_name_override'];
62 if (isset($opts['grpc_call_invoker'])) {
63 $this->call_invoker = $opts['grpc_call_invoker'];
64 unset($opts['grpc_call_invoker']);
65 $channel_opts = $this->updateOpts($opts);
66 // If the grpc_call_invoker is defined, use the channel created by the call invoker.
67 $this->channel = $this->call_invoker->createChannelFactory($hostname, $channel_opts);
70 $this->call_invoker = new DefaultCallInvoker();
72 if (!is_a($channel, 'Grpc\Channel') &&
73 !is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
74 throw new \Exception('The channel argument is not a Channel object '.
75 'or an InterceptorChannel object created by '.
76 'Interceptor::intercept($channel, Interceptor|Interceptor[] $interceptors)');
78 $this->channel = $channel;
82 $this->channel = static::getDefaultChannel($hostname, $opts);
85 private static function updateOpts($opts) {
86 if (!file_exists($composerFile = __DIR__.'/../../composer.json')) {
87 // for grpc/grpc-php subpackage
88 $composerFile = __DIR__.'/../composer.json';
90 $package_config = json_decode(file_get_contents($composerFile), true);
91 if (!empty($opts['grpc.primary_user_agent'])) {
92 $opts['grpc.primary_user_agent'] .= ' ';
94 $opts['grpc.primary_user_agent'] = '';
96 $opts['grpc.primary_user_agent'] .=
97 'grpc-php/'.$package_config['version'];
98 if (!array_key_exists('credentials', $opts)) {
99 throw new \Exception("The opts['credentials'] key is now ".
100 'required. Please see one of the '.
101 'ChannelCredentials::create methods');
107 * Creates and returns the default Channel
109 * @param array $opts Channel constructor options
111 * @return Channel The channel
113 public static function getDefaultChannel($hostname, array $opts)
115 $channel_opts = self::updateOpts($opts);
116 return new Channel($hostname, $opts);
120 * @return string The URI of the endpoint
122 public function getTarget()
124 return $this->channel->getTarget();
128 * @param bool $try_to_connect (optional)
130 * @return int The grpc connectivity state
132 public function getConnectivityState($try_to_connect = false)
134 return $this->channel->getConnectivityState($try_to_connect);
138 * @param int $timeout in microseconds
140 * @return bool true if channel is ready
141 * @throw Exception if channel is in FATAL_ERROR state
143 public function waitForReady($timeout)
145 $new_state = $this->getConnectivityState(true);
146 if ($this->_checkConnectivityState($new_state)) {
150 $now = Timeval::now();
151 $delta = new Timeval($timeout);
152 $deadline = $now->add($delta);
154 while ($this->channel->watchConnectivityState($new_state, $deadline)) {
155 // state has changed before deadline
156 $new_state = $this->getConnectivityState();
157 if ($this->_checkConnectivityState($new_state)) {
161 // deadline has passed
162 $new_state = $this->getConnectivityState();
164 return $this->_checkConnectivityState($new_state);
168 * Close the communication channel associated with this stub.
170 public function close()
172 $this->channel->close();
176 * @param $new_state Connect state
178 * @return bool true if state is CHANNEL_READY
179 * @throw Exception if state is CHANNEL_FATAL_FAILURE
181 private function _checkConnectivityState($new_state)
183 if ($new_state == \Grpc\CHANNEL_READY) {
186 if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) {
187 throw new \Exception('Failed to connect to server');
194 * constructs the auth uri for the jwt.
196 * @param string $method The method string
198 * @return string The URL string
200 private function _get_jwt_aud_uri($method)
202 // TODO(jtattermusch): This is not the correct implementation
203 // of extracting JWT "aud" claim. We should rely on
204 // grpc_metadata_credentials_plugin which
205 // also provides the correct value of "aud" claim
206 // in the grpc_auth_metadata_context.service_url field.
207 // Trying to do the construction of "aud" field ourselves
209 $last_slash_idx = strrpos($method, '/');
210 if ($last_slash_idx === false) {
211 throw new \InvalidArgumentException(
212 'service name must have a slash'
215 $service_name = substr($method, 0, $last_slash_idx);
217 if ($this->hostname_override) {
218 $hostname = $this->hostname_override;
220 $hostname = $this->hostname;
223 // Remove the port if it is 443
224 // See https://github.com/grpc/grpc/blob/07c9f7a36b2a0d34fcffebc85649cf3b8c339b5d/src/core/lib/security/transport/client_auth_filter.cc#L205
225 if ((strlen($hostname) > 4) && (substr($hostname, -4) === ":443")) {
226 $hostname = substr($hostname, 0, -4);
229 return 'https://'.$hostname.$service_name;
233 * validate and normalize the metadata array.
235 * @param array $metadata The metadata map
237 * @return array $metadata Validated and key-normalized metadata map
238 * @throw InvalidArgumentException if key contains invalid characters
240 private function _validate_and_normalize_metadata($metadata)
243 foreach ($metadata as $key => $value) {
244 if (!preg_match('/^[.A-Za-z\d_-]+$/', $key)) {
245 throw new \InvalidArgumentException(
246 'Metadata keys must be nonempty strings containing only '.
247 'alphanumeric characters, hyphens, underscores and dots'
250 $metadata_copy[strtolower($key)] = $value;
253 return $metadata_copy;
257 * Create a function which can be used to create UnaryCall
259 * @param Channel|InterceptorChannel $channel
260 * @param callable $deserialize A function that deserializes the response
264 private function _GrpcUnaryUnary($channel)
266 return function ($method,
269 array $metadata = [],
270 array $options = []) use ($channel) {
271 $call = $this->call_invoker->UnaryCall(
277 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
278 if (is_callable($this->update_metadata)) {
279 $metadata = call_user_func(
280 $this->update_metadata,
285 $metadata = $this->_validate_and_normalize_metadata(
288 $call->start($argument, $metadata, $options);
294 * Create a function which can be used to create ServerStreamingCall
296 * @param Channel|InterceptorChannel $channel
297 * @param callable $deserialize A function that deserializes the response
301 private function _GrpcStreamUnary($channel)
303 return function ($method,
305 array $metadata = [],
306 array $options = []) use ($channel) {
307 $call = $this->call_invoker->ClientStreamingCall(
313 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
314 if (is_callable($this->update_metadata)) {
315 $metadata = call_user_func(
316 $this->update_metadata,
321 $metadata = $this->_validate_and_normalize_metadata(
324 $call->start($metadata);
330 * Create a function which can be used to create ClientStreamingCall
332 * @param Channel|InterceptorChannel $channel
333 * @param callable $deserialize A function that deserializes the response
337 private function _GrpcUnaryStream($channel)
339 return function ($method,
342 array $metadata = [],
343 array $options = []) use ($channel) {
344 $call = $this->call_invoker->ServerStreamingCall(
350 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
351 if (is_callable($this->update_metadata)) {
352 $metadata = call_user_func(
353 $this->update_metadata,
358 $metadata = $this->_validate_and_normalize_metadata(
361 $call->start($argument, $metadata, $options);
367 * Create a function which can be used to create BidiStreamingCall
369 * @param Channel|InterceptorChannel $channel
370 * @param callable $deserialize A function that deserializes the response
374 private function _GrpcStreamStream($channel)
376 return function ($method,
378 array $metadata = [],
379 array $options = []) use ($channel) {
380 $call = $this->call_invoker->BidiStreamingCall(
386 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
387 if (is_callable($this->update_metadata)) {
388 $metadata = call_user_func(
389 $this->update_metadata,
394 $metadata = $this->_validate_and_normalize_metadata(
397 $call->start($metadata);
404 * Create a function which can be used to create UnaryCall
406 * @param Channel|InterceptorChannel $channel
407 * @param callable $deserialize A function that deserializes the response
411 private function _UnaryUnaryCallFactory($channel)
413 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
414 return function ($method,
417 array $metadata = [],
418 array $options = []) use ($channel) {
419 return $channel->getInterceptor()->interceptUnaryUnary(
425 $this->_UnaryUnaryCallFactory($channel->getNext())
429 return $this->_GrpcUnaryUnary($channel);
433 * Create a function which can be used to create ServerStreamingCall
435 * @param Channel|InterceptorChannel $channel
436 * @param callable $deserialize A function that deserializes the response
440 private function _UnaryStreamCallFactory($channel)
442 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
443 return function ($method,
446 array $metadata = [],
447 array $options = []) use ($channel) {
448 return $channel->getInterceptor()->interceptUnaryStream(
454 $this->_UnaryStreamCallFactory($channel->getNext())
458 return $this->_GrpcUnaryStream($channel);
462 * Create a function which can be used to create ClientStreamingCall
464 * @param Channel|InterceptorChannel $channel
465 * @param callable $deserialize A function that deserializes the response
469 private function _StreamUnaryCallFactory($channel)
471 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
472 return function ($method,
474 array $metadata = [],
475 array $options = []) use ($channel) {
476 return $channel->getInterceptor()->interceptStreamUnary(
481 $this->_StreamUnaryCallFactory($channel->getNext())
485 return $this->_GrpcStreamUnary($channel);
489 * Create a function which can be used to create BidiStreamingCall
491 * @param Channel|InterceptorChannel $channel
492 * @param callable $deserialize A function that deserializes the response
496 private function _StreamStreamCallFactory($channel)
498 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
499 return function ($method,
501 array $metadata = [],
502 array $options = []) use ($channel) {
503 return $channel->getInterceptor()->interceptStreamStream(
508 $this->_StreamStreamCallFactory($channel->getNext())
512 return $this->_GrpcStreamStream($channel);
515 /* This class is intended to be subclassed by generated code, so
516 * all functions begin with "_" to avoid name collisions. */
518 * Call a remote method that takes a single argument and has a
521 * @param string $method The name of the method to call
522 * @param mixed $argument The argument to the method
523 * @param callable $deserialize A function that deserializes the response
524 * @param array $metadata A metadata map to send to the server
526 * @param array $options An array of options (optional)
528 * @return UnaryCall The active call object
530 protected function _simpleRequest(
534 array $metadata = [],
537 $call_factory = $this->_UnaryUnaryCallFactory($this->channel);
538 $call = $call_factory($method, $argument, $deserialize, $metadata, $options);
543 * Call a remote method that takes a stream of arguments and has a single
546 * @param string $method The name of the method to call
547 * @param callable $deserialize A function that deserializes the response
548 * @param array $metadata A metadata map to send to the server
550 * @param array $options An array of options (optional)
552 * @return ClientStreamingCall The active call object
554 protected function _clientStreamRequest(
557 array $metadata = [],
560 $call_factory = $this->_StreamUnaryCallFactory($this->channel);
561 $call = $call_factory($method, $deserialize, $metadata, $options);
566 * Call a remote method that takes a single argument and returns a stream
569 * @param string $method The name of the method to call
570 * @param mixed $argument The argument to the method
571 * @param callable $deserialize A function that deserializes the responses
572 * @param array $metadata A metadata map to send to the server
574 * @param array $options An array of options (optional)
576 * @return ServerStreamingCall The active call object
578 protected function _serverStreamRequest(
582 array $metadata = [],
585 $call_factory = $this->_UnaryStreamCallFactory($this->channel);
586 $call = $call_factory($method, $argument, $deserialize, $metadata, $options);
591 * Call a remote method with messages streaming in both directions.
593 * @param string $method The name of the method to call
594 * @param callable $deserialize A function that deserializes the responses
595 * @param array $metadata A metadata map to send to the server
597 * @param array $options An array of options (optional)
599 * @return BidiStreamingCall The active call object
601 protected function _bidiRequest(
604 array $metadata = [],
607 $call_factory = $this->_StreamStreamCallFactory($this->channel);
608 $call = $call_factory($method, $deserialize, $metadata, $options);