Commits

Ruslan Osmanov committed 89831bf

Add: HTTP server support, EventHttpRequest class
Fix: turn off buggy libevent thread lock debugging

Comments (0)

Files changed (12)

 #include "src/common.h"
 #include "src/util.h"
 #include "src/priv.h"
+#include "classes/http.h"
 
 /* {{{ Private */
 
+/* {{{ _new_http_cb
+ * Allocate memory for new callback structure for the next HTTP server's URI */
+static zend_always_inline php_event_http_cb_t *_new_http_cb(zval *zarg, const zend_fcall_info *fci, const zend_fcall_info_cache *fcc TSRMLS_CC)
+{
+	php_event_http_cb_t *cb = emalloc(sizeof(php_event_http_cb_t));
+
+	if (zarg) {
+		Z_ADDREF_P(zarg);
+	}
+
+	cb->data = zarg;
+
+	PHP_EVENT_COPY_FCALL_INFO(cb->fci, cb->fcc, fci, fcc);
+
+	TSRMLS_SET_CTX(cb->thread_ctx);
+
+	cb->next = NULL;
+
+	return cb;
+}
+/* }}} */
+
 /* {{{ _http_callback */
 static void _http_callback(struct evhttp_request *req, void *arg)
 {
-	php_event_http_t *http = (php_event_http_t *) arg;
-	PHP_EVENT_ASSERT(http);
+	php_event_http_cb_t *cb = (php_event_http_cb_t *) arg;
+	PHP_EVENT_ASSERT(cb);
 
 	php_event_http_req_t *http_req;
 
-	zend_fcall_info       *pfci = http->fci;
-	zend_fcall_info_cache *pfcc = http->fcc;
+	zend_fcall_info       *pfci = cb->fci;
+	zend_fcall_info_cache *pfcc = cb->fcc;
 	PHP_EVENT_ASSERT(pfci && pfcc);
 
-	TSRMLS_FETCH_FROM_CTX(http->thread_ctx);
+	TSRMLS_FETCH_FROM_CTX(cb->thread_ctx);
 
 	/* Call userspace function according to
 	 * proto void callback(EventHttpRequest req, mixed data);*/
 
-	zval  *arg_data = http->data;
+	zval  *arg_data = cb->data;
 	zval  *arg_req;
 	zval **args[2];
 	zval  *retval_ptr;
 	MAKE_STD_ZVAL(arg_req);
 	PHP_EVENT_INIT_CLASS_OBJECT(arg_req, php_event_http_req_ce);
 	PHP_EVENT_FETCH_HTTP_REQ(http_req, arg_req);
-	http_req->ptr = req;
+	http_req->ptr      = req;
+	http_req->internal = 1; /* Don't evhttp_request_free(req) */
 	Z_ADDREF_P(arg_req);
 	args[0] = &arg_req;
 
 
 /* }}} */
 
+/* {{{  _php_event_free_http_cb */
+void _php_event_free_http_cb(php_event_http_cb_t *cb)
+{
+	if (cb->data) {
+		zval_ptr_dtor(&cb->data);
+		cb->data = NULL;
+	}
+
+	PHP_EVENT_FREE_FCALL_INFO(cb->fci, cb->fcc);
+
+	efree(cb);
+}
+/* }}} */
+
 /* {{{ proto EventHttp EventHttp::__construct(EventBase base);
- *
  * Creates new http server object.
  */
 PHP_METHOD(EventHttp, __construct)
 
 	http_ptr = evhttp_new(b->base);
 	if (!http_ptr) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING,
+				"Failed to allocate space for new HTTP server(evhttp_new)");
 		return;
 	}
 	http->ptr = http_ptr;
 	http->base = zbase;
 	Z_ADDREF_P(zbase);
 
-	http->stream_id = -1;
-	http->fci       = http->fcc      = NULL;
-	http->data      = http->gen_data = NULL;
+	http->fci     = NULL;
+	http->fcc     = NULL;
+	http->data    = NULL;
+	http->cb_head = NULL;
 }
 /* }}} */
 
 		RETURN_FALSE;
 	}
 
-#if 0
-	http->stream_id = Z_LVAL_P(ppzfd);
-	zend_list_addref(Z_LVAL_P(ppzfd));
-#endif
-
 	RETVAL_TRUE;
 }
 /* }}} */
  * Can be called multiple times to bind the same http server to multiple different ports. */
 PHP_METHOD(EventHttp, bind)
 {
-	zval              *zhttp = getThis();
-	php_event_http_t  *http;
-	char *address;
-	int address_len;
-	long port;
+	zval             *zhttp       = getThis();
+	php_event_http_t *http;
+	char             *address;
+	int               address_len;
+	long              port;
 
 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl",
 				&address, &address_len, &port) == FAILURE) {
 
 	PHP_EVENT_FETCH_HTTP(http, zhttp);
 
+	/* XXX Call evhttp_bind_socket_with_handle instead, and store the bound
+	 * socket in the internal struct for further useful API? */
 	if (evhttp_bind_socket(http->ptr, address, port)) {
 		RETURN_FALSE;
 	}
 /* }}} */
 
 /* {{{ proto bool EventHttp::setCallback(string path, callable cb[, mixed arg = NULL]);
- * Set a callback for a specified URI.
+ * Sets a callback for specified URI.
  */
 PHP_METHOD(EventHttp, setCallback)
 {
 	zend_fcall_info_cache  fcc      = empty_fcall_info_cache;
 	zval                  *zarg     = NULL;
 	int                    res;
+	php_event_http_cb_t   *cb;
+	php_event_http_cb_t   *cb_head;
 
 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sf|z!",
 				&path, &path_len, &fci, &fcc, &zarg) == FAILURE) {
 
 	PHP_EVENT_FETCH_HTTP(http, zhttp);
 
-	res = evhttp_set_cb(http->ptr, path, _http_callback, (void *) http);
+	cb = _new_http_cb(zarg, &fci, &fcc TSRMLS_CC);
+	PHP_EVENT_ASSERT(cb);
+
+	res = evhttp_set_cb(http->ptr, path, _http_callback, (void *) cb);
 	if (res == -2) {
+		_php_event_free_http_cb(cb);
+
 		RETURN_FALSE;
 	}
 	if (res == -1) { // the callback existed already
+		_php_event_free_http_cb(cb);
+
 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
 				"The callback already exists");
 		RETURN_FALSE;
 	}
 
-	if (http->data) {
-		zval_ptr_dtor(&http->data);
-	}
-	if (zarg) {
-		Z_ADDREF_P(zarg);
-	}
-	http->data = zarg;
-
-	/* 
-	 * XXX We should set up individual user functions for every path!!!
-	 */
-
-	PHP_EVENT_COPY_FCALL_INFO(http->fci, http->fcc, &fci, &fcc);
-
-	TSRMLS_SET_CTX(http->thread_ctx);
+	cb_head       = http->cb_head;
+	http->cb_head = cb;
+	cb->next      = cb_head;
 
 	RETVAL_TRUE;
 }
  * If not supported they will generate a <literal>"405 Method not
  * allowed"</literal> response.
  *
- * By default this includes the following methods: GET, POST, HEAD, PUT, DELETE
+ * By default this includes the following methods: GET, POST, HEAD, PUT, DELETE.
+ * See <literal>EventHttpRequest::CMD_*</literal> constants.
  */
 PHP_METHOD(EventHttp, setAllowedMethods)
 {
 	php_event_http_t *http;
 	long              methods;
 
-
 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
 				&methods) == FAILURE) {
 		return;
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Ruslan Osmanov <osmanov@php.net>                             |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_EVENT_HTTP_H
+#define PHP_EVENT_HTTP_H
+
+void _php_event_free_http_cb(php_event_http_cb_t *cb);
+
+#endif /* PHP_EVENT_HTTP_H */
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 sts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4 sts=4
+ */

classes/http_request.c

 #include "src/priv.h"
 #include "classes/http.h"
 
+/* {{{ EventHttpRequest::__construct */
+PHP_METHOD(EventHttpRequest, __construct)
+{
+	php_event_http_req_t *http_req;
+
+	if (zend_parse_parameters_none() == FAILURE) {
+		return;
+	}
+
+	PHP_EVENT_FETCH_HTTP_REQ(http_req, getThis());
+	http_req->ptr = NULL;
+}
+/* }}} */
+
 /* {{{ proto int EventHttpRequest::getCommand(void);
  * Returns the request command, one of EventHttpRequest::CMD_* constants. XXX Make property? */
 PHP_METHOD(EventHttpRequest, getCommand)
 PHP_METHOD(EventHttpRequest, getUri)
 {
 	php_event_http_req_t *http_req;
-	char *uri;
 
 	if (zend_parse_parameters_none() == FAILURE) {
 		return;
 		RETURN_FALSE;
 	}
 
-	uri = evhttp_request_get_uri(http_req->ptr);
-	RETVAL_STRING(uri, 1);
-	free(uri);
+	RETVAL_STRING(evhttp_request_get_uri(http_req->ptr), 1);
 }
 /* }}} */
 
 			header = header->next.tqe_next) {
 		add_assoc_string(return_value, header->key, header->value, 1);
 	}
+
+
 }
 /* }}} */
 
 		PHP_EVENT_ASSERT(b->buf);
 	}
 
-	evhttp_send_reply_chunk(http_req->ptr, code, reason, b->buf);
+	evhttp_send_reply_chunk(http_req->ptr, b->buf);
 }
 /* }}} */
 
  * <method>EventHttpRequest::sendReplyChunk</method> and complete the reply by
  * calling <method>EventHttpRequest::sendReplyEnd</method>.
  */
-PHP_METHOD(EventHttpRequest, sendReplyEnd)
+PHP_METHOD(EventHttpRequest, sendReplyStart)
 {
 	php_event_http_req_t *http_req;
 	long                  code;

examples/http.php

+<?php
+
+function _http_dump($req, $data) {
+	static $counter      = 0;
+	static $max_requests = 2;
+
+	if (++$counter >= $max_requests)  {
+		echo "Counter reached max requests $max_requests. Exiting\n";
+		exit();
+	}
+
+	echo __METHOD__, " called\n";
+	echo "request:"; var_dump($req);
+	echo "data:"; var_dump($data);
+
+	echo "\n===== DUMP =====\n";
+	echo "Command:", $req->getCommand(), PHP_EOL;
+	echo "URI:", $req->getUri(), PHP_EOL;
+	echo "Input headers:"; var_dump($req->getInputHeaders());
+	echo "Output headers:"; var_dump($req->getOutputHeaders());
+
+	echo "\n >> Sending reply ...";
+	$req->sendReply(200, "OK");
+	echo "OK\n";
+
+	echo "\n >> Reading input buffer ...\n";
+	$buf = $req->getInputBuffer();
+	while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
+		echo $s, PHP_EOL;
+	}
+	echo "No more data in the buffer\n";
+}
+
+$port = 8010;
+if ($argc > 1) {
+	$port = (int) $argv[1];
+}
+if ($port <= 0 || $port > 65535) {
+	exit("Invalid port");
+}
+
+$base = new EventBase();
+$http = new EventHttp($base);
+$http->setAllowedMethods(EventHttpRequest::CMD_GET | EventHttpRequest::CMD_POST);
+
+$http->setCallback("/dump", "_http_dump", array(4, 8));
+$http->bind("0.0.0.0", 8010);
+$base->loop();
+?>
     <email>osmanov@php.net</email>
     <active>yes</active>
   </lead>
-  <date>2013-03-08</date>
+  <date>2013-03-10</date>
   <!--{{{ Current version -->
   <version>
-    <release>1.3.0</release>
-    <api>1.3.1</api>
+    <release>1.4.0</release>
+    <api>1.4.0</api>
   </version>
   <stability>
     <release>beta</release>
   </stability>
   <license uri="http://www.php.net/license">PHP</license>
   <notes><![CDATA[
-  Fix: possible memory access violations in EventBufferEvent input/output property handlers
-  Change: Event::$timer_pending property removed; generic Event::$pending property added
-  Fix: With OPT_LEAVE_SOCKETS_BLOCKING flag EventListener::__construct turned fd to non-blocking mode
-  Fix: property and class HashTable's were not free'd in MSHUTDOWN
-  Add: Event::$data property
-  Fix: Event::__construct failed with Event::TIMEOUT flag
-  Fix: memory leak in EventBuffer::readLine
-  Add: --with-event-pthreads configure option
-  Fix: EventBase::reInit method's arginfo
+  Add: HTTP server support, EventHttpRequest class
+  Fix: turn off buggy libevent thread lock debugging
   ]]></notes>
   <!--}}}-->
   <!--{{{ Contents -->
         <file role="src" name="event_config.c"/>
         <file role="src" name="event_util.c"/>
         <file role="src" name="http.c"/>
+        <file role="src" name="http.h"/>
         <file role="src" name="http_connection.c"/>
+        <file role="src" name="http_request.c"/>
         <file role="src" name="listener.c"/>
         <file role="src" name="ssl_context.h"/>
         <file role="src" name="ssl_context.c"/>
         <file role="src" name="02-features.phpt"/>
         <file role="src" name="03-event-del.phpt"/>
         <file role="src" name="04-bevent-socket.phpt"/>
+        <file role="src" name="05-buffer-pos.phpt"/>
+        <file role="src" name="06-timer.phpt"/>
       </dir>
     </dir>
   </contents>
   </extsrcrelease>
   <!--{{{ changelog-->
   <changelog>
+    <!--{{{ 1.4.0-beta  -->
+    <release>
+      <version>
+        <release>1.4.0</release>
+        <api>1.4.0</api>
+      </version>
+      <stability>
+        <release>beta</release>
+        <api>beta</api>
+      </stability>
+      <license uri="http://www.php.net/license">PHP</license>
+      <notes><![CDATA[
+  Add: HTTP server support, EventHttpRequest class
+  Fix: turn off buggy libevent thread lock debugging
+  ]]></notes>
+    </release>
+    <!--}}}-->
     <!--{{{ 1.3.0-beta-->
     <release>
       <version>
 #include "src/common.h"
 #include "src/util.h"
 #include "src/priv.h"
+#include "classes/http.h"
 
 #if 0
 ZEND_DECLARE_MODULE_GLOBALS(event)
 static void event_http_object_free_storage(void *ptr TSRMLS_DC)
 {
 	php_event_http_t *http = (php_event_http_t *) ptr;
+	php_event_http_cb_t *cb, *cb_next;
 
 	PHP_EVENT_ASSERT(http);
 
 	PHP_EVENT_FREE_FCALL_INFO(http->fci, http->fcc);
 
+	/* Free attached callbacks */
+	PHP_EVENT_ASSERT(http->cb_head);
+	cb = http->cb_head;
+	while (cb) {
+		cb_next = cb->next;
+		_php_event_free_http_cb(cb);
+		cb = cb_next;
+	}
+
 	if (http->data) {
 		zval_ptr_dtor(&http->data);
 		http->data = NULL;
 /* }}} */
 
 /* {{{ event_http_req_object_free_storage */
-static void event_http_req_object_create(void *ptr TSRMLS_DC)
+static void event_http_req_object_free_storage(void *ptr TSRMLS_DC)
 {
 	php_event_http_req_t *http_req = (php_event_http_req_t *) ptr;
 
 	PHP_EVENT_ASSERT(http_req);
 
-	if (http_req->ptr) {
-		evhttp_request_free(http->ptr);
+	if (!http_req->internal && http_req->ptr) {
+		evhttp_request_free(http_req->ptr);
 		http_req->ptr = NULL;
 	}
 
 #ifndef PHP_EVENT_H
 #define PHP_EVENT_H
 
-#define PHP_EVENT_VERSION "1.3.0-beta"
+#define PHP_EVENT_VERSION "1.4.0-beta"
 
 
 extern zend_module_entry event_module_entry;
 # define PHP_EVENT_SOCKETS_SUPPORT
 #endif
 
+
+#include <event2/keyvalq_struct.h>
 #include <event2/event.h>
 #include <event2/bufferevent.h>
 #include <event2/buffer.h>
 };
 
 const zend_function_entry php_event_http_req_ce_functions[] = {
-	PHP_ABSTRACT_ME(EventHttpRequest, __construct, NULL)
+	PHP_ME(EventHttpRequest, __construct, arginfo_event__void, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
 
 	PHP_ME(EventHttpRequest, getCommand,       arginfo_event__void,                     ZEND_ACC_PUBLIC)
 	PHP_ME(EventHttpRequest, getUri,           arginfo_event__void,                     ZEND_ACC_PUBLIC)
 PHP_METHOD(EventHttp, setCallback);
 PHP_METHOD(EventHttp, setAllowedMethods);
 
+PHP_METHOD(EventHttpRequest, __construct);
 PHP_METHOD(EventHttpRequest, getCommand);
 PHP_METHOD(EventHttpRequest, getUri);
 PHP_METHOD(EventHttpRequest, getInputHeaders);
 	PHP_EVENT_COMMON_THREAD_CTX;
 } php_event_listener_t;
 
-/* Represents EventHttpConnection object */
-typedef struct _php_event_http_conn_t {
-	PHP_EVENT_OBJECT_HEAD;
+typedef struct _php_event_http_cb_t php_event_http_cb_t;
 
-	struct evhttp_connection *conn;
-	zval                     *base;       /* Event base associated with the listener */
-	zval                     *dns_base;   /* Associated EventDnsBase                 */
-} php_event_http_conn_t;
+/* Type for an HTTP server callback */
+struct _php_event_http_cb_t {
+	php_event_http_cb_t   *next;   /* Linked list                         */
+	zval                  *data;   /* User custom data passed to callback */
+	zend_fcall_info       *fci;
+	zend_fcall_info_cache *fcc;
+
+	PHP_EVENT_COMMON_THREAD_CTX;
+};
 
 /* Represents EventHttp object */
-typedef struct {
+typedef struct _php_event_http_t {
 	PHP_EVENT_OBJECT_HEAD;
 
 	struct evhttp         *ptr;
 	zval                  *base;        /* Event base associated with the listener              */
-	zval                  *data;        /* User custom data passed to callback                  */
-	zval                  *gen_data;    /* User custom data passed to the gen(default) callback */
-	int                    stream_id;   /* Resource ID of socket probably being listened        */
+	zval                  *data;        /* User custom data passed to the gen(default) callback */
 
+	/* General(default) callback for evhttp_gencb() */
 	zend_fcall_info       *fci;
 	zend_fcall_info_cache *fcc;
 
+	/* Linked list of attached callbacks */
+	php_event_http_cb_t   *cb_head;
+
 	PHP_EVENT_COMMON_THREAD_CTX;
 } php_event_http_t;
 
+/* Represents EventHttpConnection object */
+typedef struct _php_event_http_conn_t {
+	PHP_EVENT_OBJECT_HEAD;
+
+	struct evhttp_connection *conn;
+	zval                     *base;       /* Event base associated with the listener */
+	zval                     *dns_base;   /* Associated EventDnsBase                 */
+} php_event_http_conn_t;
+
 typedef struct {
 	PHP_EVENT_OBJECT_HEAD;
 
 	struct evhttp_request *ptr;
+   	/* Whether is artificially created object that must not free 'ptr' */
+	zend_bool              internal;
 } php_event_http_req_t;
 
 #endif/* HAVE_EVENT_EXTRA_LIB }}} */

tests/06-timer.phpt

+--TEST--
+Check for timer event basic behaviour 
+--FILE--
+<?php
+$base = new EventBase();
+$e = new Event($base, -1, Event::TIMEOUT, function($fd, $what, $e) {
+	echo "0.4 seconds elapsed";
+	//$e->delTimer();
+});
+$e->data = $e;
+$e->addTimer(0.4);
+$base->loop();
+?>
+--EXPECT--
+0.4 seconds elapsed
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.