lib/libevent-terom-r848.diff
author Tero Marttila <terom@fixme.fi>
Wed, 27 Aug 2008 21:30:32 +0300
changeset 41 540737bf6bac
parent 14 5a2246f5be78
permissions -rw-r--r--
sending requests, and partial support for receiving -- incomplete, not tested
Index: http.c
===================================================================
--- http.c	(revision 848)
+++ http.c	(working copy)
@@ -167,8 +167,10 @@
 static void evhttp_read_header_cb(struct bufferevent *bufev, void *arg);
 static void evhttp_write_cb(struct bufferevent *, void *);
 static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg);
+
 static int evhttp_decode_uri_internal(const char *uri, size_t length,
     char *ret);
+static void _evhttp_reply_notify (struct evhttp_connection *evcon, void *arg);
 
 #ifndef HAVE_STRSEP
 /* strsep replacement for platforms that lack it.  Only works if
@@ -293,13 +295,20 @@
 
 void
 evhttp_write_buffer(struct evhttp_connection *evcon,
-    void (*cb)(struct evhttp_connection *, void *), void *arg)
+    void (*cb)(struct evhttp_connection *, void *), void *arg,
+    size_t threshold)
 {
 	event_debug(("%s: preparing to write buffer\n", __func__));
 
 	/* Set call back */
 	evcon->cb = cb;
 	evcon->cb_arg = arg;
+    
+    if (threshold > 0) {
+        bufferevent_setwatermark(evcon->bufev, EV_WRITE, threshold, 0);
+    } else {
+        bufferevent_setwatermark(evcon->bufev, EV_WRITE, 0, 0);
+    }
 
 	bufferevent_disable(evcon->bufev, EV_READ);
 	bufferevent_enable(evcon->bufev, EV_WRITE);
@@ -483,6 +492,17 @@
 	switch (error) {
 	case EVCON_HTTP_TIMEOUT:
 	case EVCON_HTTP_EOF:
+        /*
+         * Fix a bug that caused a nasty infinite-malloc()-and-write() loop.
+         * A write buffer error (e.g. EPIPE) means that we can't write to that 
+         * socket anymore, and evhttp_connection_fail does disable
+         * EV_WRITE|EV_READ for that buffer. However, if we call req->cb, then
+         * that adds a "400 Bad Request" to the buffer, and enables it for
+         * EV_WRITE... which causes evhttp_connection_incoming_fail to be
+         * called again with EVCON_HTTP_BUFFER_ERROR... ended up having to
+         * hard-reset my machine.
+         */
+	case EVCON_HTTP_BUFFER_ERROR:
 		/* 
 		 * these are cases in which we probably should just
 		 * close the connection and not send a reply.  this
@@ -491,7 +511,6 @@
 		 */
 		return (-1);
 	case EVCON_HTTP_INVALID_HEADER:
-	case EVCON_HTTP_BUFFER_ERROR:
 	case EVCON_HTTP_REQUEST_CANCEL:
 	default:	/* xxx: probably should just error on default */
 		/* the callback looks at the uri to determine errors */
@@ -869,7 +888,7 @@
 	/* Create the header from the store arguments */
 	evhttp_make_header(evcon, req);
 
-	evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL);
+	evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL, 0);
 }
 
 /* Reset our connection state */
@@ -1353,9 +1372,9 @@
 		}
 	}
 		
-	event_debug(("%s: bytes to read: %d (in buffer %ld)\n",
+	event_debug(("%s: bytes to read: %ld (in buffer %ld)\n",
 		__func__, req->ntoread,
-		EVBUFFER_LENGTH(req->evcon->input_buffer)));
+		EVBUFFER_LENGTH(EVBUFFER_INPUT(req->evcon->bufev))));
 
 	return (0);
 }
@@ -1688,6 +1707,10 @@
 
 	/* delete possible close detection events */
 	evhttp_connection_stop_detectclose(evcon);
+
+    /* now that the reply was sent, we don't need to abort on connection lost */
+    if (req->abort_cb)
+        evhttp_connection_set_closecb(evcon, NULL, NULL);
 	
 	need_close =
 	    (req->minor == 0 &&
@@ -1753,7 +1776,7 @@
 	/* Adds headers to the response */
 	evhttp_make_header(evcon, req);
 
-	evhttp_write_buffer(evcon, evhttp_send_done, NULL);
+	evhttp_write_buffer(evcon, evhttp_send_done, NULL, 0);
 }
 
 void
@@ -1781,7 +1804,7 @@
 		req->chunked = 1;
 	}
 	evhttp_make_header(req->evcon, req);
-	evhttp_write_buffer(req->evcon, NULL, NULL);
+	evhttp_write_buffer(req->evcon, NULL, NULL, 0);
 }
 
 void
@@ -1796,9 +1819,32 @@
 	if (req->chunked) {
 		evbuffer_add(output, "\r\n", 2);
 	}
-	evhttp_write_buffer(req->evcon, NULL, NULL);
+
+	evhttp_write_buffer(req->evcon, &_evhttp_reply_notify, req, req->write_threshold);
 }
 
+static void
+_evhttp_reply_notify (struct evhttp_connection *evcon, void *arg)
+{
+    struct evhttp_request *req = arg;
+
+    if (req->write_cb)
+        req->write_cb(req, req->write_cb_arg);
+}
+
+int
+evhttp_set_reply_notify(struct evhttp_request *req, size_t threshold,
+    void (*write_cb)(struct evhttp_request *, void *), void *cb_arg)
+{
+    req->write_threshold = threshold;
+    req->write_cb = write_cb;
+    req->write_cb_arg = cb_arg;
+
+	evhttp_write_buffer(req->evcon, &_evhttp_reply_notify, req, threshold);
+
+    return 0;
+}
+
 void
 evhttp_send_reply_end(struct evhttp_request *req)
 {
@@ -1807,7 +1853,7 @@
 
 	if (req->chunked) {
 		evbuffer_add(output, "0\r\n\r\n", 5);
-		evhttp_write_buffer(req->evcon, evhttp_send_done, NULL);
+		evhttp_write_buffer(req->evcon, evhttp_send_done, NULL, 0);
 		req->chunked = 0;
 	} else if (EVBUFFER_LENGTH(output) == 0) {
 		/* let the connection know that we are done with the request */
@@ -1819,6 +1865,28 @@
 	}
 }
 
+void _evhttp_reply_aborted(struct evhttp_connection *evcon, void *arg)
+{
+    struct evhttp_request *req = arg;
+    
+    /* call the callback */
+    req->abort_cb(req, req->abort_cb_arg);
+    
+    /* the request is now aborted, we don't want this called twice */
+    evhttp_connection_set_closecb(evcon, NULL, NULL);
+}
+
+void evhttp_set_reply_abortcb(struct evhttp_request *req,
+    void (*cb)(struct evhttp_request *, void *), void *cb_arg)
+{
+    /* store the cb/arg in req */
+    req->abort_cb = cb;
+    req->abort_cb_arg = cb_arg;
+    
+    /* add a closecb to the connection */
+    evhttp_connection_set_closecb(req->evcon, &_evhttp_reply_aborted, req);
+}
+
 void
 evhttp_response_code(struct evhttp_request *req, int code, const char *reason)
 {
@@ -2448,6 +2516,19 @@
  * Allows for inspection of the request URI
  */
 
+enum evhttp_cmd_type
+evhttp_request_get_type(struct evhttp_request *req)
+{
+    return req->type;
+}
+
+void
+evhttp_request_get_peer(struct evhttp_request *req,
+    char **address, ev_uint16_t *port)
+{
+    return evhttp_connection_get_peer(req->evcon, address, port);
+}
+
 const char *
 evhttp_request_get_uri(struct evhttp_request *req) {
 	if (req->uri == NULL)
Index: http-internal.h
===================================================================
--- http-internal.h	(revision 848)
+++ http-internal.h	(working copy)
@@ -141,7 +141,8 @@
 void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
 
 void evhttp_write_buffer(struct evhttp_connection *,
-    void (*)(struct evhttp_connection *, void *), void *);
+    void (*)(struct evhttp_connection *, void *), void *,
+    size_t);
 
 /* response sending HTML the data in the buffer */
 void evhttp_response_code(struct evhttp_request *, int, const char *);
Index: include/event2/http.h
===================================================================
--- include/event2/http.h	(revision 848)
+++ include/event2/http.h	(working copy)
@@ -243,6 +243,7 @@
 */
 void evhttp_send_reply_chunk(struct evhttp_request *req,
     struct evbuffer *databuf);
+
 /**
    Complete a chunked reply.
 
@@ -250,6 +251,43 @@
 */
 void evhttp_send_reply_end(struct evhttp_request *req);
 
+/**
+   Used to implement flow control for sending HTTP replies.
+
+   After calling evhttp_send_reply_start, and before calling
+   evhttp_send_reply_end, you may call this function to register a callback
+   that will be fired when the level of the HTTP connection's output buffer is
+   below the given threshold (if the output buffer is empty when this function
+   is called, this will happen on the next event-loop iteration).
+
+   This callback behaves in an edge-triggered way, such that it will only fire
+   once if the otuput buffer's level is below the threshold. It may be called
+   again after the next evhttp_send_reply_chunk call.
+
+   Once evhttp_send_reply_end is called (or an error occurs), the callback is
+   invalidated and not used anymore.
+
+   @param req a request object
+   @param threshold the buffer level to watch for
+   @param write_cb the callback to use
+   @param cb_arg the callback argument
+ 
+*/
+int evhttp_set_reply_notify(struct evhttp_request *req, size_t threshold,
+    void (*write_cb)(struct evhttp_request *, void *), void *cb_arg);
+
+/**
+   Register a callback to get called if the request is aborted due to some
+   reason other than evhttp_send_{reply,error,reply_end}, e.g. the HTTP
+   connection was lost.
+
+   @param req a request object
+   @param cb the callback to use
+   @param cb_arg the callback argument
+*/
+void evhttp_set_reply_abortcb(struct evhttp_request *,
+    void (*)(struct evhttp_request *, void *), void *);
+
 /*
  * Interfaces for making requests
  */
@@ -360,6 +398,11 @@
 void evhttp_cancel_request(struct evhttp_request *req);
 
 
+/** Get the request type for this request */
+enum evhttp_cmd_type evhttp_request_get_type(struct evhttp_request *req);
+/** Get the remote address and port this request came from. */
+void evhttp_request_get_peer(struct evhttp_request *req,
+    char **address, ev_uint16_t *port);
 /** Returns the request URI */
 const char *evhttp_request_get_uri(struct evhttp_request *req);
 /** Returns the input headers */
Index: include/event2/http_struct.h
===================================================================
--- include/event2/http_struct.h	(revision 848)
+++ include/event2/http_struct.h	(working copy)
@@ -113,6 +113,19 @@
 	 * the regular callback.
 	 */
 	void (*chunk_cb)(struct evhttp_request *, void *);
+
+    /*
+     * Request reply flow control callback
+     */
+    size_t write_threshold;
+    void (*write_cb)(struct evhttp_request *, void *);
+    void *write_cb_arg;
+
+    /*
+     * Request abort callback
+     */
+    void (*abort_cb)(struct evhttp_request *, void *);
+    void *abort_cb_arg;
 };
 
 #ifdef __cplusplus
Index: buffer.c
===================================================================
--- buffer.c	(revision 848)
+++ buffer.c	(working copy)
@@ -412,6 +412,9 @@
 	if (size == 0 || size > buf->total_len)
 		return (NULL);
 
+// XXX: the following will segfault if buf->total_len == 0 && size < -1
+// XXX: the following will return a buffer of arbitrary length if size < -1
+
 	/* No need to pull up anything; the first size bytes are
 	 * already here. */
 	if (chain->off >= size)