- reflection awareness of HttpRequest
[m6w6/ext-http] / http_methods.c
index 980245c6f3bca12fa4d0659d25368a16fe1ebf73..c2b13ef091daac92aaf4833e7cafd9439646f132 100644 (file)
 #ifdef HAVE_CONFIG_H
 #      include "config.h"
 #endif
-
 #include "php.h"
+
+#ifdef ZEND_ENGINE_2
+
 #include "php_streams.h"
 
 #include "php_http.h"
@@ -27,6 +29,7 @@
 #include "php_http_api.h"
 #include "php_http_cache_api.h"
 #include "php_http_request_api.h"
+#include "php_http_request_pool_api.h"
 #include "php_http_date_api.h"
 #include "php_http_headers_api.h"
 #include "php_http_message_api.h"
 #include "php_http_message_object.h"
 #include "php_http_response_object.h"
 #include "php_http_request_object.h"
+#include "php_http_requestpool_object.h"
 #include "php_http_exception_object.h"
 
-#ifdef ZEND_ENGINE_2
-
 #include "missing.h"
 
-ZEND_EXTERN_MODULE_GLOBALS(http)
+ZEND_EXTERN_MODULE_GLOBALS(http);
 
 /* {{{ HttpResponse */
 
@@ -51,8 +53,6 @@ ZEND_EXTERN_MODULE_GLOBALS(http)
  * Instantiates a new HttpResponse object, which can be used to send
  * any data/resource/file to an HTTP client with caching and multiple
  * ranges/resuming support.
- *
- * NOTE: GZIPping is not implemented yet.
  */
 PHP_METHOD(HttpResponse, __construct)
 {
@@ -68,6 +68,37 @@ PHP_METHOD(HttpResponse, __construct)
 }
 /* }}} */
 
+/* {{{ proto void HttpResponse::__destruct()
+ *
+ * -
+ */
+PHP_METHOD(HttpResponse, __destruct)
+{
+       getObject(http_response_object, obj);
+       zval *catch_ob = GET_PROP(obj, catch_ob), *sent = GET_PROP(obj, sent);
+       fprintf(stderr, "DTOR!\n"); fflush(stderr);
+       if (!Z_LVAL_P(sent) && Z_LVAL_P(catch_ob)) {
+               zval ob_data;
+
+               /* fetch catched output buffer */
+               if (SUCCESS == php_ob_get_buffer(&ob_data TSRMLS_CC)) {
+                       zval *lmod = GET_PROP(obj, lastModified);
+
+                       SET_PROP(obj, data, &ob_data);
+                       UPD_PROP(obj, long, send_mode, SEND_DATA);
+
+                       if (!Z_LVAL_P(lmod)) {
+                               UPD_PROP(obj, long, lastModified, http_last_modified(&ob_data, SEND_DATA));
+                       }
+                       zval_dtor(&ob_data);
+
+                       http_response_object_sendhandler(getThis(), obj, 1, return_value);
+                       RETVAL_NULL();
+               }
+       }
+}
+/* }}} */
+
 /* {{{ proto bool HttpResponse::setCache(bool cache)
  *
  * Whether it sould be attempted to cache the entitity.
@@ -525,15 +556,40 @@ PHP_METHOD(HttpResponse, getFile)
 PHP_METHOD(HttpResponse, send)
 {
        zend_bool clean_ob = 1;
-       zval *do_cache, *do_gzip;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean_ob)) {
+               RETURN_FALSE;
+       }
+
+       http_response_object_sendhandler(getThis(), NULL, clean_ob, return_value);
+}
+/* }}} */
+
+/* {{{ proto bool HttpResponse::catchOutput([bool clean_ob = false])
+ *
+ * Use this method instead of HttpResponse::set*() and HttpResponse::send()
+ * to let HttpResponse catch and handle the scripts output (i.e. echo/print).
+ *
+ * Example:
+ * <pre>
+ * <?php
+ * $r = new HttpResponse(true, true);
+ * $r->setContentType('text/html; charset=utf-8');
+ * $r->catchOutput(true);
+ * // script follows
+ * ?>
+ * </pre>
+ */
+PHP_METHOD(HttpResponse, catchOutput)
+{
+       zend_bool clean_ob = 0;
        getObject(http_response_object, obj);
 
        if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean_ob)) {
                RETURN_FALSE;
        }
 
-       do_cache = GET_PROP(obj, cache);
-       do_gzip  = GET_PROP(obj, gzip);
+       UPD_PROP(obj, long, catch_ob, 1);
 
        if (clean_ob) {
                /* interrupt on-the-fly etag generation */
@@ -542,93 +598,13 @@ PHP_METHOD(HttpResponse, send)
                php_end_ob_buffers(0 TSRMLS_CC);
        }
 
-       /* gzip */
-       if (Z_LVAL_P(do_gzip)) {
-               php_start_ob_buffer_named("ob_gzhandler", 0, 1 TSRMLS_CC);
-       }
-
-       /* caching */
-       if (Z_LVAL_P(do_cache)) {
-               char *cc_hdr;
-               int cc_len;
-               zval *cctrl, *etag, *lmod, *ccraw;
-
-               etag  = GET_PROP(obj, eTag);
-               lmod  = GET_PROP(obj, lastModified);
-               cctrl = GET_PROP(obj, cacheControl);
-               ccraw = GET_PROP(obj, raw_cache_header);
-
-               if (Z_LVAL_P(ccraw)) {
-                       cc_hdr = Z_STRVAL_P(cctrl);
-                       cc_len = Z_STRLEN_P(cctrl);
-               } else {
-                       char cc_header[42] = {0};
-                       sprintf(cc_header, "%s, must-revalidate, max-age=0", Z_STRVAL_P(cctrl));
-                       cc_hdr = cc_header;
-                       cc_len = Z_STRLEN_P(cctrl) + lenof(", must-revalidate, max-age=0");
-               }
-
-               http_cache_etag(Z_STRVAL_P(etag), Z_STRLEN_P(etag), cc_hdr, cc_len);
-               http_cache_last_modified(Z_LVAL_P(lmod), Z_LVAL_P(lmod) ? Z_LVAL_P(lmod) : time(NULL), cc_hdr, cc_len);
-       }
-
-       /* content type */
-       {
-               zval *ctype = GET_PROP(obj, contentType);
-               if (Z_STRLEN_P(ctype)) {
-                       http_send_content_type(Z_STRVAL_P(ctype), Z_STRLEN_P(ctype));
-               } else {
-                       http_send_content_type("application/x-octetstream", lenof("application/x-octetstream"));
-               }
-       }
-
-       /* content disposition */
-       {
-               zval *dispo_file = GET_PROP(obj, dispoFile);
-               if (Z_STRLEN_P(dispo_file)) {
-                       zval *dispo_inline = GET_PROP(obj, dispoInline);
-                       http_send_content_disposition(Z_STRVAL_P(dispo_file), Z_STRLEN_P(dispo_file), (zend_bool) Z_LVAL_P(dispo_inline));
-               }
-       }
-
-       /* throttling */
-       {
-               zval *send_buffersize, *throttle_delay;
-               send_buffersize = GET_PROP(obj, sendBuffersize);
-               throttle_delay  = GET_PROP(obj, throttleDelay);
-               HTTP_G(send).buffer_size    = Z_LVAL_P(send_buffersize);
-               HTTP_G(send).throttle_delay = Z_DVAL_P(throttle_delay);
-       }
-
-       /* send */
-       {
-               zval *send_mode = GET_PROP(obj, send_mode);
-               switch (Z_LVAL_P(send_mode))
-               {
-                       case SEND_DATA:
-                       {
-                               zval *zdata = GET_PROP(obj, data);
-                               RETURN_SUCCESS(http_send_data(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata)));
-                       }
-
-                       case SEND_RSRC:
-                       {
-                               php_stream *the_real_stream;
-                               zval *the_stream = GET_PROP(obj, stream);
-                               php_stream_from_zval(the_real_stream, &the_stream);
-                               RETURN_SUCCESS(http_send_stream(the_real_stream));
-                       }
-
-                       default:
-                       {
-                               zval *zfile = GET_PROP(obj, file);
-                               RETURN_SUCCESS(http_send_file(Z_STRVAL_P(zfile)));
-                       }
-               }
+       if (SUCCESS != php_start_ob_buffer(NULL, 0, 1 TSRMLS_CC)) {
+               RETURN_FALSE;
        }
+       
+       RETURN_TRUE;
 }
 /* }}} */
-/* }}} */
 
 /* {{{ HttpMessage */
 
@@ -1131,7 +1107,7 @@ PHP_METHOD(HttpRequest, __destruct)
 PHP_METHOD(HttpRequest, setOptions)
 {
        char *key = NULL;
-       long idx = 0;
+       ulong idx = 0;
        zval *opts, *old_opts, **opt;
        getObject(http_request_object, obj);
 
@@ -1397,11 +1373,11 @@ PHP_METHOD(HttpRequest, unsetCookies)
 }
 /* }}} */
 
-/* {{{ proto bool HttpRequest::setURL(string url)
+/* {{{ proto bool HttpRequest::setUrl(string url)
  *
  * Set the request URL.
  */
-PHP_METHOD(HttpRequest, setURL)
+PHP_METHOD(HttpRequest, setUrl)
 {
        char *URL = NULL;
        int URL_len;
@@ -1420,7 +1396,7 @@ PHP_METHOD(HttpRequest, setURL)
  *
  * Get the previously set request URL.
  */
-PHP_METHOD(HttpRequest, getURL)
+PHP_METHOD(HttpRequest, getUrl)
 {
        NO_ARGS;
 
@@ -1726,6 +1702,29 @@ PHP_METHOD(HttpRequest, addPostFile)
 }
 /* }}} */
 
+/* {{{ proto bool HttpRequest::setPostFiles(array post_files)
+ *
+ * Set files to post.
+ * Overwrites previously set post files.
+ * Affects only POST requests.
+ */
+PHP_METHOD(HttpRequest, setPostFiles)
+{
+       zval *files, *pFiles;
+       getObject(http_request_object, obj);
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &files)) {
+               RETURN_FALSE;
+       }
+
+       pFiles = GET_PROP(obj, postFiles);
+       zend_hash_clean(Z_ARRVAL_P(pFiles));
+       array_copy(files, pFiles);
+
+       RETURN_TRUE;
+}
+/* }}} */
+
 /* {{{ proto array HttpRequest::getPostFiles()
  *
  * Get all previously added POST files.
@@ -1763,6 +1762,59 @@ PHP_METHOD(HttpRequest, unsetPostFiles)
 }
 /* }}} */
 
+/* {{{ proto bool HttpRequest::SetPutFile(string file)
+ *
+ * Set file to put.
+ * Affects only PUT requests.
+ */
+PHP_METHOD(HttpRequest, setPutFile)
+{
+       char *file;
+       int file_len;
+       getObject(http_request_object, obj);
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &file, &file_len)) {
+               RETURN_FALSE;
+       }
+
+       UPD_PROP(obj, string, putFile, file);
+       RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto string HttpRequest::getPutFile()
+ *
+ * Get previously set put file.
+ */
+PHP_METHOD(HttpRequest, getPutFile)
+{
+       NO_ARGS;
+
+       IF_RETVAL_USED {
+               zval *putfile;
+               getObject(http_request_object, obj);
+
+               putfile = GET_PROP(obj, putFile);
+               RETVAL_STRINGL(Z_STRVAL_P(putfile), Z_STRLEN_P(putfile), 1);
+       }
+}
+/* }}} */
+
+/* {{{ proto void HttpRequest::unsetPutFile()
+ *
+ * Unset file to put.
+ * Affects only PUT requests.
+ */
+PHP_METHOD(HttpRequest, unsetPutFile)
+{
+       getObject(http_request_object, obj);
+
+       NO_ARGS;
+
+       UPD_PROP(obj, string, putFile, "");
+}
+/* }}} */
+
 /* {{{ proto array HttpRequest::getResponseData()
  *
  * Get all response data after the request has been sent.
@@ -1991,12 +2043,14 @@ PHP_METHOD(HttpRequest, getResponseMessage)
                getObject(http_request_object, obj);
 
                message = GET_PROP(obj, responseMessage);
-               Z_TYPE_P(return_value) = IS_OBJECT;
-               return_value->is_ref = 1;
-               return_value->value.obj = message->value.obj;
-               zval_add_ref(&return_value);
+               if (Z_TYPE_P(message) == IS_OBJECT) {
+                       RETVAL_OBJECT(message);
+               } else {
+                       RETURN_NULL();
+               }
        }
 }
+/* }}} */
 
 /* {{{ proto bool HttpRequest::send()
  *
@@ -2035,130 +2089,219 @@ PHP_METHOD(HttpRequest, getResponseMessage)
 PHP_METHOD(HttpRequest, send)
 {
        STATUS status = FAILURE;
-       zval *meth, *URL, *qdata, *opts, *info, *resp;
-       char *request_uri;
+       http_request_body body = {0};
        getObject(http_request_object, obj);
 
        NO_ARGS;
 
        SET_EH_THROW_HTTP();
 
-       if ((!obj->ch) && (!(obj->ch = curl_easy_init()))) {
-               http_error(E_WARNING, HTTP_E_CURL, "Could not initilaize curl");
+       if (obj->pool) {
+               http_error(E_WARNING, HTTP_E_CURL, "Cannot perform HttpRequest::send() while attached to an HttpRequestPool");
+               SET_EH_NORMAL();
                RETURN_FALSE;
        }
 
-       meth  = GET_PROP(obj, method);
-       URL   = GET_PROP(obj, url);
-       qdata = GET_PROP(obj, queryData);
-       opts  = GET_PROP(obj, options);
-       info  = GET_PROP(obj, responseInfo);
-       resp  = GET_PROP(obj, responseData);
-
-       // HTTP_URI_MAXLEN+1 long char *
-       if (!(request_uri = http_absolute_uri_ex(Z_STRVAL_P(URL), Z_STRLEN_P(URL), NULL, 0, NULL, 0, 0))) {
-               RETURN_FALSE;
+       if (SUCCESS == (status = http_request_object_requesthandler(obj, getThis(), &body))) {
+               status = http_request_exec(obj->ch, NULL);
        }
+       http_request_body_dtor(&body);
 
-       if (Z_STRLEN_P(qdata) && (strlen(request_uri) < HTTP_URI_MAXLEN)) {
-               if (!strchr(request_uri, '?')) {
-                       strcat(request_uri, "?");
-               } else {
-                       strcat(request_uri, "&");
-               }
-               strncat(request_uri, Z_STRVAL_P(qdata), HTTP_URI_MAXLEN - strlen(request_uri));
+       /* final data handling */
+       if (SUCCESS == status) {
+               status = http_request_object_responsehandler(obj, getThis());
        }
 
-       switch (Z_LVAL_P(meth))
-       {
-               case HTTP_GET:
-               case HTTP_HEAD:
-                       status = http_request_ex(obj->ch, Z_LVAL_P(meth), request_uri, NULL, Z_ARRVAL_P(opts), Z_ARRVAL_P(info), &obj->response);
-               break;
+       SET_EH_NORMAL();
+       RETURN_SUCCESS(status);
+}
+/* }}} */
 
-               case HTTP_PUT:
-               {
-                       http_request_body body;
-                       php_stream *stream;
-                       php_stream_statbuf ssb;
-                       zval *file = GET_PROP(obj, putFile);
-
-                       if (    (stream = php_stream_open_wrapper(Z_STRVAL_P(file), "rb", REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL)) &&
-                                       !php_stream_stat(stream, &ssb)) {
-                               body.type = HTTP_REQUEST_BODY_UPLOADFILE;
-                               body.data = stream;
-                               body.size = ssb.sb.st_size;
-
-                               status = http_put_ex(obj->ch, request_uri, &body, Z_ARRVAL_P(opts), Z_ARRVAL_P(info), &obj->response);
-                               http_request_body_dtor(&body);
-                       } else {
-                               status = FAILURE;
-                       }
-               }
-               break;
+/* {{{ HttpRequestPool */
 
-               case HTTP_POST:
-               {
-                       http_request_body body;
-                       zval *fields = GET_PROP(obj, postFields), *files = GET_PROP(obj, postFiles);
+/* {{{ proto void HttpRequestPool::__construct([HttpRequest request[, ...]])
+ *
+ * Instantiate a new HttpRequestPool object.  An HttpRequestPool is
+ * able to send several HttpRequests in parallel.
+ *
+ * Example:
+ * <pre>
+ * <?php
+ *     $urls = array('www.php.net', 'pecl.php.net', 'pear.php.net')
+ *     $pool = new HttpRequestPool;
+ *     foreach ($urls as $url) {
+ *         $req[$url] = new HttpRequest("http://$url", HTTP_HEAD);
+ *         $pool->attach($req[$url]);
+ *     }
+ *     $pool->send();
+ *     foreach ($urls as $url) {
+ *         printf("%s (%s) is %s\n",
+ *             $url, $req[$url]->getResponseInfo('effective_url'),
+ *             $r->getResponseCode() == 200 ? 'alive' : 'not alive'
+ *         );
+ *     }
+ * ?>
+ * </pre>
+ */
+PHP_METHOD(HttpRequestPool, __construct)
+{
+       int argc = ZEND_NUM_ARGS();
+       zval ***argv = safe_emalloc(argc, sizeof(zval *), 0);
+       getObject(http_requestpool_object, obj);
+
+       if (SUCCESS == zend_get_parameters_array_ex(argc, argv)) {
+               int i;
 
-                       if (SUCCESS == (status = http_request_body_fill(&body, Z_ARRVAL_P(fields), Z_ARRVAL_P(files)))) {
-                               status = http_post_ex(obj->ch, request_uri, &body, Z_ARRVAL_P(opts), Z_ARRVAL_P(info), &obj->response);
-                               http_request_body_dtor(&body);
+               for (i = 0; i < argc; ++i) {
+                       if (Z_TYPE_PP(argv[i]) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(argv[i]), http_request_object_ce TSRMLS_CC)) {
+                               http_request_pool_attach(&obj->pool, *(argv[i]));
                        }
                }
-               break;
-
-               default:
-               {
-                       http_request_body body;
-                       zval *post = GET_PROP(obj, postData);
+       }
+       efree(argv);
+}
+/* }}} */
 
-                       body.type = HTTP_REQUEST_BODY_CSTRING;
-                       body.data = Z_STRVAL_P(post);
-                       body.size = Z_STRLEN_P(post);
+/* {{{ proto void HttpRequestPool::__destruct()
+ *
+ * Clean up HttpRequestPool object.
+ */
+PHP_METHOD(HttpRequestPool, __destruct)
+{
+       getObject(http_requestpool_object, obj);
 
-                       status = http_request_ex(obj->ch, Z_LVAL_P(meth), request_uri, &body, Z_ARRVAL_P(opts), Z_ARRVAL_P(info), &obj->response);
-               }
-               break;
-       }
+       NO_ARGS;
 
-       efree(request_uri);
+       http_request_pool_detach_all(&obj->pool);
+}
+/* }}} */
 
-       /* final data handling */
-       if (status == SUCCESS) {
-               http_message *msg;
+/* {{{ proto void HttpRequestPool::reset()
+ *
+ * Detach all attached HttpRequest objects.
+ */
+PHP_METHOD(HttpRequestPool, reset)
+{
+       getObject(http_requestpool_object, obj);
 
-               if (msg = http_message_parse(PHPSTR_VAL(&obj->response), PHPSTR_LEN(&obj->response))) {
-                       zval *headers, *message;
-                       char *body;
-                       size_t body_len;
+       NO_ARGS;
 
-                       UPD_PROP(obj, long, responseCode, msg->info.response.code);
+       http_request_pool_detach_all(&obj->pool);
+}
 
-                       MAKE_STD_ZVAL(headers)
-                       array_init(headers);
+/* {{{ proto bool HttpRequestPool::attach(HttpRequest request)
+ *
+ * Attach an HttpRequest object to this HttpRequestPool.
+ * NOTE: set all options prior attaching!
+ */
+PHP_METHOD(HttpRequestPool, attach)
+{
+       zval *request;
+       STATUS status = FAILURE;
+       getObject(http_requestpool_object, obj);
 
-                       zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
-                       phpstr_data(PHPSTR(msg), &body, &body_len);
+       SET_EH_THROW_HTTP();
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, http_request_object_ce)) {
+               status = http_request_pool_attach(&obj->pool, request);
+       }
+       SET_EH_NORMAL();
+       RETURN_SUCCESS(status);
+}
+/* }}} */
 
-                       add_assoc_zval(resp, "headers", headers);
-                       add_assoc_stringl(resp, "body", body, body_len, 0);
+/* {{{ proto bool HttpRequestPool::detach(HttpRequest request)
+ *
+ * Detach an HttpRequest object from this HttpRequestPool.
+ */
+PHP_METHOD(HttpRequestPool, detach)
+{
+       zval *request;
+       STATUS status = FAILURE;
+       getObject(http_requestpool_object, obj);
 
-                       message = GET_PROP(obj, responseMessage);
-                       zval_dtor(message);
-                       Z_TYPE_P(message)  = IS_OBJECT;
-                       message->value.obj = http_message_object_from_msg(msg);
-                       SET_PROP(obj, responseMessage, message);
-               } else {
-                       status = FAILURE;
-               }
+       SET_EH_THROW_HTTP();
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, http_request_object_ce)) {
+               status = http_request_pool_detach(&obj->pool, request);
        }
+       SET_EH_NORMAL();
+       RETURN_SUCCESS(status);
+}
+/* }}} */
 
+/* {{{ proto bool HttpRequestPool::send()
+ *
+ * Send all attached HttpRequest objects in parallel.
+ */
+PHP_METHOD(HttpRequestPool, send)
+{
+       STATUS status;
+       getObject(http_requestpool_object, obj);
+
+       NO_ARGS;
+
+       SET_EH_THROW_HTTP();
+       status = http_request_pool_send(&obj->pool);
        SET_EH_NORMAL();
+
        RETURN_SUCCESS(status);
 }
 /* }}} */
+
+/* {{{ proto protected bool HttpRequestPool::socketSend()
+ *
+ * Usage:
+ * <pre>
+ * <?php
+ *     while ($pool->socketSend()) {
+ *         do_something_else();
+ *         if (!$pool->socketSelect()) {
+ *             die('Socket error');
+ *         }
+ *     }
+ *     $pool->socketRead();
+ * ?>
+ * </pre>
+ */
+PHP_METHOD(HttpRequestPool, socketSend)
+{
+       getObject(http_requestpool_object, obj);
+
+       NO_ARGS;
+
+       RETURN_BOOL(0 < http_request_pool_perform(&obj->pool));
+}
+/* }}} */
+
+/* {{{ proto protected bool HttpRequestPool::socketSelect()
+ *
+ * See HttpRequestPool::socketSend().
+ */
+PHP_METHOD(HttpRequestPool, socketSelect)
+{
+       getObject(http_requestpool_object, obj);
+
+       NO_ARGS;
+
+       RETURN_SUCCESS(http_request_pool_select(&obj->pool));
+}
+/* }}} */
+
+/* {{{ proto protected void HttpRequestPool::socketRead()
+ *
+ * See HttpRequestPool::socketSend().
+ */
+PHP_METHOD(HttpRequestPool, socketRead)
+{
+       getObject(http_requestpool_object, obj);
+
+       NO_ARGS;
+
+       zend_llist_apply(&obj->pool.handles, (llist_apply_func_t) http_request_pool_responsehandler TSRMLS_CC);
+}
+/* }}} */
+
+/* }}} */
+
 /* }}} */
 #endif /* HTTP_HAVE_CURL */
 
@@ -2172,3 +2315,4 @@ PHP_METHOD(HttpRequest, send)
  * vim600: noet sw=4 ts=4 fdm=marker
  * vim<600: noet sw=4 ts=4
  */
+