| <?php | |
| /* | |
| * | |
| * Copyright 2015 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; | |
| /** | |
| * Base class for generated client stubs. Stub methods are expected to call | |
| * _simpleRequest or _streamRequest and return the result. | |
| */ | |
| class BaseStub | |
| { | |
| private $hostname; | |
| private $hostname_override; | |
| private $channel; | |
| private $call_invoker; | |
| // a callback function | |
| private $update_metadata; | |
| /** | |
| * @param string $hostname | |
| * @param array $opts | |
| * - 'update_metadata': (optional) a callback function which takes in a | |
| * metadata array, and returns an updated metadata array | |
| * - 'grpc.primary_user_agent': (optional) a user-agent string | |
| * @param Channel|InterceptorChannel $channel An already created Channel or InterceptorChannel object (optional) | |
| */ | |
| public function __construct($hostname, $opts, $channel = null) | |
| { | |
| if (!method_exists('Grpc\ChannelCredentials', 'isDefaultRootsPemSet') || | |
| !ChannelCredentials::isDefaultRootsPemSet()) { | |
| $ssl_roots = file_get_contents( | |
| dirname(__FILE__).'/../../../../etc/roots.pem' | |
| ); | |
| ChannelCredentials::setDefaultRootsPem($ssl_roots); | |
| } | |
| $this->hostname = $hostname; | |
| $this->update_metadata = null; | |
| if (isset($opts['update_metadata'])) { | |
| if (is_callable($opts['update_metadata'])) { | |
| $this->update_metadata = $opts['update_metadata']; | |
| } | |
| unset($opts['update_metadata']); | |
| } | |
| if (!empty($opts['grpc.ssl_target_name_override'])) { | |
| $this->hostname_override = $opts['grpc.ssl_target_name_override']; | |
| } | |
| if (isset($opts['grpc_call_invoker'])) { | |
| $this->call_invoker = $opts['grpc_call_invoker']; | |
| unset($opts['grpc_call_invoker']); | |
| $channel_opts = $this->updateOpts($opts); | |
| // If the grpc_call_invoker is defined, use the channel created by the call invoker. | |
| $this->channel = $this->call_invoker->createChannelFactory($hostname, $channel_opts); | |
| return; | |
| } | |
| $this->call_invoker = new DefaultCallInvoker(); | |
| if ($channel) { | |
| if (!is_a($channel, 'Grpc\Channel') && | |
| !is_a($channel, 'Grpc\Internal\InterceptorChannel')) { | |
| throw new \Exception('The channel argument is not a Channel object '. | |
| 'or an InterceptorChannel object created by '. | |
| 'Interceptor::intercept($channel, Interceptor|Interceptor[] $interceptors)'); | |
| } | |
| $this->channel = $channel; | |
| return; | |
| } | |
| $this->channel = static::getDefaultChannel($hostname, $opts); | |
| } | |
| private static function updateOpts($opts) { | |
| if (!empty($opts['grpc.primary_user_agent'])) { | |
| $opts['grpc.primary_user_agent'] .= ' '; | |
| } else { | |
| $opts['grpc.primary_user_agent'] = ''; | |
| } | |
| if (defined('\Grpc\VERSION')) { | |
| $version_str = \Grpc\VERSION; | |
| } else { | |
| if (!file_exists($composerFile = __DIR__.'/../../composer.json')) { | |
| // for grpc/grpc-php subpackage | |
| $composerFile = __DIR__.'/../composer.json'; | |
| } | |
| $package_config = json_decode(file_get_contents($composerFile), true); | |
| $version_str = $package_config['version']; | |
| } | |
| $opts['grpc.primary_user_agent'] .= 'grpc-php/'.$version_str; | |
| if (!array_key_exists('credentials', $opts)) { | |
| throw new \Exception("The opts['credentials'] key is now ". | |
| 'required. Please see one of the '. | |
| 'ChannelCredentials::create methods'); | |
| } | |
| return $opts; | |
| } | |
| /** | |
| * Creates and returns the default Channel | |
| * | |
| * @param array $opts Channel constructor options | |
| * | |
| * @return Channel The channel | |
| */ | |
| public static function getDefaultChannel($hostname, array $opts) | |
| { | |
| $channel_opts = self::updateOpts($opts); | |
| return new Channel($hostname, $channel_opts); | |
| } | |
| /** | |
| * @return string The URI of the endpoint | |
| */ | |
| public function getTarget() | |
| { | |
| return $this->channel->getTarget(); | |
| } | |
| /** | |
| * @param bool $try_to_connect (optional) | |
| * | |
| * @return int The grpc connectivity state | |
| */ | |
| public function getConnectivityState($try_to_connect = false) | |
| { | |
| return $this->channel->getConnectivityState($try_to_connect); | |
| } | |
| /** | |
| * @param int $timeout in microseconds | |
| * | |
| * @return bool true if channel is ready | |
| * @throws Exception if channel is in FATAL_ERROR state | |
| */ | |
| public function waitForReady($timeout) | |
| { | |
| $new_state = $this->getConnectivityState(true); | |
| if ($this->_checkConnectivityState($new_state)) { | |
| return true; | |
| } | |
| $now = Timeval::now(); | |
| $delta = new Timeval($timeout); | |
| $deadline = $now->add($delta); | |
| while ($this->channel->watchConnectivityState($new_state, $deadline)) { | |
| // state has changed before deadline | |
| $new_state = $this->getConnectivityState(); | |
| if ($this->_checkConnectivityState($new_state)) { | |
| return true; | |
| } | |
| } | |
| // deadline has passed | |
| $new_state = $this->getConnectivityState(); | |
| return $this->_checkConnectivityState($new_state); | |
| } | |
| /** | |
| * Close the communication channel associated with this stub. | |
| */ | |
| public function close() | |
| { | |
| $this->channel->close(); | |
| } | |
| /** | |
| * @param $new_state Connect state | |
| * | |
| * @return bool true if state is CHANNEL_READY | |
| * @throws Exception if state is CHANNEL_FATAL_FAILURE | |
| */ | |
| private function _checkConnectivityState($new_state) | |
| { | |
| if ($new_state == \Grpc\CHANNEL_READY) { | |
| return true; | |
| } | |
| if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) { | |
| throw new \Exception('Failed to connect to server'); | |
| } | |
| return false; | |
| } | |
| /** | |
| * constructs the auth uri for the jwt. | |
| * | |
| * @param string $method The method string | |
| * | |
| * @return string The URL string | |
| */ | |
| private function _get_jwt_aud_uri($method) | |
| { | |
| // TODO(jtattermusch): This is not the correct implementation | |
| // of extracting JWT "aud" claim. We should rely on | |
| // grpc_metadata_credentials_plugin which | |
| // also provides the correct value of "aud" claim | |
| // in the grpc_auth_metadata_context.service_url field. | |
| // Trying to do the construction of "aud" field ourselves | |
| // is bad. | |
| $last_slash_idx = strrpos($method, '/'); | |
| if ($last_slash_idx === false) { | |
| throw new \InvalidArgumentException( | |
| 'service name must have a slash' | |
| ); | |
| } | |
| $service_name = substr($method, 0, $last_slash_idx); | |
| if ($this->hostname_override) { | |
| $hostname = $this->hostname_override; | |
| } else { | |
| $hostname = $this->hostname; | |
| } | |
| // Remove the port if it is 443 | |
| // See https://github.com/grpc/grpc/blob/07c9f7a36b2a0d34fcffebc85649cf3b8c339b5d/src/core/lib/security/transport/client_auth_filter.cc#L205 | |
| if ((strlen($hostname) > 4) && (substr($hostname, -4) === ":443")) { | |
| $hostname = substr($hostname, 0, -4); | |
| } | |
| return 'https://'.$hostname.$service_name; | |
| } | |
| /** | |
| * validate and normalize the metadata array. | |
| * | |
| * @param array $metadata The metadata map | |
| * | |
| * @return array $metadata Validated and key-normalized metadata map | |
| * @throws InvalidArgumentException if key contains invalid characters | |
| */ | |
| private function _validate_and_normalize_metadata($metadata) | |
| { | |
| $metadata_copy = []; | |
| foreach ($metadata as $key => $value) { | |
| if (!preg_match('/^[.A-Za-z\d_-]+$/', $key)) { | |
| throw new \InvalidArgumentException( | |
| 'Metadata keys must be nonempty strings containing only '. | |
| 'alphanumeric characters, hyphens, underscores and dots' | |
| ); | |
| } | |
| $metadata_copy[strtolower($key)] = $value; | |
| } | |
| return $metadata_copy; | |
| } | |
| /** | |
| * Create a function which can be used to create UnaryCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _GrpcUnaryUnary($channel) | |
| { | |
| return function ($method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| $call = $this->call_invoker->UnaryCall( | |
| $channel, | |
| $method, | |
| $deserialize, | |
| $options | |
| ); | |
| $jwt_aud_uri = $this->_get_jwt_aud_uri($method); | |
| if (is_callable($this->update_metadata)) { | |
| $metadata = call_user_func( | |
| $this->update_metadata, | |
| $metadata, | |
| $jwt_aud_uri | |
| ); | |
| } | |
| $metadata = $this->_validate_and_normalize_metadata( | |
| $metadata | |
| ); | |
| $call->start($argument, $metadata, $options); | |
| return $call; | |
| }; | |
| } | |
| /** | |
| * Create a function which can be used to create ServerStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _GrpcStreamUnary($channel) | |
| { | |
| return function ($method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| $call = $this->call_invoker->ClientStreamingCall( | |
| $channel, | |
| $method, | |
| $deserialize, | |
| $options | |
| ); | |
| $jwt_aud_uri = $this->_get_jwt_aud_uri($method); | |
| if (is_callable($this->update_metadata)) { | |
| $metadata = call_user_func( | |
| $this->update_metadata, | |
| $metadata, | |
| $jwt_aud_uri | |
| ); | |
| } | |
| $metadata = $this->_validate_and_normalize_metadata( | |
| $metadata | |
| ); | |
| $call->start($metadata); | |
| return $call; | |
| }; | |
| } | |
| /** | |
| * Create a function which can be used to create ClientStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _GrpcUnaryStream($channel) | |
| { | |
| return function ($method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| $call = $this->call_invoker->ServerStreamingCall( | |
| $channel, | |
| $method, | |
| $deserialize, | |
| $options | |
| ); | |
| $jwt_aud_uri = $this->_get_jwt_aud_uri($method); | |
| if (is_callable($this->update_metadata)) { | |
| $metadata = call_user_func( | |
| $this->update_metadata, | |
| $metadata, | |
| $jwt_aud_uri | |
| ); | |
| } | |
| $metadata = $this->_validate_and_normalize_metadata( | |
| $metadata | |
| ); | |
| $call->start($argument, $metadata, $options); | |
| return $call; | |
| }; | |
| } | |
| /** | |
| * Create a function which can be used to create BidiStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _GrpcStreamStream($channel) | |
| { | |
| return function ($method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| $call = $this->call_invoker->BidiStreamingCall( | |
| $channel, | |
| $method, | |
| $deserialize, | |
| $options | |
| ); | |
| $jwt_aud_uri = $this->_get_jwt_aud_uri($method); | |
| if (is_callable($this->update_metadata)) { | |
| $metadata = call_user_func( | |
| $this->update_metadata, | |
| $metadata, | |
| $jwt_aud_uri | |
| ); | |
| } | |
| $metadata = $this->_validate_and_normalize_metadata( | |
| $metadata | |
| ); | |
| $call->start($metadata); | |
| return $call; | |
| }; | |
| } | |
| /** | |
| * Create a function which can be used to create UnaryCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _UnaryUnaryCallFactory($channel) | |
| { | |
| if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) { | |
| return function ($method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| return $channel->getInterceptor()->interceptUnaryUnary( | |
| $method, | |
| $argument, | |
| $deserialize, | |
| $this->_UnaryUnaryCallFactory($channel->getNext()), | |
| $metadata, | |
| $options | |
| ); | |
| }; | |
| } | |
| return $this->_GrpcUnaryUnary($channel); | |
| } | |
| /** | |
| * Create a function which can be used to create ServerStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _UnaryStreamCallFactory($channel) | |
| { | |
| if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) { | |
| return function ($method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| return $channel->getInterceptor()->interceptUnaryStream( | |
| $method, | |
| $argument, | |
| $deserialize, | |
| $this->_UnaryStreamCallFactory($channel->getNext()), | |
| $metadata, | |
| $options | |
| ); | |
| }; | |
| } | |
| return $this->_GrpcUnaryStream($channel); | |
| } | |
| /** | |
| * Create a function which can be used to create ClientStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _StreamUnaryCallFactory($channel) | |
| { | |
| if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) { | |
| return function ($method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| return $channel->getInterceptor()->interceptStreamUnary( | |
| $method, | |
| $deserialize, | |
| $this->_StreamUnaryCallFactory($channel->getNext()), | |
| $metadata, | |
| $options | |
| ); | |
| }; | |
| } | |
| return $this->_GrpcStreamUnary($channel); | |
| } | |
| /** | |
| * Create a function which can be used to create BidiStreamingCall | |
| * | |
| * @param Channel|InterceptorChannel $channel | |
| * @param callable $deserialize A function that deserializes the response | |
| * | |
| * @return \Closure | |
| */ | |
| private function _StreamStreamCallFactory($channel) | |
| { | |
| if (is_a($channel, 'Grpc\Internal\InterceptorChannel')) { | |
| return function ($method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = []) use ($channel) { | |
| return $channel->getInterceptor()->interceptStreamStream( | |
| $method, | |
| $deserialize, | |
| $this->_StreamStreamCallFactory($channel->getNext()), | |
| $metadata, | |
| $options | |
| ); | |
| }; | |
| } | |
| return $this->_GrpcStreamStream($channel); | |
| } | |
| /* This class is intended to be subclassed by generated code, so | |
| * all functions begin with "_" to avoid name collisions. */ | |
| /** | |
| * Call a remote method that takes a single argument and has a | |
| * single output. | |
| * | |
| * @param string $method The name of the method to call | |
| * @param mixed $argument The argument to the method | |
| * @param callable $deserialize A function that deserializes the response | |
| * @param array $metadata A metadata map to send to the server | |
| * (optional) | |
| * @param array $options An array of options (optional) | |
| * | |
| * @return UnaryCall The active call object | |
| */ | |
| protected function _simpleRequest( | |
| $method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = [] | |
| ) { | |
| $call_factory = $this->_UnaryUnaryCallFactory($this->channel); | |
| $call = $call_factory($method, $argument, $deserialize, $metadata, $options); | |
| return $call; | |
| } | |
| /** | |
| * Call a remote method that takes a stream of arguments and has a single | |
| * output. | |
| * | |
| * @param string $method The name of the method to call | |
| * @param callable $deserialize A function that deserializes the response | |
| * @param array $metadata A metadata map to send to the server | |
| * (optional) | |
| * @param array $options An array of options (optional) | |
| * | |
| * @return ClientStreamingCall The active call object | |
| */ | |
| protected function _clientStreamRequest( | |
| $method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = [] | |
| ) { | |
| $call_factory = $this->_StreamUnaryCallFactory($this->channel); | |
| $call = $call_factory($method, $deserialize, $metadata, $options); | |
| return $call; | |
| } | |
| /** | |
| * Call a remote method that takes a single argument and returns a stream | |
| * of responses. | |
| * | |
| * @param string $method The name of the method to call | |
| * @param mixed $argument The argument to the method | |
| * @param callable $deserialize A function that deserializes the responses | |
| * @param array $metadata A metadata map to send to the server | |
| * (optional) | |
| * @param array $options An array of options (optional) | |
| * | |
| * @return ServerStreamingCall The active call object | |
| */ | |
| protected function _serverStreamRequest( | |
| $method, | |
| $argument, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = [] | |
| ) { | |
| $call_factory = $this->_UnaryStreamCallFactory($this->channel); | |
| $call = $call_factory($method, $argument, $deserialize, $metadata, $options); | |
| return $call; | |
| } | |
| /** | |
| * Call a remote method with messages streaming in both directions. | |
| * | |
| * @param string $method The name of the method to call | |
| * @param callable $deserialize A function that deserializes the responses | |
| * @param array $metadata A metadata map to send to the server | |
| * (optional) | |
| * @param array $options An array of options (optional) | |
| * | |
| * @return BidiStreamingCall The active call object | |
| */ | |
| protected function _bidiRequest( | |
| $method, | |
| $deserialize, | |
| array $metadata = [], | |
| array $options = [] | |
| ) { | |
| $call_factory = $this->_StreamStreamCallFactory($this->channel); | |
| $call = $call_factory($method, $deserialize, $metadata, $options); | |
| return $call; | |
| } | |
| } |