Commits

Ruslan Osmanov committed 1c015d2

Add: support of UNIX domain sockets in EventListener::__construct, EventBufferEvent::connect methods

Comments (0)

Files changed (8)

classes/buffer_event.c

 }
 /* }}} */
 
-/* {{{ proto bool EventBufferEvent::connect(string addr[, bool sync_resolve = FALSE]);
+/* {{{ proto bool EventBufferEvent::connect(string addr[, bool sync_resolve = FALSE[, int family = EventUtil::AF_UNSPEC]]);
  *
  * Connect buffer event's socket to given address(optionally with port).  The
  * function available since libevent 2.0.2-alpha.
 	char               *addr;
 	int                 addr_len;
 	struct sockaddr     sa;
-	socklen_t           sa_len       = sizeof(struct sockaddr);
+	struct sockaddr_un *sun;
+	socklen_t           sa_len;
 	zend_bool           sync_resolve = 0;
+	long                family       = AF_UNSPEC;
 
-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b",
-				&addr, &addr_len, &sync_resolve) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bl",
+				&addr, &addr_len, &sync_resolve, &family) == FAILURE) {
 		return;
 	}
 
+	if (family & ~(AF_UNSPEC | AF_INET | AF_INET6 | AF_UNIX)) {
+		php_error_docref(NULL TSRMLS_CC, E_ERROR,
+				"Unsupported address family: %ld", family);
+		RETURN_FALSE;
+	}
+
 	PHP_EVENT_FETCH_BEVENT(bev, zbevent);
 	_ret_if_invalid_bevent_ptr(bev);
 
-	if (sync_resolve) {
+	if (family == AF_UNIX) {
+		sun = (struct sockaddr_un *) &sa;
+
+		sun->sun_family = AF_UNIX;
+		strncpy(sun->sun_path, addr, addr_len);
+
+		sa_len = sizeof(sun->sun_family) + addr_len;
+	} else if (sync_resolve) {
 		/* The PHP API *syncronously* resolves hostname, if it doesn't look
 		 * like IP(v4/v6) */
 		if (php_network_parse_network_address_with_port(addr, addr_len, &sa, &sa_len TSRMLS_CC) != SUCCESS) {

classes/listener.c

 
 /* {{{ Private */
 
+#define _ret_if_invalid_listener_ptr(l)              \
+{                                                    \
+    PHP_EVENT_ASSERT(l && l->listener);              \
+    if (!l->listener) {                              \
+        php_error_docref(NULL TSRMLS_CC, E_WARNING,  \
+                "EventListener is not initialized"); \
+        RETURN_FALSE;                                \
+    }                                                \
+}
+
 /* {{{ sockaddr_parse
  * Parse in_addr and fill out_arr with IP and port.
  * out_arr must be a pre-allocated empty zend array */
 
 /* Private }}} */
 
-
-/* {{{ proto EventListener EventListener::__construct(EventBase base, callable cb, mixed data, int flags, int backlog, mixed target);
+/* {{{ proto EventListener EventListener::__construct(EventBase base, callable cb, mixed data, int flags, int backlog, mixed target[, int family = EventUtil::AF_UNSPEC]);
  *
  * Creates new connection listener associated with an event base.
  *
  */
 PHP_METHOD(EventListener, __construct)
 {
-	zval                  *zself    = getThis();
-	zval                  *zbase;
-	php_event_base_t      *base;
-	zend_fcall_info        fci      = empty_fcall_info;
-	zend_fcall_info_cache  fcc      = empty_fcall_info_cache;
-	php_event_listener_t  *l;
-	zval                  *zdata    = NULL;
-	zval                 **ppztarget;
-	long                   flags;
-	long                   backlog;
-	struct evconnlistener *listener;
-
-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ofz!llZ",
+	zval                   *zself     = getThis();
+	zval                   *zbase;
+	php_event_base_t       *base;
+	zend_fcall_info         fci       = empty_fcall_info;
+	zend_fcall_info_cache   fcc       = empty_fcall_info_cache;
+	php_event_listener_t   *l;
+	zval                   *zdata     = NULL;
+	zval                  **ppztarget;
+	long                    flags;
+	long                    backlog;
+	long                    family    = AF_UNSPEC;
+	struct evconnlistener  *listener;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ofz!llZ|l",
 				&zbase, php_event_base_ce,
-				&fci, &fcc, &zdata, &flags, &backlog, &ppztarget) == FAILURE) {
+				&fci, &fcc, &zdata, &flags, &backlog, &ppztarget, &family) == FAILURE) {
+		return;
+	}
+
+	if (family & ~(AF_UNSPEC | AF_INET | AF_INET6 | AF_UNIX)) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING,
+				"Unsupported address family: %ld", family);
+		ZVAL_NULL(zself);
 		return;
 	}
 
 
 	if (Z_TYPE_PP(ppztarget) == IS_STRING) {
 		struct sockaddr sa;
-		socklen_t       sa_len = sizeof(struct sockaddr);
+		struct sockaddr_un *sun;
+		socklen_t       sa_len;
+
+		if (family == AF_UNIX) {
+			sun             = (struct sockaddr_un *) &sa;
+			sun->sun_family = AF_UNIX;
+			strncpy(sun->sun_path, Z_STRVAL_PP(ppztarget), Z_STRLEN_PP(ppztarget));
 
-		if (php_network_parse_network_address_with_port(Z_STRVAL_PP(ppztarget), Z_STRLEN_PP(ppztarget),
+			sa_len = sizeof(sun->sun_family) + Z_STRLEN_PP(ppztarget);
+		} else if (php_network_parse_network_address_with_port(Z_STRVAL_PP(ppztarget), Z_STRLEN_PP(ppztarget),
 					&sa, &sa_len TSRMLS_CC) != SUCCESS) {
-			RETURN_FALSE;
+			ZVAL_NULL(zself);
+			return;
 		}
 
 		PHP_EVENT_FETCH_LISTENER(l, zself);
 
 		listener = evconnlistener_new_bind(base->base, _php_event_listener_cb,
 				(void *) l, flags, backlog, &sa, sa_len);
-		if (!listener) {
-			return;
-		}
-		l->listener = listener;
 	} else { /* ppztarget is not string */
 		evutil_socket_t   fd    = -1;
 
 	 	 * in case if it is not a valid socket resource */
 		fd = php_event_zval_to_fd(ppztarget TSRMLS_CC);
 		if (fd < 0) {
+			ZVAL_NULL(zself);
 			return;
 		}
 
 
 		listener = evconnlistener_new(base->base, _php_event_listener_cb,
 				(void *) l, flags, backlog, fd);
-		if (!listener) {
-			return;
-		}
-		l->listener = listener;
-
-#if 0
-		/* WARNING! Don't do this, since libevent calls accept() afterwards,
-		 * thus producing new file descriptor. The new descriptor is available
-		 * in _php_event_listener_cb() callback. */
-
-		if (Z_TYPE_PP(ppztarget) == IS_RESOURCE) {
-			/* lval of ppztarget is the resource ID */
-			l->stream_id = Z_LVAL_PP(ppztarget);
-			zend_list_addref(Z_LVAL_PP(ppztarget));
-		} else {
-			l->stream_id = -1;
-		}
-#endif
 	}
+	
+	if (!listener) {
+		ZVAL_NULL(zself);
+		return;
+	}
+
+	l->listener = listener;
 
 	if (zdata) {
 		l->data = zdata;
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	if (evconnlistener_enable(l->listener)) {
 		RETURN_FALSE;
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	if (evconnlistener_disable(l->listener)) {
 		RETURN_FALSE;
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	if (ZEND_FCI_INITIALIZED(fci)) {
 		PHP_EVENT_FREE_FCALL_INFO(l->fci, l->fcc);
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	if (ZEND_FCI_INITIALIZED(fci)) {
 		PHP_EVENT_FREE_FCALL_INFO(l->fci_err, l->fcc_err);
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	/* base = evconnlistener_get_base(l->listener); */
 
 	}
 
 	PHP_EVENT_FETCH_LISTENER(l, zlistener);
+	_ret_if_invalid_listener_ptr(l);
 
 	fd = evconnlistener_get_fd(l->listener);
 	if (fd <= 0) {

examples/unix-domain-listener-client.php

+<?php
+class MyUnixSocketClient {
+	private $base, $bev;
+
+	function __construct($base, $sock_path) {
+		$this->base = $base;
+		$this->bev = new EventBufferEvent($base, NULL, EventBufferEvent::OPT_CLOSE_ON_FREE,
+			array ($this, "read_cb"), NULL, array ($this, "event_cb"));
+
+		if (!$this->bev->connect($sock_path, TRUE, EventUtil::AF_UNIX)) {
+			trigger_error("Failed to connect to socket `$sock_path'", E_USER_ERROR);
+		}
+
+		$this->bev->enable(Event::READ);
+	}
+
+	function __destruct() {
+		if ($this->bev) {
+			$this->bev->free();
+			$this->bev = NULL;
+		}
+	}
+
+	function dispatch() {
+		$this->base->dispatch();
+	}
+
+	function read_cb($bev, $unused) {
+		$in = $bev->input;
+
+		printf("Received %ld bytes\n", $in->length);
+    	printf("----- data ----\n");
+    	printf("%ld:\t%s\n", (int) $in->length, $in->pullup(-1));
+
+		$this->bev->free();
+		$this->bev = NULL;
+		$this->base->exit(NULL);
+	}
+
+	function event_cb($bev, $events, $unused) {
+		if ($events & EventBufferEvent::ERROR) {
+			echo "Error from bufferevent\n";
+		}
+
+		if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR)) {
+			$bev->free();
+			$bev = NULL;
+		} elseif ($events & EventBufferEvent::CONNECTED) {
+			$bev->output->add("test\n");
+		}
+	}
+}
+
+if ($argc <= 1) {
+	exit("Socket path is not provided\n");
+}
+$sock_path = $argv[1];
+
+$base = new EventBase();
+$cl = new MyUnixSocketClient($base, $sock_path);
+$cl->dispatch();
+?>

examples/unix-domain-listener.php

+<?php
+/*
+ * Simple echo server based on libevent's connection listener.
+ *
+ * Usage:
+ * 1) In one terminal window run:
+ *
+ * $ php unix-domain-listener.php [path-to-socket]
+ *
+ * 2) In another terminal window open up connection
+ * to the socket, e.g.:
+ *
+ * $ socat - GOPEN:/tmp/1.sock
+ *
+ * 3) Start typing. The server should repeat the input.
+ */
+
+class MyListenerConnection {
+	private $bev, $base;
+
+	public function __destruct() {
+		if ($this->bev) {
+			$this->bev->free();
+		}
+	}
+
+	public function __construct($base, $fd) {
+		$this->base = $base;
+
+		$this->bev = new EventBufferEvent($base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);
+
+		$this->bev->setCallbacks(array($this, "echoReadCallback"), NULL,
+			array($this, "echoEventCallback"), NULL);
+
+		if (!$this->bev->enable(Event::READ)) {
+			echo "Failed to enable READ\n";
+			return;
+		}
+	}
+
+	public function echoReadCallback($bev, $ctx) {
+		// Copy all the data from the input buffer to the output buffer
+		$bev->output->addBuffer($bev->input);
+	}
+
+	public function echoEventCallback($bev, $events, $ctx) {
+		if ($events & EventBufferEvent::ERROR) {
+			echo "Error from bufferevent\n";
+		}
+
+		if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR)) {
+			$bev->free();
+			$bev = NULL;
+		}
+	}
+}
+
+class MyListener {
+	public $base,
+		$listener,
+		$socket;
+	private $conn = array();
+
+	public function __destruct() {
+		foreach ($this->conn as &$c) $c = NULL;
+	}
+
+	public function __construct($sock_path) {
+		$this->base = new EventBase();
+		if (!$this->base) {
+			echo "Couldn't open event base";
+			exit(1);
+		}
+
+		if (file_exists($sock_path)) {
+			unlink($sock_path);
+		}
+
+ 		$this->listener = new EventListener($this->base,
+ 			array($this, "acceptConnCallback"), $this->base,
+ 			EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE,-1,
+ 			$sock_path, EventUtil::AF_UNIX);
+
+		if (!$this->listener) {
+			trigger_error("Couldn't create listener", E_USER_ERROR);
+		}
+
+		$this->listener->setErrorCallback(array($this, "accept_error_cb"));
+	}
+
+	public function dispatch() {
+		$this->base->dispatch();
+	}
+
+	// This callback is invoked when there is data to read on $bev
+	public function acceptConnCallback($listener, $fd, $address, $ctx) {
+		// We got a new connection! Set up a bufferevent for it. */
+		$base = $this->base;
+		$this->conn[] = new MyListenerConnection($base, $fd);
+	}
+
+	public function accept_error_cb($listener, $ctx) {
+		$base = $this->base;
+
+		fprintf(STDERR, "Got an error %d (%s) on the listener. "
+			."Shutting down.\n",
+			EventUtil::getLastSocketErrno(),
+			EventUtil::getLastSocketError());
+
+		$base->exit(NULL);
+	}
+}
+
+if ($argc <= 1) {
+	exit("Socket path is not provided\n");
+}
+$sock_path = $argv[1];
+
+$l = new MyListener($sock_path);
+$l->dispatch();
     <email>osmanov@php.net</email>
     <active>yes</active>
   </lead>
-  <date>2013-03-20</date>
+  <date>2013-03-21</date>
   <!--{{{ Current version -->
   <version>
     <release>1.5.0</release>
   Add: EventUtil::getSocketName method
   Add: EventListener::getSocketName method
   Fix: memory leak due to lack of zend_hash_destroy on the ssl context options
+  Add: support of UNIX domain sockets in EventListener::__construct, EventBufferEvent::connect methods
   ]]></notes>
   <!--}}}-->
   <!--{{{ Contents -->
         <file role="src" name="04-bevent-socket.phpt"/>
         <file role="src" name="05-buffer-pos.phpt"/>
         <file role="src" name="06-timer.phpt"/>
+        <file role="src" name="07-listener-error.phpt"/>
       </dir>
     </dir>
   </contents>
         Add: EventBuffer::searchEol method
         Add: EventUtil::getSocketName method
         Add: EventListener::getSocketName method
-  Fix: memory leak due to lack of zend_hash_destroy on the ssl context options
+        Fix: memory leak due to lack of zend_hash_destroy on the ssl context options
+        Add: support of UNIX domain sockets in EventListener::__construct, EventBufferEvent::connect methods
         ]]></notes>
     </release>
     <!--}}}-->
 	/* Address families */
 	REGISTER_EVENT_CLASS_CONST_LONG(php_event_util_ce, AF_INET,   AF_INET);
 	REGISTER_EVENT_CLASS_CONST_LONG(php_event_util_ce, AF_INET6,  AF_INET6);
+	REGISTER_EVENT_CLASS_CONST_LONG(php_event_util_ce, AF_UNIX,   AF_UNIX);
 	REGISTER_EVENT_CLASS_CONST_LONG(php_event_util_ce, AF_UNSPEC, AF_UNSPEC);
 
 #ifdef HAVE_EVENT_EXTRA_LIB
 ZEND_END_ARG_INFO();
 
 
-ZEND_BEGIN_ARG_INFO_EX(arginfo_evconnlistener__construct, 0, 0, 5)
+ZEND_BEGIN_ARG_INFO_EX(arginfo_evconnlistener__construct, 0, 0, 6)
 	ZEND_ARG_INFO(0, base)
 	ZEND_ARG_INFO(0, cb)
 	ZEND_ARG_INFO(0, data)
 	ZEND_ARG_INFO(0, flags)
 	ZEND_ARG_INFO(0, backlog)
 	ZEND_ARG_INFO(0, target)
+	ZEND_ARG_INFO(0, family)
 ZEND_END_ARG_INFO();
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_evconnlistener_new_bind, 0, 0, 5)

tests/07-listener-error.phpt

+--TEST--
+Check for EventListener error behaviour 
+--FILE--
+<?php
+$base = new EventBase();
+
+// Create listener based on UNIX domain socket. Pass wrong address family.
+// The contructor should return NULL
+$listener = new EventListener($base, function() {}, NULL, 0, -1,
+	"/tmp/".mt_rand().".sock", EventUtil::AF_UNSPEC);
+
+var_dump($listener);
+?>
+--EXPECT--
+NULL