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 if (!method_exists('ChannelCredentials', 'isDefaultRootsPemSet') ||
47 !ChannelCredentials::isDefaultRootsPemSet()) {
48 $ssl_roots = file_get_contents(
49 dirname(__FILE__).'/../../../../etc/roots.pem'
51 ChannelCredentials::setDefaultRootsPem($ssl_roots);
54 $this->hostname = $hostname;
55 $this->update_metadata = null;
56 if (isset($opts['update_metadata'])) {
57 if (is_callable($opts['update_metadata'])) {
58 $this->update_metadata = $opts['update_metadata'];
60 unset($opts['update_metadata']);
62 if (!empty($opts['grpc.ssl_target_name_override'])) {
63 $this->hostname_override = $opts['grpc.ssl_target_name_override'];
65 if (isset($opts['grpc_call_invoker'])) {
66 $this->call_invoker = $opts['grpc_call_invoker'];
67 unset($opts['grpc_call_invoker']);
68 $channel_opts = $this->updateOpts($opts);
69 // If the grpc_call_invoker is defined, use the channel created by the call invoker.
70 $this->channel = $this->call_invoker->createChannelFactory($hostname, $channel_opts);
73 $this->call_invoker = new DefaultCallInvoker();
75 if (!is_a($channel, 'Grpc\Channel') &&
76 !is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
77 throw new \Exception('The channel argument is not a Channel object '.
78 'or an InterceptorChannel object created by '.
79 'Interceptor::intercept($channel, Interceptor|Interceptor[] $interceptors)');
81 $this->channel = $channel;
85 $this->channel = static::getDefaultChannel($hostname, $opts);
88 private static function updateOpts($opts) {
89 if (!file_exists($composerFile = __DIR__.'/../../composer.json')) {
90 // for grpc/grpc-php subpackage
91 $composerFile = __DIR__.'/../composer.json';
93 $package_config = json_decode(file_get_contents($composerFile), true);
94 if (!empty($opts['grpc.primary_user_agent'])) {
95 $opts['grpc.primary_user_agent'] .= ' ';
97 $opts['grpc.primary_user_agent'] = '';
99 $opts['grpc.primary_user_agent'] .=
100 'grpc-php/'.$package_config['version'];
101 if (!array_key_exists('credentials', $opts)) {
102 throw new \Exception("The opts['credentials'] key is now ".
103 'required. Please see one of the '.
104 'ChannelCredentials::create methods');
110 * Creates and returns the default Channel
112 * @param array $opts Channel constructor options
114 * @return Channel The channel
116 public static function getDefaultChannel($hostname, array $opts)
118 $channel_opts = self::updateOpts($opts);
119 return new Channel($hostname, $opts);
123 * @return string The URI of the endpoint
125 public function getTarget()
127 return $this->channel->getTarget();
131 * @param bool $try_to_connect (optional)
133 * @return int The grpc connectivity state
135 public function getConnectivityState($try_to_connect = false)
137 return $this->channel->getConnectivityState($try_to_connect);
141 * @param int $timeout in microseconds
143 * @return bool true if channel is ready
144 * @throw Exception if channel is in FATAL_ERROR state
146 public function waitForReady($timeout)
148 $new_state = $this->getConnectivityState(true);
149 if ($this->_checkConnectivityState($new_state)) {
153 $now = Timeval::now();
154 $delta = new Timeval($timeout);
155 $deadline = $now->add($delta);
157 while ($this->channel->watchConnectivityState($new_state, $deadline)) {
158 // state has changed before deadline
159 $new_state = $this->getConnectivityState();
160 if ($this->_checkConnectivityState($new_state)) {
164 // deadline has passed
165 $new_state = $this->getConnectivityState();
167 return $this->_checkConnectivityState($new_state);
171 * Close the communication channel associated with this stub.
173 public function close()
175 $this->channel->close();
179 * @param $new_state Connect state
181 * @return bool true if state is CHANNEL_READY
182 * @throw Exception if state is CHANNEL_FATAL_FAILURE
184 private function _checkConnectivityState($new_state)
186 if ($new_state == \Grpc\CHANNEL_READY) {
189 if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) {
190 throw new \Exception('Failed to connect to server');
197 * constructs the auth uri for the jwt.
199 * @param string $method The method string
201 * @return string The URL string
203 private function _get_jwt_aud_uri($method)
205 // TODO(jtattermusch): This is not the correct implementation
206 // of extracting JWT "aud" claim. We should rely on
207 // grpc_metadata_credentials_plugin which
208 // also provides the correct value of "aud" claim
209 // in the grpc_auth_metadata_context.service_url field.
210 // Trying to do the construction of "aud" field ourselves
212 $last_slash_idx = strrpos($method, '/');
213 if ($last_slash_idx === false) {
214 throw new \InvalidArgumentException(
215 'service name must have a slash'
218 $service_name = substr($method, 0, $last_slash_idx);
220 if ($this->hostname_override) {
221 $hostname = $this->hostname_override;
223 $hostname = $this->hostname;
226 // Remove the port if it is 443
227 // See https://github.com/grpc/grpc/blob/07c9f7a36b2a0d34fcffebc85649cf3b8c339b5d/src/core/lib/security/transport/client_auth_filter.cc#L205
228 if ((strlen($hostname) > 4) && (substr($hostname, -4) === ":443")) {
229 $hostname = substr($hostname, 0, -4);
232 return 'https://'.$hostname.$service_name;
236 * validate and normalize the metadata array.
238 * @param array $metadata The metadata map
240 * @return array $metadata Validated and key-normalized metadata map
241 * @throw InvalidArgumentException if key contains invalid characters
243 private function _validate_and_normalize_metadata($metadata)
246 foreach ($metadata as $key => $value) {
247 if (!preg_match('/^[.A-Za-z\d_-]+$/', $key)) {
248 throw new \InvalidArgumentException(
249 'Metadata keys must be nonempty strings containing only '.
250 'alphanumeric characters, hyphens, underscores and dots'
253 $metadata_copy[strtolower($key)] = $value;
256 return $metadata_copy;
260 * Create a function which can be used to create UnaryCall
262 * @param Channel|InterceptorChannel $channel
263 * @param callable $deserialize A function that deserializes the response
267 private function _GrpcUnaryUnary($channel)
269 return function ($method,
272 array $metadata = [],
273 array $options = []) use ($channel) {
274 $call = $this->call_invoker->UnaryCall(
280 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
281 if (is_callable($this->update_metadata)) {
282 $metadata = call_user_func(
283 $this->update_metadata,
288 $metadata = $this->_validate_and_normalize_metadata(
291 $call->start($argument, $metadata, $options);
297 * Create a function which can be used to create ServerStreamingCall
299 * @param Channel|InterceptorChannel $channel
300 * @param callable $deserialize A function that deserializes the response
304 private function _GrpcStreamUnary($channel)
306 return function ($method,
308 array $metadata = [],
309 array $options = []) use ($channel) {
310 $call = $this->call_invoker->ClientStreamingCall(
316 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
317 if (is_callable($this->update_metadata)) {
318 $metadata = call_user_func(
319 $this->update_metadata,
324 $metadata = $this->_validate_and_normalize_metadata(
327 $call->start($metadata);
333 * Create a function which can be used to create ClientStreamingCall
335 * @param Channel|InterceptorChannel $channel
336 * @param callable $deserialize A function that deserializes the response
340 private function _GrpcUnaryStream($channel)
342 return function ($method,
345 array $metadata = [],
346 array $options = []) use ($channel) {
347 $call = $this->call_invoker->ServerStreamingCall(
353 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
354 if (is_callable($this->update_metadata)) {
355 $metadata = call_user_func(
356 $this->update_metadata,
361 $metadata = $this->_validate_and_normalize_metadata(
364 $call->start($argument, $metadata, $options);
370 * Create a function which can be used to create BidiStreamingCall
372 * @param Channel|InterceptorChannel $channel
373 * @param callable $deserialize A function that deserializes the response
377 private function _GrpcStreamStream($channel)
379 return function ($method,
381 array $metadata = [],
382 array $options = []) use ($channel) {
383 $call = $this->call_invoker->BidiStreamingCall(
389 $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
390 if (is_callable($this->update_metadata)) {
391 $metadata = call_user_func(
392 $this->update_metadata,
397 $metadata = $this->_validate_and_normalize_metadata(
400 $call->start($metadata);
407 * Create a function which can be used to create UnaryCall
409 * @param Channel|InterceptorChannel $channel
410 * @param callable $deserialize A function that deserializes the response
414 private function _UnaryUnaryCallFactory($channel)
416 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
417 return function ($method,
420 array $metadata = [],
421 array $options = []) use ($channel) {
422 return $channel->getInterceptor()->interceptUnaryUnary(
426 $this->_UnaryUnaryCallFactory($channel->getNext()),
432 return $this->_GrpcUnaryUnary($channel);
436 * Create a function which can be used to create ServerStreamingCall
438 * @param Channel|InterceptorChannel $channel
439 * @param callable $deserialize A function that deserializes the response
443 private function _UnaryStreamCallFactory($channel)
445 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
446 return function ($method,
449 array $metadata = [],
450 array $options = []) use ($channel) {
451 return $channel->getInterceptor()->interceptUnaryStream(
455 $this->_UnaryStreamCallFactory($channel->getNext()),
461 return $this->_GrpcUnaryStream($channel);
465 * Create a function which can be used to create ClientStreamingCall
467 * @param Channel|InterceptorChannel $channel
468 * @param callable $deserialize A function that deserializes the response
472 private function _StreamUnaryCallFactory($channel)
474 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
475 return function ($method,
477 array $metadata = [],
478 array $options = []) use ($channel) {
479 return $channel->getInterceptor()->interceptStreamUnary(
482 $this->_StreamUnaryCallFactory($channel->getNext()),
488 return $this->_GrpcStreamUnary($channel);
492 * Create a function which can be used to create BidiStreamingCall
494 * @param Channel|InterceptorChannel $channel
495 * @param callable $deserialize A function that deserializes the response
499 private function _StreamStreamCallFactory($channel)
501 if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) {
502 return function ($method,
504 array $metadata = [],
505 array $options = []) use ($channel) {
506 return $channel->getInterceptor()->interceptStreamStream(
509 $this->_StreamStreamCallFactory($channel->getNext()),
515 return $this->_GrpcStreamStream($channel);
518 /* This class is intended to be subclassed by generated code, so
519 * all functions begin with "_" to avoid name collisions. */
521 * Call a remote method that takes a single argument and has a
524 * @param string $method The name of the method to call
525 * @param mixed $argument The argument to the method
526 * @param callable $deserialize A function that deserializes the response
527 * @param array $metadata A metadata map to send to the server
529 * @param array $options An array of options (optional)
531 * @return UnaryCall The active call object
533 protected function _simpleRequest(
537 array $metadata = [],
540 $call_factory = $this->_UnaryUnaryCallFactory($this->channel);
541 $call = $call_factory($method, $argument, $deserialize, $metadata, $options);
546 * Call a remote method that takes a stream of arguments and has a single
549 * @param string $method The name of the method to call
550 * @param callable $deserialize A function that deserializes the response
551 * @param array $metadata A metadata map to send to the server
553 * @param array $options An array of options (optional)
555 * @return ClientStreamingCall The active call object
557 protected function _clientStreamRequest(
560 array $metadata = [],
563 $call_factory = $this->_StreamUnaryCallFactory($this->channel);
564 $call = $call_factory($method, $deserialize, $metadata, $options);
569 * Call a remote method that takes a single argument and returns a stream
572 * @param string $method The name of the method to call
573 * @param mixed $argument The argument to the method
574 * @param callable $deserialize A function that deserializes the responses
575 * @param array $metadata A metadata map to send to the server
577 * @param array $options An array of options (optional)
579 * @return ServerStreamingCall The active call object
581 protected function _serverStreamRequest(
585 array $metadata = [],
588 $call_factory = $this->_UnaryStreamCallFactory($this->channel);
589 $call = $call_factory($method, $argument, $deserialize, $metadata, $options);
594 * Call a remote method with messages streaming in both directions.
596 * @param string $method The name of the method to call
597 * @param callable $deserialize A function that deserializes the responses
598 * @param array $metadata A metadata map to send to the server
600 * @param array $options An array of options (optional)
602 * @return BidiStreamingCall The active call object
604 protected function _bidiRequest(
607 array $metadata = [],
610 $call_factory = $this->_StreamStreamCallFactory($this->channel);
611 $call = $call_factory($method, $deserialize, $metadata, $options);