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)