From b6f8b5f25a50022dd7c67acf8f7c7ddc13e9ffc8 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Tue, 10 Feb 2015 17:38:50 +0100 Subject: [PATCH 1/1] implement curl multi options through http\Client::configure() - add http\Client::configure() * options: * bool(use_eventloop) * bool(pipelining) * int(maxconnects) * int(max_total_connections) * int(max_host_connections) * int(max_pipeling_length) * int(chunk_length_penalty_size) * int(content_length_penalty_size) * array(pipelining_site_bl) * array(pipelining_server_bl) - deprecate http\Client::enablePipelining() - deprecate http\Client::enableEvents() --- php_http_client.c | 21 +++- php_http_client.h | 1 + php_http_client_curl.c | 211 +++++++++++++++++++++++++++++++++++++---- php_http_misc.c | 12 ++- tests/client008.phpt | 3 +- tests/client015.phpt | 11 +-- tests/client016.phpt | 9 +- 7 files changed, 224 insertions(+), 44 deletions(-) diff --git a/php_http_client.c b/php_http_client.c index 3050bd8..948ca70 100644 --- a/php_http_client.c +++ b/php_http_client.c @@ -843,6 +843,22 @@ static PHP_METHOD(HttpClient, wait) } } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_configure, 0, 0, 1) + ZEND_ARG_ARRAY_INFO(0, settings, 1) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpClient, configure) +{ + HashTable *settings = NULL; + php_http_client_object_t *obj; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H!", &settings), invalid_arg, return); + obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_expect(SUCCESS == php_http_client_setopt(obj->client, PHP_HTTP_CLIENT_OPT_CONFIGURATION, settings), unexpected_val, return); + + RETVAL_ZVAL(getThis(), 1, 0); +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_enablePipelining, 0, 0, 0) ZEND_ARG_INFO(0, enable) ZEND_END_ARG_INFO(); @@ -1185,8 +1201,9 @@ static zend_function_entry php_http_client_methods[] = { PHP_ME(HttpClient, wait, ai_HttpClient_wait, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getResponse, ai_HttpClient_getResponse, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getHistory, ai_HttpClient_getHistory, ZEND_ACC_PUBLIC) - PHP_ME(HttpClient, enablePipelining, ai_HttpClient_enablePipelining, ZEND_ACC_PUBLIC) - PHP_ME(HttpClient, enableEvents, ai_HttpClient_enableEvents, ZEND_ACC_PUBLIC) + PHP_ME(HttpClient, configure, ai_HttpClient_configure, ZEND_ACC_PUBLIC) + PHP_ME(HttpClient, enablePipelining, ai_HttpClient_enablePipelining, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) + PHP_ME(HttpClient, enableEvents, ai_HttpClient_enableEvents, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) PHP_ME(HttpClient, notify, ai_HttpClient_notify, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, attach, ai_HttpClient_attach, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, detach, ai_HttpClient_detach, ZEND_ACC_PUBLIC) diff --git a/php_http_client.h b/php_http_client.h index b536753..47b3020 100644 --- a/php_http_client.h +++ b/php_http_client.h @@ -16,6 +16,7 @@ typedef enum php_http_client_setopt_opt { PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING, PHP_HTTP_CLIENT_OPT_USE_EVENTS, + PHP_HTTP_CLIENT_OPT_CONFIGURATION, } php_http_client_setopt_opt_t; typedef enum php_http_client_getopt_opt { diff --git a/php_http_client_curl.c b/php_http_client_curl.c index 3b4d5b1..289e43c 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -806,7 +806,7 @@ static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *t /* curl options */ -static php_http_options_t php_http_curle_options; +static php_http_options_t php_http_curle_options, php_http_curlm_options; #define PHP_HTTP_CURLE_OPTION_CHECK_STRLEN 0x0001 #define PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR 0x0002 @@ -1577,6 +1577,182 @@ static STATUS php_http_curle_set_option(php_http_option_t *opt, zval *val, void return rv; } +#if PHP_HTTP_CURL_VERSION(7,30,0) +static STATUS php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata) +{ + php_http_client_t *client = userdata; + php_http_client_curl_t *curl = client->ctx; + CURLM *ch = curl->handle; + HashTable tmp_ht; + char **bl = NULL; + TSRMLS_FETCH_FROM_CTX(client->ts); + + /* array of char *, ending with a NULL */ + if (value && Z_TYPE_P(value) != IS_NULL) { + zval **entry; + HashPosition pos; + HashTable *ht = HASH_OF(value); + int c = zend_hash_num_elements(ht); + char **ptr = ecalloc(c + 1, sizeof(char *)); + + bl = ptr; + + zend_hash_init(&tmp_ht, c, NULL, ZVAL_PTR_DTOR, 0); + array_join(ht, &tmp_ht, 0, ARRAY_JOIN_STRINGIFY); + + FOREACH_HASH_VAL(pos, &tmp_ht, entry) { + *ptr++ = Z_STRVAL_PP(entry); + } + } + + if (CURLM_OK != curl_multi_setopt(ch, opt->option, bl)) { + if (bl) { + efree(bl); + zend_hash_destroy(&tmp_ht); + } + return FAILURE; + } + + if (bl) { + efree(bl); + zend_hash_destroy(&tmp_ht); + } + return SUCCESS; +} +#endif + +#if PHP_HTTP_HAVE_EVENT +static inline STATUS php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable) +{ + php_http_client_curl_t *curl = h->ctx; + + if ((curl->useevents = enable)) { + if (!curl->evbase) { + curl->evbase = event_base_new(); + } + if (!curl->timeout) { + curl->timeout = ecalloc(1, sizeof(struct event)); + } + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h); + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback); + } else { + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL); + } + + return SUCCESS; +} + +static STATUS php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata) +{ + php_http_client_t *client = userdata; + + return php_http_curlm_use_eventloop(client, value && Z_BVAL_P(value)); +} +#endif + +static void php_http_curlm_options_init(php_http_options_t *registry TSRMLS_DC) +{ + php_http_option_t *opt; + + /* set size of connection cache */ + if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLMOPT_MAXCONNECTS, IS_LONG))) { + /* -1 == default, 0 == unlimited */ + ZVAL_LONG(&opt->defval, -1); + } + /* set max number of connections to a single host */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("max_host_connections"), CURLMOPT_MAX_HOST_CONNECTIONS, IS_LONG); +#endif + /* maximum number of requests in a pipeline */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("max_pipeline_length"), CURLMOPT_MAX_PIPELINE_LENGTH, IS_LONG))) { + ZVAL_LONG(&opt->defval, 5); + } +#endif + /* max simultaneously open connections */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("max_total_connections"), CURLMOPT_MAX_TOTAL_CONNECTIONS, IS_LONG); +#endif + /* enable/disable HTTP pipelining */ + php_http_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, IS_BOOL); + /* chunk length threshold for pipelining */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("chunk_lenght_penalty_size"), CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, IS_LONG); +#endif + /* size threshold for pipelining penalty */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("content_length_penalty_size"), CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, IS_LONG); +#endif + /* pipelining server blacklist */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_server_bl"), CURLMOPT_PIPELINING_SERVER_BL, IS_ARRAY))) { + opt->setter = php_http_curlm_option_set_pipelining_bl; + } +#endif + /* pipelining host blacklist */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_site_bl"), CURLMOPT_PIPELINING_SITE_BL, IS_ARRAY))) { + opt->setter = php_http_curlm_option_set_pipelining_bl; + } +#endif + /* events */ +#ifdef PHP_HTTP_HAVE_EVENT + if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, IS_BOOL))) { + opt->setter = php_http_curlm_option_set_use_eventloop; + } +#endif +} + +static STATUS php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_t *client = userdata; + php_http_client_curl_t *curl = client->ctx; + CURLM *ch = curl->handle; + zval *orig = val; + CURLMcode rc = CURLM_UNKNOWN_OPTION; + STATUS rv = SUCCESS; + TSRMLS_FETCH_FROM_CTX(client->ts); + + if (!val) { + val = &opt->defval; + } else if (Z_TYPE_P(val) != opt->type && !(Z_TYPE_P(val) == IS_NULL && opt->type == IS_ARRAY)) { + val = php_http_ztyp(opt->type, val); + } + + if (opt->setter) { + rv = opt->setter(opt, val, client); + } else { + switch (opt->type) { + case IS_BOOL: + if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) Z_BVAL_P(val)))) { + rv = FAILURE; + } + break; + case IS_LONG: + if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, Z_LVAL_P(val)))) { + rv = FAILURE; + } + break; + default: + rv = FAILURE; + break; + } + } + + if (val && val != orig && val != &opt->defval) { + zval_ptr_dtor(&val); + } + + if (rv != SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name.s, curl_easy_strerror(rc)); + } + return rv; +} /* client ops */ @@ -2109,6 +2285,10 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_ php_http_client_curl_t *curl = h->ctx; switch (opt) { + case PHP_HTTP_CLIENT_OPT_CONFIGURATION: + return php_http_options_apply(&php_http_curlm_options, (HashTable *) arg, h); + break; + case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING: if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) { return FAILURE; @@ -2117,23 +2297,7 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_ case PHP_HTTP_CLIENT_OPT_USE_EVENTS: #if PHP_HTTP_HAVE_EVENT - if ((curl->useevents = *((zend_bool *) arg))) { - if (!curl->evbase) { - curl->evbase = event_base_new(); - } - if (!curl->timeout) { - curl->timeout = ecalloc(1, sizeof(struct event)); - } - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h); - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback); - } else { - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL); - } + return php_http_curlm_use_eventloop(h, *(zend_bool *) arg); break; #endif @@ -2202,8 +2366,8 @@ PHP_MINIT_FUNCTION(http_client_curl) }; if (SUCCESS != php_http_client_driver_add(&driver)) { - return FAILURE; - } + return FAILURE; + } if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl"), &php_http_curlm_resource_factory_ops, NULL, NULL TSRMLS_CC)) { return FAILURE; @@ -2218,6 +2382,12 @@ PHP_MINIT_FUNCTION(http_client_curl) php_http_curle_options_init(options TSRMLS_CC); } + if ((options = php_http_options_init(&php_http_curlm_options, 1))) { + options->getter = php_http_option_get; + options->setter = php_http_curlm_set_option; + + php_http_curlm_options_init(options TSRMLS_CC); + } /* * HTTP Protocol Version Constants @@ -2300,6 +2470,7 @@ PHP_MSHUTDOWN_FUNCTION(http_client_curl) php_persistent_handle_cleanup(ZEND_STRL("http\\Client\\Curl\\Request"), NULL, 0 TSRMLS_CC); php_http_options_dtor(&php_http_curle_options); + php_http_options_dtor(&php_http_curlm_options); return SUCCESS; } diff --git a/php_http_misc.c b/php_http_misc.c index c6e2270..8e2227d 100644 --- a/php_http_misc.c +++ b/php_http_misc.c @@ -188,8 +188,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis if ((flags & ARRAY_JOIN_PRETTIFY) && hash_key->nKeyLength) { key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); zend_hash_find(dst, key, hash_key->nKeyLength, (void *) &data); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_find(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &data); + } else { + zend_hash_index_find(dst, hash_key->h, (void *) &data); } if (flags & ARRAY_JOIN_STRINGIFY) { @@ -205,8 +207,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis add_next_index_zval(*data, value); } else if (key) { zend_symtable_update(dst, key, hash_key->nKeyLength, &value, sizeof(zval *), NULL); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_add(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &value, sizeof(zval *), NULL); + } else { + zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL); } if (key) { @@ -238,8 +242,10 @@ int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); zend_hash_update(dst, key, hash_key->nKeyLength, (void *) &value, sizeof(zval *), NULL); efree(key); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_update(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &value, sizeof(zval *), NULL); + } else { + zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL); } } diff --git a/tests/client008.phpt b/tests/client008.phpt index 31584d3..2585f23 100644 --- a/tests/client008.phpt +++ b/tests/client008.phpt @@ -13,8 +13,7 @@ $request = new http\Client\Request("GET", "http://www.example.org"); foreach (http\Client::getAvailableDrivers() as $driver) { $client = new http\Client($driver); - $client->enablePipelining(true); - $client->enableEvents(true); + $client->configure(["pipelining" => true, "use_eventloop" => true]); $client->enqueue($request); $client->enqueue(clone $request); diff --git a/tests/client015.phpt b/tests/client015.phpt index 60d3132..0ab520c 100644 --- a/tests/client015.phpt +++ b/tests/client015.phpt @@ -3,13 +3,6 @@ http client event base --SKIPIF-- enableEvents()) - throw new Exception("need events support"); -} catch (Exception $e) { - die("skip ".$e->getMessage()); -} skip_online_test(); ?> --FILE-- @@ -19,8 +12,8 @@ echo "Test\n"; $client1 = new http\Client; $client2 = new http\Client; -$client1->enableEvents(); -$client2->enableEvents(); +$client1->configure(["use_eventloop" => true]); +$client2->configure(["use_eventloop" => true]); $client1->enqueue(new http\Client\Request("GET", "http://www.google.ca/")); $client2->enqueue(new http\Client\Request("GET", "http://www.google.co.uk/")); diff --git a/tests/client016.phpt b/tests/client016.phpt index f50d9bb..5fda0b6 100644 --- a/tests/client016.phpt +++ b/tests/client016.phpt @@ -3,13 +3,6 @@ client once & wait with events --SKIPIF-- enableEvents()) - throw new Exception("need events support"); -} catch (Exception $e) { - die("skip ".$e->getMessage()); -} skip_online_test(); ?> --FILE-- @@ -20,7 +13,7 @@ $request = new http\Client\Request("GET", "http://www.example.org/"); foreach (http\Client::getAvailableDrivers() as $driver) { $client = new http\Client($driver); - $client->enableEvents(true); + $client->configure(["use_eventloop" => true]); $client->enqueue($request); while ($client->once()) { -- 2.30.2