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, ¶ms) == 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, ¶meters, &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