1 /*
2   +----------------------------------------------------------------------+
3   | Yar - Light, concurrent RPC framework                                |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2012-2013 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author:  Xinchen Hui   <laruence@php.net>                            |
16   |          Zhenyu  Zhang <zhangzhenyu@php.net>                         |
17   +----------------------------------------------------------------------+
18 */
19 
20 /* $Id$ */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "php.h"
27 #include "SAPI.h"
28 #include "Zend/zend_exceptions.h"
29 #include "ext/standard/url.h" /* for php_url */
30 
31 #include "php_yar.h"
32 #include "yar_exception.h"
33 #include "yar_packager.h"
34 #include "yar_transport.h"
35 #include "yar_client.h"
36 #include "yar_request.h"
37 #include "yar_response.h"
38 #include "yar_protocol.h"
39 
40 #include <curl/curl.h> /* For checking CUROPT_RESOLVE */
41 
42 zend_class_entry *yar_client_ce;
43 zend_class_entry *yar_concurrent_client_ce;
44 
45 /* {{{ ARG_INFO */
46 ZEND_BEGIN_ARG_INFO_EX(arginfo_client___construct, 0, 0, 1)
47 	ZEND_ARG_INFO(0, url)
48 	ZEND_ARG_INFO(0, async)
ZEND_END_ARG_INFO()49 ZEND_END_ARG_INFO()
50 ZEND_BEGIN_ARG_INFO_EX(arginfo_client___call, 0, 0, 2)
51 	ZEND_ARG_INFO(0, method)
52 	ZEND_ARG_INFO(0, parameters)
53 ZEND_END_ARG_INFO()
54 ZEND_BEGIN_ARG_INFO_EX(arginfo_client_getopt, 0, 0, 1)
55 	ZEND_ARG_INFO(0, type)
56 ZEND_END_ARG_INFO()
57 ZEND_BEGIN_ARG_INFO_EX(arginfo_client_setopt, 0, 0, 2)
58 	ZEND_ARG_INFO(0, type)
59 	ZEND_ARG_INFO(0, value)
60 ZEND_END_ARG_INFO()
61 ZEND_BEGIN_ARG_INFO_EX(arginfo_client_async, 0, 0, 3)
62 	ZEND_ARG_INFO(0, uri)
63 	ZEND_ARG_INFO(0, method)
64 	ZEND_ARG_INFO(0, parameters)
65 	ZEND_ARG_INFO(0, callback)
66 ZEND_END_ARG_INFO()
67 ZEND_BEGIN_ARG_INFO_EX(arginfo_client_loop, 0, 0, 0)
68 	ZEND_ARG_INFO(0, callback)
69 	ZEND_ARG_INFO(0, error_callback)
70 ZEND_END_ARG_INFO()
71 ZEND_BEGIN_ARG_INFO_EX(arginfo_client_void, 0, 0, 0)
72 ZEND_END_ARG_INFO()
73 /* }}} */
74 
75 static void php_yar_client_trigger_error(int throw_exception, int code, const char *format, ...) /* {{{ */ {
76 	va_list arg;
77 	char *message;
78 	zend_class_entry *ce;
79 
80 	va_start(arg, format);
81 	vspprintf(&message, 0, format, arg);
82 	va_end(arg);
83 
84 	if (throw_exception) {
85 		switch (code) {
86 			case YAR_ERR_PACKAGER:
87 				ce = yar_client_packager_exception_ce;
88 				break;
89 			case YAR_ERR_PROTOCOL:
90 				ce = yar_client_protocol_exception_ce;
91 				break;
92 			case YAR_ERR_TRANSPORT:
93 				ce = yar_client_transport_exception_ce;
94 				break;
95 			case YAR_ERR_EXCEPTION:
96 				ce = yar_server_exception_ce;
97 				break;
98 			case YAR_ERR_REQUEST:
99 			default:
100 				ce  = yar_client_exception_ce;
101 				break;
102 		}
103 		zend_throw_exception(ce, message, code);
104 	} else {
105 		php_error_docref(NULL, E_WARNING, "[%d] %s", code, message);
106 	}
107 
108 	if (message) {
109 		efree(message);
110 	}
111 } /* }}} */
112 
php_yar_client_handle_error(int throw_exception,yar_response_t * response)113 static void php_yar_client_handle_error(int throw_exception, yar_response_t *response) /* {{{ */ {
114 	if (response->status == YAR_ERR_EXCEPTION && Z_TYPE(response->err) == IS_ARRAY) {
115 		if (throw_exception) {
116 			zval ex, *property;
117 			object_init_ex(&ex, yar_server_exception_ce);
118 #if PHP_VERSION_ID < 80000
119 			zval *obj = &ex;
120 #else
121 			zend_object *obj = Z_OBJ(ex);
122 #endif
123 
124 			if ((property = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("message"))) != NULL) {
125 				zend_update_property(yar_server_exception_ce, obj, ZEND_STRL("message"), property);
126 			}
127 
128 			if ((property = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("code"))) != NULL) {
129 				zend_update_property(yar_server_exception_ce, obj, ZEND_STRL("code"), property);
130 			}
131 
132 			if ((property = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("file"))) != NULL) {
133 				zend_update_property(yar_server_exception_ce, obj, ZEND_STRL("file"), property);
134 			}
135 
136 			if ((property = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("line"))) != NULL) {
137 				zend_update_property(yar_server_exception_ce, obj, ZEND_STRL("line"), property);
138 			}
139 
140 			if ((property = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("_type"))) != NULL) {
141 				zend_update_property(yar_server_exception_ce, obj, ZEND_STRL("_type"), property);
142 			}
143 
144 			zend_throw_exception_object(&ex);
145 		} else {
146 			zval *msg, *code;
147 			if ((msg = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("message"))) != NULL
148 					&& (code = zend_hash_str_find(Z_ARRVAL(response->err), ZEND_STRL("code"))) != NULL) {
149 				convert_to_string_ex(msg);
150 				convert_to_long_ex(code);
151 				php_yar_client_trigger_error(0, Z_LVAL_P(code), "server threw an exception with message `%s`", Z_STRVAL_P(msg));
152 			}
153 		}
154 	} else {
155 		php_yar_client_trigger_error(throw_exception, response->status, "%s", Z_STRVAL(response->err));
156 	}
157 }
158 /* }}} */
159 
php_yar_client_get_opt(zval * options,long type)160 static zval * php_yar_client_get_opt(zval *options, long type) /* {{{ */ {
161 	zval *value;
162 	if (IS_ARRAY != Z_TYPE_P(options)) {
163 		return NULL;
164 	}
165 
166 	if ((value = zend_hash_index_find(Z_ARRVAL_P(options), type)) != NULL) {
167 		return value;
168 	}
169 
170 	return NULL;
171 } /* }}} */
172 
php_yar_client_set_opt(zval * client,long type,zval * value)173 static int php_yar_client_set_opt(zval *client, long type, zval *value) /* {{{ */ {
174 	zval *options;
175 	zval rv;
176 #if PHP_VERSION_ID < 80000
177 	zval *zobj = client;
178 #else
179 	zend_object *zobj = Z_OBJ_P(client);
180 #endif
181 
182 	switch (type) {
183 		case YAR_OPT_PACKAGER: {
184              if (IS_STRING != Z_TYPE_P(value)) {
185 				 php_error_docref(NULL, E_WARNING, "expects a string packager name");
186 				 return 0;
187 			}
188 		}
189 		break;
190 		case YAR_OPT_PERSISTENT: {
191 				if (IS_LONG != Z_TYPE_P(value) && IS_TRUE != Z_TYPE_P(value) && IS_FALSE != Z_TYPE_P(value)) {
192 					php_error_docref(NULL, E_WARNING, "expects a boolean persistent flag");
193 					return 0;
194 				}
195 		}
196 		break;
197 		case YAR_OPT_HEADER: {
198 				zval *protocol = zend_read_property(yar_client_ce, zobj, ZEND_STRL("_protocol"), 0, &rv);
199 				if (Z_LVAL_P(protocol) != YAR_CLIENT_PROTOCOL_HTTP) {
200 					php_error_docref(NULL, E_WARNING, "header only works with HTTP protocol");
201 					return 0;
202 				}
203 				if (IS_ARRAY != Z_TYPE_P(value)) {
204 					php_error_docref(NULL, E_WARNING, "expects an array as header value");
205 					return 0;
206 				}
207 		}
208 		break;
209 		case YAR_OPT_RESOLVE:{
210 				zval *protocol = zend_read_property(yar_client_ce, zobj, ZEND_STRL("_protocol"), 0, &rv);
211 				if (Z_LVAL_P(protocol) != YAR_CLIENT_PROTOCOL_HTTP) {
212 					php_error_docref(NULL, E_WARNING, "resolve only works with HTTP protocol");
213 					return 0;
214 				}
215 				if (IS_ARRAY != Z_TYPE_P(value)) {
216 					php_error_docref(NULL, E_WARNING, "expects an array as resolve value");
217 					return 0;
218 				}
219 #if LIBCURL_VERSION_NUM < 0x071503
220 				/* Available since 7.21.3 */
221 				php_error_docref(NULL, E_WARNING, "YAR_OPT_RESOLVE require libcurl 7.21.3 and above");
222 				return 0;
223 #endif
224 		}
225 		break;
226 		case YAR_OPT_TIMEOUT:
227 		case YAR_OPT_CONNECT_TIMEOUT: {
228 			if (IS_LONG != Z_TYPE_P(value)) {
229 				php_error_docref(NULL, E_WARNING, "expects a long integer timeout value");
230 				return 0;
231 			}
232 		}
233 		break;
234 		default:
235 			return 0;
236 	}
237 
238 	options = zend_read_property(yar_client_ce, zobj, ZEND_STRL("_options"), 0, &rv);
239 	if (IS_ARRAY != Z_TYPE_P(options)) {
240 		zval empty_options;
241 		array_init(&empty_options);
242 		zend_update_property(yar_client_ce, zobj, ZEND_STRL("_options"), &empty_options);
243 		Z_DELREF(empty_options);
244 	}
245 
246 	Z_TRY_ADDREF_P(value);
247 	zend_hash_index_update(Z_ARRVAL_P(options), type, value);
248 
249 	return 1;
250 } /* }}} */
251 
php_yar_client_handle(int protocol,zval * client,zend_string * method,zval * params,zval * retval)252 static int php_yar_client_handle(int protocol, zval *client, zend_string *method, zval *params, zval *retval) /* {{{ */ {
253 	char *msg;
254 	zval *uri, *options;
255 	zval rv;
256 	const yar_transport_t *factory;
257 	yar_transport_interface_t *transport;
258 	yar_request_t *request;
259 	yar_response_t *response;
260 	int flags = 0;
261 #if PHP_VERSION_ID < 80000
262 	zval *zobj = client;
263 #else
264 	zend_object *zobj = Z_OBJ_P(client);
265 #endif
266 
267 	uri = zend_read_property(yar_client_ce, zobj, ZEND_STRL("_uri"), 0, &rv);
268 
269 	if (protocol == YAR_CLIENT_PROTOCOL_HTTP) {
270 		factory = php_yar_transport_get(ZEND_STRL("curl"));
271 	} else if (protocol == YAR_CLIENT_PROTOCOL_TCP || protocol == YAR_CLIENT_PROTOCOL_UNIX) {
272 		factory = php_yar_transport_get(ZEND_STRL("sock"));
273 	} else {
274 		return 0;
275 	}
276 
277 	transport = factory->init();
278 
279 	options = zend_read_property(yar_client_ce, zobj, ZEND_STRL("_options"), 1, &rv);
280 
281 	if (IS_ARRAY != Z_TYPE_P(options)) {
282 		options = NULL;
283 	}
284 
285 	if (UNEXPECTED(!(request = php_yar_request_instance(method, params, options)))) {
286 		transport->close(transport);
287 		factory->destroy(transport);
288 		return 0;
289 	}
290 
291 	if (options) {
292 		zval *flag = php_yar_client_get_opt(options, YAR_OPT_PERSISTENT);
293 		if (flag && (Z_TYPE_P(flag) == IS_TRUE || (Z_TYPE_P(flag) == IS_LONG && Z_LVAL_P(flag)))) {
294 			flags |= YAR_PROTOCOL_PERSISTENT;
295 		}
296 	}
297 
298 	/* This is tricky to pass options in, for custom headers*/
299 	msg = (char*)options;
300 	if (UNEXPECTED(!transport->open(transport, Z_STR_P(uri), flags, &msg))) {
301 		php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
302 		php_yar_request_destroy(request);
303 		ZEND_ASSERT(msg != (char*)options);
304 		efree(msg);
305 		transport->close(transport);
306 		factory->destroy(transport);
307 		return 0;
308 	}
309 
310 	DEBUG_C(ZEND_ULONG_FMT": call api '%s' at (%c)'%s' with '%d' parameters",
311 			request->id, ZSTR_VAL(request->method), (flags & YAR_PROTOCOL_PERSISTENT)? 'p' : 'r', Z_STRVAL_P(uri),
312 			zend_hash_num_elements(Z_ARRVAL(request->parameters)));
313 
314 	if (UNEXPECTED(!transport->send(transport, request, &msg))) {
315 		php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
316 		php_yar_request_destroy(request);
317 		efree(msg);
318 		transport->close(transport);
319 		factory->destroy(transport);
320 		return 0;
321 	}
322 
323 	response = transport->exec(transport, request);
324 
325 	if (UNEXPECTED(response->status != YAR_ERR_OKEY)) {
326 		php_yar_client_handle_error(1, response);
327 		php_yar_request_destroy(request);
328 		php_yar_response_destroy(response);
329 		transport->close(transport);
330 		factory->destroy(transport);
331 		return 0;
332 	} else {
333 		if (response->out && ZSTR_LEN(response->out)) {
334 			PHPWRITE(ZSTR_VAL(response->out), ZSTR_LEN(response->out));
335 		}
336 		ZVAL_COPY(retval, &response->retval);
337 		php_yar_request_destroy(request);
338 		php_yar_response_destroy(response);
339 		transport->close(transport);
340 		factory->destroy(transport);
341 		return 1;
342 	}
343 } /* }}} */
344 
php_yar_concurrent_client_callback(yar_call_data_t * calldata,int status,yar_response_t * response)345 int php_yar_concurrent_client_callback(yar_call_data_t *calldata, int status, yar_response_t *response) /* {{{ */ {
346 	zval code, retval, retval_ptr;
347 	zval callinfo, *callback, func_params[3];
348 	zend_bool bailout = 0;
349 	unsigned params_count, i;
350 
351 	if (calldata) {
352 		/* data callback */
353 		if (status == YAR_ERR_OKEY) {
354 			if (!Z_ISUNDEF(calldata->callback)) {
355 				callback = &calldata->callback;
356 			} else {
357 				callback = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_callback"), 0);
358 			}
359 			params_count = 2;
360 		} else {
361 			if (!Z_ISUNDEF(calldata->ecallback)) {
362 				callback = &calldata->ecallback;
363 			} else {
364 				callback = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_error_callback"), 0);
365 			}
366 			params_count = 3;
367 		}
368 
369 		if (Z_ISNULL_P(callback)) {
370 			if (status != YAR_ERR_OKEY) {
371 				if (!Z_ISUNDEF(response->err)) {
372 					php_yar_client_handle_error(0, response);
373 				} else {
374 					php_error_docref(NULL, E_WARNING, "[%d]:unknown Error", status);
375 				}
376 			} else if (!Z_ISUNDEF(response->retval)) {
377 				zend_print_zval(&response->retval, 1);
378 			}
379 			return 1;
380 		}
381 
382 		if (status == YAR_ERR_OKEY) {
383 			if (Z_ISUNDEF(response->retval)) {
384 				php_yar_client_trigger_error(0, YAR_ERR_REQUEST, "%s", "server responsed empty response");
385 				return 1;
386 			}
387 			ZVAL_COPY(&retval, &response->retval);
388 		} else {
389 			ZVAL_LONG(&code, status);
390 			ZVAL_COPY(&retval, &response->err);
391 		}
392 
393 		array_init(&callinfo);
394 
395 		add_assoc_long_ex(&callinfo, "sequence", sizeof("sequence") - 1, calldata->sequence);
396 		add_assoc_str_ex(&callinfo, "uri", sizeof("uri") - 1, zend_string_copy(calldata->uri));
397 		add_assoc_str_ex(&callinfo, "method", sizeof("method") - 1, zend_string_copy(calldata->method));
398 	} else {
399 		callback = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_callback"), 0);
400 		if (Z_ISNULL_P(callback)) {
401 			return 1;
402 		}
403 		params_count = 2;
404 	}
405 
406 	if (calldata && (status != YAR_ERR_OKEY)) {
407 	    ZVAL_COPY_VALUE(&func_params[0], &code);
408 	    ZVAL_COPY_VALUE(&func_params[1], &retval);
409 	    ZVAL_COPY_VALUE(&func_params[2], &callinfo);
410 	} else if (calldata) {
411 	    ZVAL_COPY_VALUE(&func_params[0], &retval);
412 	    ZVAL_COPY_VALUE(&func_params[1], &callinfo);
413 	} else {
414 	    ZVAL_NULL(&func_params[0]);
415 	    ZVAL_NULL(&func_params[1]);
416 	}
417 
418 	zend_try {
419 		if (call_user_function(EG(function_table), NULL, callback, &retval_ptr, params_count, func_params) != SUCCESS) {
420 			for (i = 0; i < params_count; i++) {
421 				zval_ptr_dtor(&func_params[i]);
422 			}
423 			if (calldata) {
424 				php_error_docref(NULL, E_WARNING, "call to callback failed for request: '%s'", ZSTR_VAL(calldata->method));
425 			} else {
426 				php_error_docref(NULL, E_WARNING, "call to initial callback failed");
427 			}
428 			return 1;
429 		}
430 	} zend_catch {
431 		bailout = 1;
432 	} zend_end_try();
433 
434 	if (!Z_ISUNDEF(retval_ptr)) {
435 		zval_ptr_dtor(&retval_ptr);
436 	}
437 
438 	for (i = 0; i < params_count; i++) {
439 		zval_ptr_dtor(&func_params[i]);
440 	}
441     return bailout? 0 : 1;
442 } /* }}} */
443 
php_yar_concurrent_client_handle(zval * callstack)444 int php_yar_concurrent_client_handle(zval *callstack) /* {{{ */ {
445 	char *msg;
446 	zval *calldata;
447 	yar_request_t *request;
448 	const yar_transport_t *factory;
449 	yar_transport_interface_t *transport;
450 	yar_transport_multi_interface_t *multi;
451 
452 	factory = php_yar_transport_get(ZEND_STRL("curl"));
453 	multi = factory->multi->init();
454 
455     ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(callstack), calldata) {
456 		yar_call_data_t *entry;
457 		long flags = 0;
458 
459 		entry = (yar_call_data_t *)zend_fetch_resource(Z_RES_P(calldata), "Yar Call Data", le_calldata);
460 
461 		if (!entry) {
462 			continue;
463 		}
464 
465 		if (Z_ISUNDEF(entry->parameters)) {
466 			array_init(&entry->parameters);
467 		}
468 
469 		transport = factory->init();
470 
471 		if (!Z_ISUNDEF(entry->options)) {
472 			zval *flag = php_yar_client_get_opt(&entry->options, YAR_OPT_PERSISTENT);
473 			if (flag && (Z_TYPE_P(flag) == IS_TRUE || (Z_TYPE_P(flag) == IS_LONG && Z_LVAL_P(flag)))) {
474 				flags |= YAR_PROTOCOL_PERSISTENT;
475 			}
476 		}
477 
478 		if (!(request = php_yar_request_instance(entry->method,
479 						&entry->parameters, Z_ISUNDEF(entry->options)? NULL: & entry->options))) {
480 			transport->close(transport);
481 			factory->destroy(transport);
482 			return 0;
483 		}
484 
485 		msg = (char*)&entry->options;
486 		if (!transport->open(transport, entry->uri, flags, &msg)) {
487 			php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
488 			transport->close(transport);
489 			factory->destroy(transport);
490 			efree(msg);
491 			return 0;
492 		}
493 
494 		DEBUG_C(ZEND_ULONG_FMT": call api '%s' at (%c)'%s' with '%d' parameters",
495 				request->id, ZSTR_VAL(request->method), (flags & YAR_PROTOCOL_PERSISTENT)? 'p' : 'r', entry->uri,
496 			   	zend_hash_num_elements(Z_ARRVAL(request->parameters)));
497 
498 		if (!transport->send(transport, request, &msg)) {
499 			php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
500 			transport->close(transport);
501 			factory->destroy(transport);
502 			efree(msg);
503 			return 0;
504 		}
505 
506 		transport->calldata(transport, entry);
507 		multi->add(multi, transport);
508 		php_yar_request_destroy(request);
509 	} ZEND_HASH_FOREACH_END();
510 
511 	if (!multi->exec(multi, php_yar_concurrent_client_callback)) {
512 		multi->close(multi);
513 		return 0;
514 	}
515 
516 	multi->close(multi);
517 	return 1;
518 } /* }}} */
519 
520 /* {{{ proto Yar_Client::__construct($uri[, array $options = NULL]) */
PHP_METHOD(yar_client,__construct)521 PHP_METHOD(yar_client, __construct) {
522 	zend_string *url;
523 	zval *options = NULL;
524 #if PHP_VERSION_ID < 80000
525 	zval *this_ptr = getThis();
526 #else
527 	zend_object *this_ptr = Z_OBJ_P(getThis());
528 #endif
529 
530     if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "S|a!", &url, &options) == FAILURE) {
531         return;
532     }
533 
534     zend_update_property_str(yar_client_ce, this_ptr, ZEND_STRL("_uri"), url);
535 
536 	if (strncasecmp(ZSTR_VAL(url), "http://", sizeof("http://") - 1) == 0
537 			|| strncasecmp(ZSTR_VAL(url), "https://", sizeof("https://") - 1) == 0) {
538 	} else if (strncasecmp(ZSTR_VAL(url), "tcp://", sizeof("tcp://") - 1) == 0) {
539 		zend_update_property_long(yar_client_ce, this_ptr, ZEND_STRL("_protocol"), YAR_CLIENT_PROTOCOL_TCP);
540 	} else if (strncasecmp(ZSTR_VAL(url), "unix://", sizeof("unix://") - 1) == 0) {
541 		zend_update_property_long(yar_client_ce, this_ptr, ZEND_STRL("_protocol"), YAR_CLIENT_PROTOCOL_UNIX);
542 	} else {
543 		php_yar_client_trigger_error(1, YAR_ERR_PROTOCOL, "unsupported protocol address %s", ZSTR_VAL(url));
544 		return;
545 	}
546 
547 	if (options) {
548     	zend_update_property(yar_client_ce, this_ptr, ZEND_STRL("_options"), options);
549 	}
550 }
551 /* }}} */
552 
553 /* {{{ proto Yar_Client::__call($method, $parameters = NULL) */
PHP_METHOD(yar_client,__call)554 PHP_METHOD(yar_client, __call) {
555 	zval *params, *protocol, rv;
556 	zend_string *method;
557 #if PHP_VERSION_ID < 80000
558 	zval *this_ptr = getThis();
559 #else
560 	zend_object *this_ptr = Z_OBJ_P(getThis());
561 #endif
562 
563 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa", &method, &params) == FAILURE) {
564 		return;
565 	}
566 
567 	protocol = zend_read_property(yar_client_ce, this_ptr, ZEND_STRL("_protocol"), 0, &rv);
568 
569 	switch (Z_LVAL_P(protocol)) {
570 		case YAR_CLIENT_PROTOCOL_TCP:
571 		case YAR_CLIENT_PROTOCOL_UNIX:
572 		case YAR_CLIENT_PROTOCOL_HTTP:
573 			if ((php_yar_client_handle(Z_LVAL_P(protocol), getThis(), method, params, return_value))) {
574 				return;
575 			}
576 			break;
577 		default:
578 			php_error_docref(NULL, E_WARNING, "unsupported protocol %ld", Z_LVAL_P(protocol));
579 			break;
580 	}
581 
582 	RETURN_FALSE;
583 }
584 /* }}} */
585 
586 /* {{{ proto Yar_Client::call($method, $parameters = NULL) */
PHP_METHOD(yar_client,call)587 PHP_METHOD(yar_client, call) {
588 	PHP_MN(yar_client___call)(INTERNAL_FUNCTION_PARAM_PASSTHRU);
589 }
590 /* }}} */
591 
592 /* {{{ proto Yar_Client::getOpt(int $type) */
PHP_METHOD(yar_client,getOpt)593 PHP_METHOD(yar_client, getOpt) {
594 	long type;
595 	zval *value, rv;
596 #if PHP_VERSION_ID < 80000
597 	zval *this_ptr = getThis();
598 #else
599 	zend_object *this_ptr = Z_OBJ_P(getThis());
600 #endif
601 
602 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &type) == FAILURE) {
603 		return;
604 	} else {
605 		zval *options = zend_read_property(yar_client_ce, this_ptr, ZEND_STRL("_options"), 0, &rv);
606 		if (!(value = php_yar_client_get_opt(options, type))) {
607 			RETURN_FALSE;
608 		}
609 
610 		RETURN_ZVAL(value, 1, 0);
611 	}
612 }
613 /* }}} */
614 
615 /* {{{ proto Yar_Client::setOpt(int $type, mixed $value) */
PHP_METHOD(yar_client,setOpt)616 PHP_METHOD(yar_client, setOpt) {
617 	long type;
618 	zval *value;
619 
620 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &type, &value) == FAILURE) {
621 		return;
622 	}
623 
624 	if (!php_yar_client_set_opt(getThis(), type, value)) {
625 		RETURN_FALSE;
626 	}
627 
628 	RETURN_ZVAL(getThis(), 1, 0);
629 }
630 /* }}} */
631 
632 /* {{{ proto Yar_Concurrent_Client::call($uri, $method, $parameters = NULL, $callback = NULL, $error_callback = NULL, $options = array()) */
PHP_METHOD(yar_concurrent_client,call)633 PHP_METHOD(yar_concurrent_client, call) {
634 	zend_string *uri, *method;
635     zend_string *name = NULL;
636 	zval *callstack, item, *status;
637 	zval *error_callback = NULL, *callback = NULL, *parameters = NULL, *options = NULL;
638 	yar_call_data_t *entry;
639 
640 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|a!z!za",
641 				&uri, &method, &parameters, &callback, &error_callback, &options) == FAILURE) {
642 		return;
643 	}
644 
645 	if (UNEXPECTED(!ZSTR_LEN(uri))) {
646 		php_error_docref(NULL, E_WARNING, "first parameter is expected to be a valid rpc server uri");
647 		return;
648 	}
649 
650 	if (UNEXPECTED(strncasecmp(ZSTR_VAL(uri), "http://", sizeof("http://") - 1) &&
651 		strncasecmp(ZSTR_VAL(uri), "https://", sizeof("https://") - 1))) {
652 		php_error_docref(NULL, E_WARNING, "only http protocol is supported in concurrent client for now");
653 		return;
654 	}
655 
656 	if (UNEXPECTED(!method->len)) {
657 		php_error_docref(NULL, E_WARNING, "second parameter is expected to be a valid rpc api name");
658 		return;
659 	}
660 
661     if ((callback && !Z_ISNULL_P(callback) && !zend_is_callable(callback, 0, &name))) {
662         php_error_docref1(NULL, ZSTR_VAL(name), E_ERROR, "fourth parameter is expected to be a valid callback");
663         zend_string_release(name);
664         RETURN_FALSE;
665     }
666 
667 	if (name) {
668 		zend_string_release(name);
669 		name = NULL;
670 	}
671 
672     if (UNEXPECTED(error_callback && !Z_ISNULL_P(error_callback) && !zend_is_callable(error_callback, 0, &name))) {
673         php_error_docref1(NULL, ZSTR_VAL(name), E_ERROR, "fifth parameter is expected to be a valid error callback");
674         zend_string_release(name);
675         RETURN_FALSE;
676     }
677 
678 	if (name) {
679         zend_string_release(name);
680 	}
681 
682 	status = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_start"), 0);
683 	if (UNEXPECTED(Z_TYPE_P(status) == IS_TRUE)) {
684         php_error_docref(NULL, E_WARNING, "concurrent client has already started");
685 		RETURN_FALSE;
686 	}
687 
688 	entry = ecalloc(1, sizeof(yar_call_data_t));
689 
690 	entry->uri = zend_string_copy(uri);
691 	entry->method = zend_string_copy(method);
692 
693 	if (callback && !Z_ISNULL_P(callback)) {
694 		ZVAL_COPY(&entry->callback, callback);
695 	}
696 	if (error_callback && !Z_ISNULL_P(error_callback)) {
697 		ZVAL_COPY(&entry->ecallback, error_callback);
698 	}
699 	if (parameters && IS_ARRAY == Z_TYPE_P(parameters)) {
700 		ZVAL_COPY(&entry->parameters, parameters);
701 	}
702 	if (options && IS_ARRAY == Z_TYPE_P(options)) {
703 		ZVAL_COPY(&entry->options, options);
704 	}
705 
706 	callstack = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_callstack"), 0);
707 	if (Z_ISNULL_P(callstack)) {
708 	    zval rv;
709 		array_init(&rv);
710 		zend_update_static_property(yar_concurrent_client_ce, ZEND_STRL("_callstack"), &rv);
711 		ZVAL_ARR(callstack, Z_ARRVAL(rv));
712 		Z_DELREF(rv);
713 	}
714 
715 	ZVAL_RES(&item, zend_register_resource(entry, le_calldata));
716 
717 	entry->sequence = zend_hash_num_elements(Z_ARRVAL_P(callstack)) + 1;
718 
719 	zend_hash_next_index_insert(Z_ARRVAL_P(callstack), &item);
720 
721 	RETURN_LONG(entry->sequence);
722 }
723 /* }}} */
724 
725 /* {{{ proto Yar_Concurrent_Client::reset(void) */
PHP_METHOD(yar_concurrent_client,reset)726 PHP_METHOD(yar_concurrent_client, reset) {
727 	zval *callstack;
728 
729 	callstack = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_callstack"), 0);
730 	if (Z_ISNULL_P(callstack) || zend_hash_num_elements(Z_ARRVAL_P(callstack)) == 0) {
731 		RETURN_TRUE;
732 	}
733 	zend_hash_clean(Z_ARRVAL_P(callstack));
734 	RETURN_TRUE;
735 }
736 /* }}} */
737 
738 /* {{{ proto Yar_Concurrent_Client::loop($callback = NULL, $error_callback) */
PHP_METHOD(yar_concurrent_client,loop)739 PHP_METHOD(yar_concurrent_client, loop) {
740 	zend_string *name = NULL;
741 	zval *callstack;
742 	zval *callback = NULL, *error_callback = NULL;
743 	zval *status;
744 	unsigned ret = 0;
745 
746 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|zz", &callback, &error_callback) == FAILURE) {
747 		return;
748 	}
749 
750 	status = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_start"), 0);
751 	if (UNEXPECTED(Z_TYPE_P(status) == IS_TRUE)) {
752         php_error_docref(NULL, E_WARNING, "concurrent client has already started");
753 		RETURN_FALSE;
754 	}
755 
756     if (UNEXPECTED(callback && !Z_ISNULL_P(callback) && !zend_is_callable(callback, 0, &name))) {
757         php_error_docref1(NULL, ZSTR_VAL(name), E_ERROR, "first argument is expected to be a valid callback");
758         zend_string_release(name);
759         RETURN_FALSE;
760     }
761 
762 	if (name) {
763 		zend_string_release(name);
764 		name = NULL;
765 	}
766 
767     if (UNEXPECTED(error_callback && !Z_ISNULL_P(error_callback) && !zend_is_callable(error_callback, 0, &name))) {
768         php_error_docref1(NULL, ZSTR_VAL(name), E_ERROR, "second argument is expected to be a valid error callback");
769         zend_string_release(name);
770         RETURN_FALSE;
771     }
772 	if (name) {
773 		zend_string_release(name);
774 	}
775 
776 	callstack = zend_read_static_property(yar_concurrent_client_ce, ZEND_STRL("_callstack"), 0);
777 	if (Z_ISNULL_P(callstack) || zend_hash_num_elements(Z_ARRVAL_P(callstack)) == 0) {
778 		RETURN_TRUE;
779 	}
780 
781 	if (callback && !Z_ISNULL_P(callback)) {
782 		zend_update_static_property(yar_concurrent_client_ce, ZEND_STRL("_callback"), callback);
783 	}
784 
785 	if (error_callback && !Z_ISNULL_P(error_callback)) {
786 		zend_update_static_property(yar_concurrent_client_ce, ZEND_STRL("_error_callback"), error_callback);
787 	}
788 
789 	ZVAL_BOOL(status, 1);
790 	ret = php_yar_concurrent_client_handle(callstack);
791 	ZVAL_BOOL(status, 0);
792 	RETURN_BOOL(ret);
793 }
794 /* }}} */
795 
796 /* {{{ yar_client_methods */
797 zend_function_entry yar_client_methods[] = {
798 	PHP_ME(yar_client, __construct, arginfo_client___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR|ZEND_ACC_FINAL)
799 	PHP_ME(yar_client, call, arginfo_client___call, ZEND_ACC_PUBLIC)
800 	PHP_ME(yar_client, __call, arginfo_client___call, ZEND_ACC_PUBLIC)
801 	PHP_ME(yar_client, getOpt, arginfo_client_getopt, ZEND_ACC_PUBLIC)
802 	PHP_ME(yar_client, setOpt, arginfo_client_setopt, ZEND_ACC_PUBLIC)
803 	PHP_FE_END
804 };
805 /* }}} */
806 
807 /* {{{ yar_concurrent_client_methods */
808 zend_function_entry yar_concurrent_client_methods[] = {
809 	PHP_ME(yar_concurrent_client, call, arginfo_client_async, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
810 	PHP_ME(yar_concurrent_client, loop, arginfo_client_loop, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
811 	PHP_ME(yar_concurrent_client, reset,arginfo_client_void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
812 	PHP_FE_END
813 };
814 /* }}} */
815 
YAR_STARTUP_FUNCTION(client)816 YAR_STARTUP_FUNCTION(client) /* {{{ */ {
817     zend_class_entry ce;
818 
819     INIT_CLASS_ENTRY(ce, "Yar_Client", yar_client_methods);
820     yar_client_ce = zend_register_internal_class(&ce);
821 
822 	zend_declare_property_long(yar_client_ce, ZEND_STRL("_protocol"), YAR_CLIENT_PROTOCOL_HTTP, ZEND_ACC_PROTECTED);
823 	zend_declare_property_null(yar_client_ce, ZEND_STRL("_uri"), ZEND_ACC_PROTECTED);
824 	zend_declare_property_null(yar_client_ce, ZEND_STRL("_options"),  ZEND_ACC_PROTECTED);
825 	zend_declare_property_null(yar_client_ce, ZEND_STRL("_running"),  ZEND_ACC_PROTECTED);
826 
827 	INIT_CLASS_ENTRY(ce, "Yar_Concurrent_Client", yar_concurrent_client_methods);
828     yar_concurrent_client_ce = zend_register_internal_class(&ce);
829 	zend_declare_property_null(yar_concurrent_client_ce, ZEND_STRL("_callstack"), ZEND_ACC_PROTECTED|ZEND_ACC_STATIC);
830 	zend_declare_property_null(yar_concurrent_client_ce, ZEND_STRL("_callback"), ZEND_ACC_PROTECTED|ZEND_ACC_STATIC);
831 	zend_declare_property_null(yar_concurrent_client_ce, ZEND_STRL("_error_callback"), ZEND_ACC_PROTECTED|ZEND_ACC_STATIC);
832 	zend_declare_property_bool(yar_concurrent_client_ce, ZEND_STRL("_start"), 0, ZEND_ACC_PROTECTED|ZEND_ACC_STATIC);
833 
834 	REGISTER_LONG_CONSTANT("YAR_CLIENT_PROTOCOL_HTTP", YAR_CLIENT_PROTOCOL_HTTP, CONST_PERSISTENT | CONST_CS);
835 	REGISTER_LONG_CONSTANT("YAR_CLIENT_PROTOCOL_TCP", YAR_CLIENT_PROTOCOL_TCP, CONST_PERSISTENT | CONST_CS);
836 	REGISTER_LONG_CONSTANT("YAR_CLIENT_PROTOCOL_UNIX", YAR_CLIENT_PROTOCOL_UNIX, CONST_PERSISTENT | CONST_CS);
837 
838     return SUCCESS;
839 }
840 /* }}} */
841 
YAR_SHUTDOWN_FUNCTION(client)842 YAR_SHUTDOWN_FUNCTION(client) /* {{{ */ {
843 	return SUCCESS;
844 } /* }}} */
845 
846 /*
847  * Local variables:
848  * tab-width: 4
849  * c-basic-offset: 4
850  * End:
851  * vim600: noet sw=4 ts=4 fdm=marker
852  * vim<600: noet sw=4 ts=4
853  */
854