1: <?php
2: /*
3: *
4: * Copyright 2015, Google Inc.
5: * All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions are
9: * met:
10: *
11: * * Redistributions of source code must retain the above copyright
12: * notice, this list of conditions and the following disclaimer.
13: * * Redistributions in binary form must reproduce the above
14: * copyright notice, this list of conditions and the following disclaimer
15: * in the documentation and/or other materials provided with the
16: * distribution.
17: * * Neither the name of Google Inc. nor the names of its
18: * contributors may be used to endorse or promote products derived from
19: * this software without specific prior written permission.
20: *
21: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32: *
33: */
34:
35: namespace Grpc;
36:
37: /**
38: * Base class for generated client stubs. Stub methods are expected to call
39: * _simpleRequest or _streamRequest and return the result.
40: */
41: class BaseStub
42: {
43: private $hostname;
44: private $hostname_override;
45: private $channel;
46:
47: // a callback function
48: private $update_metadata;
49:
50: /**
51: * @param string $hostname
52: * @param array $opts
53: * - 'update_metadata': (optional) a callback function which takes in a
54: * metadata array, and returns an updated metadata array
55: * - 'grpc.primary_user_agent': (optional) a user-agent string
56: * @param Channel $channel An already created Channel object (optional)
57: */
58: public function __construct($hostname, $opts, Channel $channel = null)
59: {
60: $ssl_roots = file_get_contents(
61: dirname(__FILE__).'/../../../../etc/roots.pem');
62: ChannelCredentials::setDefaultRootsPem($ssl_roots);
63:
64: $this->hostname = $hostname;
65: $this->update_metadata = null;
66: if (isset($opts['update_metadata'])) {
67: if (is_callable($opts['update_metadata'])) {
68: $this->update_metadata = $opts['update_metadata'];
69: }
70: unset($opts['update_metadata']);
71: }
72: $package_config = json_decode(
73: file_get_contents(dirname(__FILE__).'/../../composer.json'), true);
74: if (!empty($opts['grpc.primary_user_agent'])) {
75: $opts['grpc.primary_user_agent'] .= ' ';
76: } else {
77: $opts['grpc.primary_user_agent'] = '';
78: }
79: if (!empty($opts['grpc.ssl_target_name_override'])) {
80: $this->hostname_override = $opts['grpc.ssl_target_name_override'];
81: }
82: $opts['grpc.primary_user_agent'] .=
83: 'grpc-php/'.$package_config['version'];
84: if (!array_key_exists('credentials', $opts)) {
85: throw new \Exception("The opts['credentials'] key is now ".
86: 'required. Please see one of the '.
87: 'ChannelCredentials::create methods');
88: }
89: if ($channel) {
90: if (!is_a($channel, 'Channel')) {
91: throw new \Exception('The channel argument is not a'.
92: 'Channel object');
93: }
94: $this->channel = $channel;
95: } else {
96: $this->channel = new Channel($hostname, $opts);
97: }
98: }
99:
100: /**
101: * @return string The URI of the endpoint
102: */
103: public function getTarget()
104: {
105: return $this->channel->getTarget();
106: }
107:
108: /**
109: * @param bool $try_to_connect (optional)
110: *
111: * @return int The grpc connectivity state
112: */
113: public function getConnectivityState($try_to_connect = false)
114: {
115: return $this->channel->getConnectivityState($try_to_connect);
116: }
117:
118: /**
119: * @param int $timeout in microseconds
120: *
121: * @return bool true if channel is ready
122: * @throw Exception if channel is in FATAL_ERROR state
123: */
124: public function waitForReady($timeout)
125: {
126: $new_state = $this->getConnectivityState(true);
127: if ($this->_checkConnectivityState($new_state)) {
128: return true;
129: }
130:
131: $now = Timeval::now();
132: $delta = new Timeval($timeout);
133: $deadline = $now->add($delta);
134:
135: while ($this->channel->watchConnectivityState($new_state, $deadline)) {
136: // state has changed before deadline
137: $new_state = $this->getConnectivityState();
138: if ($this->_checkConnectivityState($new_state)) {
139: return true;
140: }
141: }
142: // deadline has passed
143: $new_state = $this->getConnectivityState();
144:
145: return $this->_checkConnectivityState($new_state);
146: }
147:
148: /**
149: * Close the communication channel associated with this stub.
150: */
151: public function close()
152: {
153: $this->channel->close();
154: }
155:
156: /**
157: * @param $new_state Connect state
158: *
159: * @return bool true if state is CHANNEL_READY
160: * @throw Exception if state is CHANNEL_FATAL_FAILURE
161: */
162: private function _checkConnectivityState($new_state)
163: {
164: if ($new_state == \Grpc\CHANNEL_READY) {
165: return true;
166: }
167: if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) {
168: throw new \Exception('Failed to connect to server');
169: }
170:
171: return false;
172: }
173:
174: /**
175: * constructs the auth uri for the jwt.
176: *
177: * @param string $method The method string
178: *
179: * @return string The URL string
180: */
181: private function _get_jwt_aud_uri($method)
182: {
183: $last_slash_idx = strrpos($method, '/');
184: if ($last_slash_idx === false) {
185: throw new \InvalidArgumentException(
186: 'service name must have a slash');
187: }
188: $service_name = substr($method, 0, $last_slash_idx);
189:
190: if ($this->hostname_override) {
191: $hostname = $this->hostname_override;
192: } else {
193: $hostname = $this->hostname;
194: }
195:
196: return 'https://'.$hostname.$service_name;
197: }
198:
199: /**
200: * validate and normalize the metadata array.
201: *
202: * @param array $metadata The metadata map
203: *
204: * @return array $metadata Validated and key-normalized metadata map
205: * @throw InvalidArgumentException if key contains invalid characters
206: */
207: private function _validate_and_normalize_metadata($metadata)
208: {
209: $metadata_copy = [];
210: foreach ($metadata as $key => $value) {
211: if (!preg_match('/^[A-Za-z\d_-]+$/', $key)) {
212: throw new \InvalidArgumentException(
213: 'Metadata keys must be nonempty strings containing only '.
214: 'alphanumeric characters, hyphens and underscores');
215: }
216: $metadata_copy[strtolower($key)] = $value;
217: }
218:
219: return $metadata_copy;
220: }
221:
222: /* This class is intended to be subclassed by generated code, so
223: * all functions begin with "_" to avoid name collisions. */
224:
225: /**
226: * Call a remote method that takes a single argument and has a
227: * single output.
228: *
229: * @param string $method The name of the method to call
230: * @param mixed $argument The argument to the method
231: * @param callable $deserialize A function that deserializes the response
232: * @param array $metadata A metadata map to send to the server
233: * (optional)
234: * @param array $options An array of options (optional)
235: *
236: * @return SimpleSurfaceActiveCall The active call object
237: */
238: protected function _simpleRequest($method,
239: $argument,
240: $deserialize,
241: array $metadata = [],
242: array $options = [])
243: {
244: $call = new UnaryCall($this->channel,
245: $method,
246: $deserialize,
247: $options);
248: $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
249: if (is_callable($this->update_metadata)) {
250: $metadata = call_user_func($this->update_metadata,
251: $metadata,
252: $jwt_aud_uri);
253: }
254: $metadata = $this->_validate_and_normalize_metadata(
255: $metadata);
256: $call->start($argument, $metadata, $options);
257:
258: return $call;
259: }
260:
261: /**
262: * Call a remote method that takes a stream of arguments and has a single
263: * output.
264: *
265: * @param string $method The name of the method to call
266: * @param callable $deserialize A function that deserializes the response
267: * @param array $metadata A metadata map to send to the server
268: * (optional)
269: * @param array $options An array of options (optional)
270: *
271: * @return ClientStreamingSurfaceActiveCall The active call object
272: */
273: protected function _clientStreamRequest($method,
274: $deserialize,
275: array $metadata = [],
276: array $options = [])
277: {
278: $call = new ClientStreamingCall($this->channel,
279: $method,
280: $deserialize,
281: $options);
282: $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
283: if (is_callable($this->update_metadata)) {
284: $metadata = call_user_func($this->update_metadata,
285: $metadata,
286: $jwt_aud_uri);
287: }
288: $metadata = $this->_validate_and_normalize_metadata(
289: $metadata);
290: $call->start($metadata);
291:
292: return $call;
293: }
294:
295: /**
296: * Call a remote method that takes a single argument and returns a stream
297: * of responses.
298: *
299: * @param string $method The name of the method to call
300: * @param mixed $argument The argument to the method
301: * @param callable $deserialize A function that deserializes the responses
302: * @param array $metadata A metadata map to send to the server
303: * (optional)
304: * @param array $options An array of options (optional)
305: *
306: * @return ServerStreamingSurfaceActiveCall The active call object
307: */
308: protected function _serverStreamRequest($method,
309: $argument,
310: $deserialize,
311: array $metadata = [],
312: array $options = [])
313: {
314: $call = new ServerStreamingCall($this->channel,
315: $method,
316: $deserialize,
317: $options);
318: $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
319: if (is_callable($this->update_metadata)) {
320: $metadata = call_user_func($this->update_metadata,
321: $metadata,
322: $jwt_aud_uri);
323: }
324: $metadata = $this->_validate_and_normalize_metadata(
325: $metadata);
326: $call->start($argument, $metadata, $options);
327:
328: return $call;
329: }
330:
331: /**
332: * Call a remote method with messages streaming in both directions.
333: *
334: * @param string $method The name of the method to call
335: * @param callable $deserialize A function that deserializes the responses
336: * @param array $metadata A metadata map to send to the server
337: * (optional)
338: * @param array $options An array of options (optional)
339: *
340: * @return BidiStreamingSurfaceActiveCall The active call object
341: */
342: protected function _bidiRequest($method,
343: $deserialize,
344: array $metadata = [],
345: array $options = [])
346: {
347: $call = new BidiStreamingCall($this->channel,
348: $method,
349: $deserialize,
350: $options);
351: $jwt_aud_uri = $this->_get_jwt_aud_uri($method);
352: if (is_callable($this->update_metadata)) {
353: $metadata = call_user_func($this->update_metadata,
354: $metadata,
355: $jwt_aud_uri);
356: }
357: $metadata = $this->_validate_and_normalize_metadata(
358: $metadata);
359: $call->start($metadata);
360:
361: return $call;
362: }
363: }
364: