let the client share cookies/tls sessions
authorMichael Wallner <mike@php.net>
Tue, 26 Apr 2016 12:04:41 +0000 (14:04 +0200)
committerMichael Wallner <mike@php.net>
Tue, 26 Apr 2016 12:04:41 +0000 (14:04 +0200)
A curl client handle now contains a curl_multi and a curl_share handle.

Test client021.phpt still fails, so somethings still at odds here.

src/php_http_client_curl.c
tests/client021.phpt
tests/client027.phpt [new file with mode: 0644]
tests/helper/dump.inc
tests/helper/server.inc

index 7fa2023..df0c506 100644 (file)
 #      include <gnutls.h>
 #endif
 
+typedef struct php_http_client_curl_handle {
+       CURLM *multi;
+       CURLSH *share;
+} php_http_client_curl_handle_t;
+
 typedef struct php_http_client_curl {
-       CURLM *handle;
+       php_http_client_curl_handle_t *handle;
 
        int unfinished;  /* int because of curl_multi_perform() */
 
@@ -158,12 +163,29 @@ static php_resource_factory_ops_t php_http_curle_resource_factory_ops = {
 
 static void *php_http_curlm_ctor(void *opaque, void *init_arg TSRMLS_DC)
 {
-       return curl_multi_init();
+       php_http_client_curl_handle_t *curl = calloc(1, sizeof(*curl));
+
+       if (!(curl->multi = curl_multi_init())) {
+               free(curl);
+               return NULL;
+       }
+       if (!(curl->share = curl_share_init())) {
+               curl_multi_cleanup(curl->multi);
+               free(curl);
+               return NULL;
+       }
+       curl_share_setopt(curl->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
+       curl_share_setopt(curl->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
+       return curl;
 }
 
 static void php_http_curlm_dtor(void *opaque, void *handle TSRMLS_DC)
 {
-       curl_multi_cleanup(handle);
+       php_http_client_curl_handle_t *curl = handle;
+
+       curl_share_cleanup(curl->share);
+       curl_multi_cleanup(curl->multi);
+       free(handle);
 }
 
 static php_resource_factory_ops_t php_http_curlm_resource_factory_ops = {
@@ -638,7 +660,7 @@ static void php_http_curlm_responsehandler(php_http_client_t *context)
        TSRMLS_FETCH_FROM_CTX(context->ts);
 
        do {
-               CURLMsg *msg = curl_multi_info_read(curl->handle, &remaining);
+               CURLMsg *msg = curl_multi_info_read(curl->handle->multi, &remaining);
 
                if (msg && CURLMSG_DONE == msg->msg) {
                        if (CURLE_OK != msg->data.result) {
@@ -721,7 +743,7 @@ static void php_http_curlm_timeout_callback(int socket, short action, void *even
                (void) socket;
                (void) action;
 
-               while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, CURL_SOCKET_TIMEOUT, 0, &curl->unfinished)));
+               while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle->multi, CURL_SOCKET_TIMEOUT, 0, &curl->unfinished)));
 
                if (CURLM_OK != rc) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s",  curl_multi_strerror(rc));
@@ -743,7 +765,7 @@ static void php_http_curlm_event_callback(int socket, short action, void *event_
                CURLMcode rc = CURLM_OK;
                TSRMLS_FETCH_FROM_CTX(context->ts);
 
-               while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, socket, etoca(action), &curl->unfinished)));
+               while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle->multi, socket, etoca(action), &curl->unfinished)));
 
                if (CURLM_OK != rc) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", curl_multi_strerror(rc));
@@ -774,7 +796,7 @@ static int php_http_curlm_socket_callback(CURL *easy, curl_socket_t sock, int ac
                if (!ev) {
                        ev = ecalloc(1, sizeof(php_http_curlm_event_t));
                        ev->context = context;
-                       curl_multi_assign(curl->handle, sock, ev);
+                       curl_multi_assign(curl->handle->multi, sock, ev);
                } else {
                        event_del(&ev->evnt);
                }
@@ -875,6 +897,7 @@ static ZEND_RESULT_CODE php_http_curle_option_set_cookiestore(php_http_option_t
        ) {
                return FAILURE;
        }
+
        return SUCCESS;
 }
 
@@ -1640,7 +1663,7 @@ static ZEND_RESULT_CODE php_http_curlm_option_set_pipelining_bl(php_http_option_
 {
        php_http_client_t *client = userdata;
        php_http_client_curl_t *curl = client->ctx;
-       CURLM *ch = curl->handle;
+       CURLM *ch = curl->handle->multi;
        HashTable tmp_ht;
        char **bl = NULL;
        TSRMLS_FETCH_FROM_CTX(client->ts);
@@ -1691,15 +1714,15 @@ static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h
                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);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, h);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, h);
+               curl_multi_setopt(curl->handle->multi, 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);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL);
+               curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL);
        }
 
        return SUCCESS;
@@ -1770,7 +1793,7 @@ static ZEND_RESULT_CODE php_http_curlm_set_option(php_http_option_t *opt, zval *
 {
        php_http_client_t *client = userdata;
        php_http_client_curl_t *curl = client->ctx;
-       CURLM *ch = curl->handle;
+       CURLM *ch = curl->handle->multi;
        zval *orig = val;
        CURLMcode rc = CURLM_UNKNOWN_OPTION;
        ZEND_RESULT_CODE rv = SUCCESS;
@@ -1873,11 +1896,12 @@ static ZEND_RESULT_CODE php_http_client_curl_handler_reset(php_http_client_curl_
 static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_http_client_t *h, php_resource_factory_t *rf)
 {
        void *handle;
+       php_http_client_curl_t *curl = h->ctx;
        php_http_client_curl_handler_t *handler;
        TSRMLS_FETCH_FROM_CTX(h->ts);
 
        if (!(handle = php_resource_factory_handle_ctor(rf, NULL TSRMLS_CC))) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to initialize curl handle");
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to initialize curl handle->multi");
                return NULL;
        }
 
@@ -1908,12 +1932,13 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, php_http_curle_xferinfo_callback);
        curl_easy_setopt(handle, CURLOPT_XFERINFODATA, handler);
 #else
-       curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, php_http_curle_progress_callback);
-       curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler);
+       curl_easy_setopt(handle->multi, CURLOPT_PROGRESSFUNCTION, php_http_curle_progress_callback);
+       curl_easy_setopt(handle->multi, CURLOPT_PROGRESSDATA, handler);
 #endif
        curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler);
        curl_easy_setopt(handle, CURLOPT_WRITEDATA, handler);
        curl_easy_setopt(handle, CURLOPT_HEADERDATA, handler);
+       curl_easy_setopt(handle, CURLOPT_SHARE, curl->handle->share);
 
        php_http_client_curl_handler_reset(handler);
 
@@ -2036,10 +2061,12 @@ static void php_http_client_curl_handler_clear(php_http_client_curl_handler_t *h
 #if PHP_HTTP_CURL_VERSION(7,32,0)
        curl_easy_setopt(handler->handle, CURLOPT_XFERINFOFUNCTION, NULL);
 #else
-       curl_easy_setopt(handler->handle, CURLOPT_PROGRESSFUNCTION, NULL);
+       curl_easy_setopt(handler->handle->multi, CURLOPT_PROGRESSFUNCTION, NULL);
 #endif
        curl_easy_setopt(handler->handle, CURLOPT_VERBOSE, 0L);
        curl_easy_setopt(handler->handle, CURLOPT_DEBUGFUNCTION, NULL);
+       curl_easy_setopt(handler->handle, CURLOPT_COOKIELIST, "FLUSH");
+       curl_easy_setopt(handler->handle, CURLOPT_SHARE, NULL);
 }
 
 static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *handler)
@@ -2071,7 +2098,7 @@ static php_http_client_t *php_http_client_curl_init(php_http_client_t *h, void *
        TSRMLS_FETCH_FROM_CTX(h->ts);
 
        if (!handle && !(handle = php_resource_factory_handle_ctor(h->rf, NULL TSRMLS_CC))) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to initialize curl handle");
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to initialize curl handle->multi");
                return NULL;
        }
 
@@ -2137,6 +2164,7 @@ static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_e
                size_t id_len;
                int port = url->port ? url->port : 80;
                zval **zport;
+               php_persistent_handle_factory_t *phf = h->rf->data;
 
                if (SUCCESS == zend_hash_find(enqueue->options, ZEND_STRS("port"), (void *) &zport)) {
                        zval *zcpy = php_http_ztyp(IS_LONG, *zport);
@@ -2147,7 +2175,7 @@ static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_e
                        zval_ptr_dtor(&zcpy);
                }
 
-               id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port);
+               id_len = spprintf(&id_str, 0, "%.*s:%s:%d", (int) phf->ident.len, phf->ident.str, STR_PTR(url->host), port);
                pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
                efree(id_str);
        }
@@ -2189,21 +2217,22 @@ static ZEND_RESULT_CODE php_http_client_curl_enqueue(php_http_client_t *h, php_h
        enqueue->opaque = handler;
        enqueue->dtor = queue_dtor;
 
-       if (CURLM_OK == (rs = curl_multi_add_handle(curl->handle, handler->handle))) {
-               zend_llist_add_element(&h->requests, enqueue);
-               ++curl->unfinished;
-
-               if (h->callback.progress.func && SUCCESS == php_http_client_getopt(h, PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, enqueue->request, &progress)) {
-                       progress->info = "start";
-                       h->callback.progress.func(h->callback.progress.arg, h, &handler->queue, progress);
-                       progress->started = 1;
-               }
-
-               return SUCCESS;
-       } else {
+       if (CURLM_OK != (rs = curl_multi_add_handle(curl->handle->multi, handler->handle))) {
+               php_http_client_curl_handler_dtor(handler);
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not enqueue request: %s", curl_multi_strerror(rs));
                return FAILURE;
        }
+
+       zend_llist_add_element(&h->requests, enqueue);
+       ++curl->unfinished;
+
+       if (h->callback.progress.func && SUCCESS == php_http_client_getopt(h, PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, enqueue->request, &progress)) {
+               progress->info = "start";
+               h->callback.progress.func(h->callback.progress.arg, h, &handler->queue, progress);
+               progress->started = 1;
+       }
+
+       return SUCCESS;
 }
 
 static ZEND_RESULT_CODE php_http_client_curl_dequeue(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
@@ -2214,7 +2243,7 @@ static ZEND_RESULT_CODE php_http_client_curl_dequeue(php_http_client_t *h, php_h
        TSRMLS_FETCH_FROM_CTX(h->ts);
 
        php_http_client_curl_handler_clear(handler);
-       if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle, handler->handle))) {
+       if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle->multi, handler->handle))) {
                zend_llist_del_element(&h->requests, handler->handle, (int (*)(void *, void *)) compare_queue);
                return SUCCESS;
        } else {
@@ -2236,7 +2265,7 @@ static void php_http_client_curl_reset(php_http_client_t *h)
 
 static inline void php_http_client_curl_get_timeout(php_http_client_curl_t *curl, long max_tout, struct timeval *timeout)
 {
-       if ((CURLM_OK == curl_multi_timeout(curl->handle, &max_tout)) && (max_tout > 0)) {
+       if ((CURLM_OK == curl_multi_timeout(curl->handle->multi, &max_tout)) && (max_tout > 0)) {
                timeout->tv_sec = max_tout / 1000;
                timeout->tv_usec = (max_tout % 1000) * 1000;
        } else {
@@ -2279,7 +2308,7 @@ static ZEND_RESULT_CODE php_http_client_curl_wait(php_http_client_t *h, struct t
        FD_ZERO(&W);
        FD_ZERO(&E);
 
-       if (CURLM_OK == curl_multi_fdset(curl->handle, &R, &W, &E, &MAX)) {
+       if (CURLM_OK == curl_multi_fdset(curl->handle->multi, &R, &W, &E, &MAX)) {
                if (custom_timeout && timerisset(custom_timeout)) {
                        timeout = *custom_timeout;
                } else {
@@ -2305,7 +2334,7 @@ static int php_http_client_curl_once(php_http_client_t *h)
                event_base_loop(curl->evbase, EVLOOP_NONBLOCK);
        } else
 #endif
-       while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle, &curl->unfinished));
+       while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle->multi, &curl->unfinished));
 
        php_http_curlm_responsehandler(h);
 
@@ -2364,7 +2393,7 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht
                        break;
 
                case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING:
-                       if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
+                       if (CURLM_OK != curl_multi_setopt(curl->handle->multi, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
                                return FAILURE;
                        }
                        break;
index 1335aaa..ebb9b0e 100644 (file)
@@ -12,42 +12,58 @@ include "helper/server.inc";
 
 echo "Test\n";
 
+function dump($f) {
+       return;
+       readfile($f);
+}
+
 $tmpfile = tempnam(sys_get_temp_dir(), "cookie.");
 $request = new http\Client\Request("GET", "http://localhost");
 $request->setOptions(array("cookiestore" => $tmpfile));
 
-server("cookie.inc", function($port) use($request) {
+server("cookie.inc", function($port) use($request, $tmpfile) {
        $request->setOptions(array("port" => $port));
        $client = new http\Client;
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
 });
-
-server("cookie.inc", function($port) use($request) {
+server("cookie.inc", function($port) use($request, $tmpfile) {
        $request->setOptions(array("port" => $port));
        $client = new http\Client;
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+#dump($tmpfile);
 });
-
-server("cookie.inc", function($port) use($request) {
+       
+server("cookie.inc", function($port) use($request, $tmpfile) {
        $request->setOptions(array("port" => $port, "cookiesession" => true));
        $client = new http\Client;
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
 });
-
-server("cookie.inc", function($port) use($request) {
+       
+server("cookie.inc", function($port) use($request, $tmpfile) {
        $request->setOptions(array("port" => $port, "cookiesession" => false));
        $client = new http\Client;
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
        echo $client->requeue($request)->send()->getResponse();
+dump($tmpfile);
 });
-
+       
 unlink($tmpfile);
 
 ?>
diff --git a/tests/client027.phpt b/tests/client027.phpt
new file mode 100644 (file)
index 0000000..5d81194
--- /dev/null
@@ -0,0 +1,50 @@
+--TEST--
+client cookie woes
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+include "helper/dump.inc";
+
+echo "Test\n";
+
+server("cookie.inc", function($port) {
+       $client = new http\Client(null, "cookies");
+       $client->configure(["pipelining" => false]);
+       $request = new http\Client\Request("GET", "http://localhost:$port?r1");
+       $client->enqueue($request);
+       $client->send();
+       while (($r = $client->getResponse())) {
+               dump_headers(null, $r->getHeaders());
+       }
+       $client->requeue($request);
+       $request = new http\Client\Request("GET", "http://localhost:$port?r2");
+       $client->enqueue($request);
+       $client->send();
+       while (($r = $client->getResponse())) {
+               dump_headers(null, $r->getHeaders());
+       }
+});
+
+?>
+===DONE===
+--EXPECTF--
+Test
+Etag: ""
+Set-Cookie: counter=1;
+X-Original-Transfer-Encoding: chunked
+
+Etag: ""
+Set-Cookie: counter=2;
+X-Original-Transfer-Encoding: chunked
+
+Etag: ""
+Set-Cookie: counter=2;
+X-Original-Transfer-Encoding: chunked
+
+===DONE===
index 5f5f367..452a715 100644 (file)
@@ -1,16 +1,22 @@
 <?php
 
-function dump_message($stream, http\Message $msg, $parent = false) {
+function dump_headers($stream, array $headers) {
        if (!is_resource($stream)) {
                $stream = fopen("php://output", "w");
        }
-       fprintf($stream, "%s\n", $msg->getInfo());
-       $headers = $msg->getHeaders();
        ksort($headers);
        foreach ($headers as $key => $val) {
                fprintf($stream, "%s: %s\n", $key, $val);
        }
        fprintf($stream, "\n");
+}
+
+function dump_message($stream, http\Message $msg, $parent = false) {
+       if (!is_resource($stream)) {
+               $stream = fopen("php://output", "w");
+       }
+       fprintf($stream, "%s\n", $msg->getInfo());
+       dump_headers($stream, $msg->getHeaders());
        $msg->getBody()->toStream($stream);
        
        if ($parent && ($msg = $msg->getParentMessage())) {
index 029cd46..420960c 100644 (file)
@@ -43,11 +43,12 @@ function serve($cb) {
                                                stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER);
                                        }
                                        try {
-                                               while (!feof($client)) {
+                                               $R = array($client);
+                                               while (!feof($client) && stream_select($R, $W, $E, 1, 0)) {
                                                        logger("serve: Handle client %d", (int) $client);
                                                        $cb($client);
                                                }
-                                               logger("serve: EOF on client %d", (int) $client);
+                                               logger("serve: EOF/timeout on client %d", (int) $client);
                                        } catch (Exception $ex) {
                                                logger("serve: Exception on client %d: %s", (int) $client, $ex->getMessage());
                                                /* ignore disconnect */