Commits

Ruslan Osmanov committed 0fa8deb

Add: EventHttpConnection::setCloseCallback
Renamed EventHttpRequest::getEventBufferEvent to EventHttpRequest::getBufferEvent
Renamed EventHttpRequest::getEventHttpConnection to EventHttpRequest::getConnection
Fix: missing check for pointer returned by evhttp_request_get_connection in EventHttpRequest::closeConnection()
Add: examples/http_closecb.php - example of handling closed connections

Comments (0)

Files changed (8)

classes/http_connection.c

 #include "src/util.h"
 #include "src/priv.h"
 
+/* {{{ Private */
+
+static void _conn_close_cb(struct evhttp_connection *conn, void *arg)/* {{{ */
+{
+	php_event_http_conn_t *evcon = (php_event_http_conn_t *) arg;
+	PHP_EVENT_ASSERT(evcon && conn);
+
+	zend_fcall_info       *pfci = evcon->fci_closecb;
+	zend_fcall_info_cache *pfcc = evcon->fcc_closecb;
+	PHP_EVENT_ASSERT(pfci && pfcc);
+
+	TSRMLS_FETCH_FROM_CTX(evcon->thread_ctx);
+
+	/* Call userspace function according to
+	 * proto void callback(EventHttpConnection conn, mixed data); */
+
+	zval  *arg_data = evcon->data_closecb;
+	zval  *arg_conn;
+	zval **args[2];
+	zval  *retval_ptr;
+
+	arg_conn = evcon->self;
+	if (conn == NULL || !arg_conn) {
+		ALLOC_INIT_ZVAL(arg_conn);
+	} else {
+		Z_ADDREF_P(arg_conn);
+	}
+	args[0] = &arg_conn;
+
+	if (arg_data) {
+		Z_ADDREF_P(arg_data);
+	} else {
+		ALLOC_INIT_ZVAL(arg_data);
+	}
+	args[1] = &arg_data;
+
+	pfci->params		 = args;
+	pfci->retval_ptr_ptr = &retval_ptr;
+	pfci->param_count	 = 2;
+	pfci->no_separation  = 1;
+
+    if (zend_call_function(pfci, pfcc TSRMLS_CC) == SUCCESS && retval_ptr) {
+        zval_ptr_dtor(&retval_ptr);
+    } else {
+        php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                "An error occurred while invoking the http connection close callback");
+    }
+
+    zval_ptr_dtor(&arg_conn);
+    zval_ptr_dtor(&arg_data);
+}/* }}} */
+
+/* Private }}} */
+
+
 /* {{{ proto EventHttpConnection EventHttpConnection::__construct(EventBase base, EventDnsBase dns_base, string address, int port);
  * If <parameter>dns_base</parameter> is &null;, hostname resolution will block.
  */
 PHP_METHOD(EventHttpConnection, __construct)
 {
+	zval                     *zself       = getThis();
 	zval                     *zbase;
 	php_event_base_t         *b;
-	zval                     *zdns_base = NULL;
+	zval                     *zdns_base   = NULL;
 	php_event_dns_base_t     *dnsb;
 	char                     *address;
 	int                       address_len;
 		PHP_EVENT_FETCH_DNS_BASE(dnsb, zdns_base);
 	}
 
-	PHP_EVENT_FETCH_HTTP_CONN(evcon, getThis());
+	PHP_EVENT_FETCH_HTTP_CONN(evcon, zself);
 
 	conn = evhttp_connection_base_new(b->base,
 			(zdns_base ? dnsb->dns_base : NULL),
 	}
 	evcon->conn = conn;
 
+	evcon->self = zself;
+	Z_ADDREF_P(zself);
+
 	evcon->base = zbase;
 	Z_ADDREF_P(zbase);
 
 }
 /* }}} */
 
+/* {{{ void EventHttpConnection::setCloseCallback(callable callback[, mixed data]);
+ * Set callback for connection close. */
+PHP_METHOD(EventHttpConnection, setCloseCallback)
+{
+	zval                  *zevcon = getThis();
+	php_event_http_conn_t *evcon;
+	zend_fcall_info        fci    = empty_fcall_info;
+	zend_fcall_info_cache  fcc    = empty_fcall_info_cache;
+	zval                  *zarg   = NULL;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f|z",
+				&fci, &fcc, &zarg) == FAILURE) {
+		return;
+	}
+
+	PHP_EVENT_FETCH_HTTP_CONN(evcon, zevcon);
+
+	PHP_EVENT_FREE_FCALL_INFO(evcon->fci_closecb, evcon->fcc_closecb);
+	PHP_EVENT_COPY_FCALL_INFO(evcon->fci_closecb, evcon->fcc_closecb, &fci, &fcc);
+
+	if (zarg) {
+		if (evcon->data_closecb) {
+			zval_ptr_dtor(&evcon->data_closecb);
+		}
+		evcon->data_closecb = zarg;
+		Z_ADDREF_P(zarg);
+	}
+
+	TSRMLS_SET_CTX(evcon->thread_ctx);
+
+	evhttp_connection_set_closecb(evcon->conn, _conn_close_cb, (void *) evcon);
+}
+/* }}} */
+
 
 /*
  * Local variables:

classes/http_request.c

 }
 /* }}} */
 
-/* {{{ proto EventBufferEvent EventHttpRequest::getEventBufferEvent(void);
+/* {{{ proto EventBufferEvent EventHttpRequest::getBufferEvent(void);
  * Returns EventBufferEvent object on success, otherwise &null. */
-PHP_METHOD(EventHttpRequest, getEventBufferEvent)
+PHP_METHOD(EventHttpRequest, getBufferEvent)
 {
 	php_event_http_req_t     *http_req;
 	struct evhttp_connection *conn;
 	bev->bevent = evhttp_connection_get_bufferevent(conn);
 	bev->self = return_value;
 	Z_ADDREF_P(return_value);
-	bev->input = bev->output = NULL;
+	bev->input = NULL;
+	bev->output = NULL;
 	bev->_internal = 1;
 }
 /* }}} */
 
-/* {{{ proto EventHttpConnection EventHttpRequest::getEventHttpConnection(void);
- * Returns EventHttpConnection object. */
-PHP_METHOD(EventHttpRequest, getEventHttpConnection)
+/* {{{ proto EventHttpConnection EventHttpRequest::getConnection(void);
+ * Returns EventHttpConnection object. 
+ *
+ * Warning! Libevent API allows http request objects not bound to any http connection.
+ * Therefore we can't unambiguously associate EventHttpRequest with EventHttpConnection.
+ * Thus, we construct EventHttpConnection object on-the-fly. Having no information about
+ * base, dns_base and connection-close callback, we just leave these fields unset.
+ *
+ * If somebody finds some way to return full-value EventHttpConnection object,
+ * please don't hesitate to make a pull request.
+ */
+PHP_METHOD(EventHttpRequest, getConnection)
 {
 	php_event_http_req_t     *http_req;
 	struct evhttp_connection *conn;
 	PHP_EVENT_FETCH_HTTP_CONN(evcon, return_value);
 
 	evcon->conn = conn;
-	evcon->base = NULL;
-	evcon->dns_base = NULL;
+	evcon->self = return_value;
+	Z_ADDREF_P(return_value);
+
+	/* Set in ctor:
+	   evcon->base = NULL;
+	   evcon->dns_base = NULL;
+	   evcon->data_closecb = NULL;
+	   evcon->fci_closecb = NULL;
+	   evcon->fcc_closecb = NULL;
+	*/
 	Z_ADDREF_P(return_value);
 }
 /* }}} */
 	_check_http_req_ptr(http_req);
 
 	conn = evhttp_request_get_connection(http_req->ptr);
+	if (conn == NULL) {
+		return;
+	}
 	evhttp_connection_free(conn);
 }
 /* }}} */

examples/http_closecb.php

+<?php
+/*
+ * Setting up close-connection callback
+ *
+ * The script handles closed connections using HTTP API.
+ *
+ * Usage:
+ * 1) Launch the server:
+ * $ php examples/http_closecb.php 4242
+ *
+ * 2) Launch a client in another terminal. Telnet-like
+ * session should look like the following:
+ *
+ * $ nc -t 127.0.0.1 4242
+ * GET / HTTP/1.0
+ * Connection: close
+ *
+ * The server will output something similar to the following:
+ *
+ * HTTP/1.0 200 OK
+ * Content-Type: multipart/x-mixed-replace;boundary=boundarydonotcross
+ * Connection: close
+ *
+ * <html>
+ *
+ * 3) Terminate the client connection abruptly,
+ * i.e. kill the process, or just press Ctrl-C.
+ *
+ * 4) Check if the server called _close_callback.
+ * The script should output "_close_callback" string to standard output.
+ *
+ * 5) Check if the server's process has no orphaned connections,
+ * e.g. with `lsof` utility.
+ */
+
+function _close_callback($conn) {
+	echo __FUNCTION__, PHP_EOL;
+}
+
+function _http_default($req, $dummy) {
+	$conn = $req->getConnection();
+	$conn->setCloseCallback('_close_callback', NULL);
+
+	/*
+	By enabling Event::READ we protect the server against unclosed conections.
+	This is a peculiarity of Libevent. The library disables Event::READ events
+ 	on this connection, and the server is not notified about terminated
+	connections.
+
+	So each time client terminates connection abruptly, we get an orphaned
+	connection. For instance, the following is a part of `lsof -p $PID | grep TCP`
+	command after client has terminated connection:
+
+	57-php     15057 ruslan  6u  unix 0xffff8802fb59c780   0t0  125187 socket
+	58:php     15057 ruslan  7u  IPv4             125189   0t0     TCP *:4242 (LISTEN)
+	59:php     15057 ruslan  8u  IPv4             124342   0t0     TCP localhost:4242->localhost:37375 (CLOSE_WAIT)
+
+	where $PID is our process ID. 
+
+	The following block of code fixes such kind of orphaned connections.
+	 */
+	$bev = $req->getBufferEvent();
+	$bev->enable(Event::READ);
+
+	$req->addHeader('Content-Type',
+		'multipart/x-mixed-replace;boundary=boundarydonotcross',
+		EventHttpRequest::OUTPUT_HEADER);
+
+	$buf = new EventBuffer();
+	$buf->add('<html>');
+
+	$req->sendReply(200, "OK");
+	$req->sendReplyChunk($buf);
+}
+
+$port = 4242;
+if ($argc > 1) {
+	$port = (int) $argv[1];
+}
+if ($port <= 0 || $port > 65535) {
+	exit("Invalid port");
+}
+
+$base = new EventBase();
+$http = new EventHttp($base);
+
+$http->setDefaultCallback("_http_default", NULL);
+$http->bind("0.0.0.0", $port);
+$base->loop();
     <email>osmanov@php.net</email>
     <active>yes</active>
   </lead>
-  <date>2013-09-24</date>
+  <date>2013-10-06</date>
   <!--{{{ Current version -->
   <version>
     <release>1.8.0</release>
   </stability>
   <license uri="http://www.php.net/license">PHP</license>
   <notes><![CDATA[
-  Add: EventHttpRequest methods: getEventBufferEvent, getEventHttpConnection, closeConnection(thanks to rickysu at Bitbucket)
+  Add: EventHttpRequest methods: getBufferEvent, getConnection, closeConnection(thanks to rickysu at Bitbucket)
+  Add: EventHttpConnection::setCloseCallback method
   ]]></notes>
   <!--}}}-->
   <!--{{{ Contents -->
         <file role="doc" name="http_accept.php"/>
         <file role="doc" name="http_alias.php"/>
         <file role="doc" name="http_bind.php"/>
+        <file role="doc" name="http_closecb.php"/>
         <file role="doc" name="http_request.php"/>
         <file role="doc" name="httpv0client.php"/>
         <file role="doc" name="httpv0client2.php"/>
       </stability>
       <license uri="http://www.php.net/license">PHP</license>
       <notes><![CDATA[
-  Add: EventHttpRequest methods: getEventBufferEvent, getEventHttpConnection, closeConnection(thanks to rickysu at Bitbucket)
+  Add: EventHttpRequest methods: getBufferEvent, getConnection, closeConnection(thanks to rickysu at Bitbucket)
+  Add: EventHttpConnection::setCloseCallback method
   ]]></notes>
     </release>
     <!--}}}-->
 
 	PHP_EVENT_ASSERT(evcon);
 
+	PHP_EVENT_FREE_FCALL_INFO(evcon->fci_closecb, evcon->fcc_closecb);
+
+	if (evcon->self) {
+		zval_ptr_dtor(&evcon->self);
+		evcon->self = NULL;
+	}
+
+	if (evcon->data_closecb) {
+		zval_ptr_dtor(&evcon->data_closecb);
+		evcon->data_closecb = NULL;
+	}
+
 	if (evcon->base) {
 		zval_ptr_dtor(&evcon->base);
 		evcon->base = NULL;
 	ZEND_ARG_INFO(0, uri)
 ZEND_END_ARG_INFO();
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_event_http_con_set_closecb, 0, 0, 1)
+	ZEND_ARG_INFO(0, callback)
+	ZEND_ARG_INFO(0, data)
+ZEND_END_ARG_INFO();
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_event_http_req_add_header, 0, 0, 3)
 	ZEND_ARG_INFO(0, key)
 	ZEND_ARG_INFO(0, value)
 	PHP_ME(EventHttpConnection, setMaxBodySize,    arginfo_event_evhttp_connection_set_max_size,      ZEND_ACC_PUBLIC)
 	PHP_ME(EventHttpConnection, setRetries,        arginfo_event_evhttp_connection_set_retries,       ZEND_ACC_PUBLIC)
 	PHP_ME(EventHttpConnection, makeRequest,       arginfo_event_http_con_make_request,               ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpConnection, setCloseCallback,  arginfo_event_http_con_set_closecb,                ZEND_ACC_PUBLIC)
 
 	PHP_FE_END
 };
 const zend_function_entry php_event_http_req_ce_functions[] = {
 	PHP_ME(EventHttpRequest, __construct, arginfo_event_http_req__construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
 
-	PHP_ME(EventHttpRequest, free,                   arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getCommand,             arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getHost,                arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getUri,                 arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getResponseCode,        arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getInputHeaders,        arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getOutputHeaders,       arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getInputBuffer,         arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getOutputBuffer,        arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, sendError,              arginfo_event_http_req_send_error,       ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, sendReply,              arginfo_event_http_req_send_reply,       ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, sendReplyChunk,         arginfo_event_http_req_send_reply_chunk, ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, sendReplyEnd,           arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, sendReplyStart,         arginfo_event_http_req_send_reply_start, ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, cancel,                 arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, addHeader,              arginfo_event_http_req_add_header,       ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, clearHeaders,           arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, removeHeader,           arginfo_event_http_req_remove_header,    ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, findHeader,             arginfo_event_http_req_remove_header,    ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getEventBufferEvent,    arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, getEventHttpConnection, arginfo_event__void,                     ZEND_ACC_PUBLIC)
-	PHP_ME(EventHttpRequest, closeConnection,        arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, free,             arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getCommand,       arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getHost,          arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getUri,           arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getResponseCode,  arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getInputHeaders,  arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getOutputHeaders, arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getInputBuffer,   arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getOutputBuffer,  arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, sendError,        arginfo_event_http_req_send_error,       ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, sendReply,        arginfo_event_http_req_send_reply,       ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, sendReplyChunk,   arginfo_event_http_req_send_reply_chunk, ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, sendReplyEnd,     arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, sendReplyStart,   arginfo_event_http_req_send_reply_start, ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, cancel,           arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, addHeader,        arginfo_event_http_req_add_header,       ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, clearHeaders,     arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, removeHeader,     arginfo_event_http_req_remove_header,    ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, findHeader,       arginfo_event_http_req_remove_header,    ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getBufferEvent,   arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, getConnection,    arginfo_event__void,                     ZEND_ACC_PUBLIC)
+	PHP_ME(EventHttpRequest, closeConnection,  arginfo_event__void,                     ZEND_ACC_PUBLIC)
 
 	PHP_FE_END
 };
 PHP_METHOD(EventHttpConnection, setMaxBodySize);
 PHP_METHOD(EventHttpConnection, setRetries);
 PHP_METHOD(EventHttpConnection, makeRequest);
+PHP_METHOD(EventHttpConnection, setCloseCallback);
 
 PHP_METHOD(EventHttp, __construct);
 PHP_METHOD(EventHttp, accept);
 PHP_METHOD(EventHttpRequest, clearHeaders);
 PHP_METHOD(EventHttpRequest, removeHeader);
 PHP_METHOD(EventHttpRequest, findHeader);
-PHP_METHOD(EventHttpRequest, getEventBufferEvent);
-PHP_METHOD(EventHttpRequest, getEventHttpConnection);
+PHP_METHOD(EventHttpRequest, getBufferEvent);
+PHP_METHOD(EventHttpRequest, getConnection);
 PHP_METHOD(EventHttpRequest, closeConnection);
 
 /* Extra API END }}} */
 	struct evhttp_connection *conn;
 	zval                     *base;       /* Event base associated with the listener */
 	zval                     *dns_base;   /* Associated EventDnsBase                 */
+	zval                     *self;
+
+	/* User custom data passed to the callback for connection close */
+	zval                  *data_closecb;
+	/* Callback for connection close */
+	zend_fcall_info       *fci_closecb;
+	zend_fcall_info_cache *fcc_closecb;
+	
+	PHP_EVENT_COMMON_THREAD_CTX;
 } php_event_http_conn_t;
 
 typedef struct {