From 7b077194cc8fd38808e3f210fc090aedb5df25d5 Mon Sep 17 00:00:00 2001
From: Azat Khuzhin <a3at.mail@gmail.com>
Date: Thu, 21 Mar 2013 13:55:40 +0400
Subject: [PATCH] Add new error_cb for actual reporting of HTTP request errors.

It is useful to know why you callback called with NULL (i.e. it failed),
for example if you set max_body with evhttp_connection_set_max_body_size()
you must know that it failed because of body was longer than this size.

 (Commit message tweaked by Nick)
---
 evrpc.c                      |  2 +-
 http-internal.h              | 11 ++-----
 http.c                       | 56 ++++++++++++++++++++++--------------
 include/event2/http.h        | 41 ++++++++++++++++++++++++++
 include/event2/http_struct.h |  7 +++++
 test/regress_http.c          |  3 +-
 6 files changed, 88 insertions(+), 32 deletions(-)

diff --git a/evrpc.c b/evrpc.c
index fd1f67c0..8d8ecc5a 100644
--- a/evrpc.c
+++ b/evrpc.c
@@ -977,7 +977,7 @@ evrpc_request_timeout(evutil_socket_t fd, short what, void *arg)
 	struct evhttp_connection *evcon = ctx->evcon;
 	EVUTIL_ASSERT(evcon != NULL);
 
-	evhttp_connection_fail_(evcon, EVCON_HTTP_TIMEOUT);
+	evhttp_connection_fail_(evcon, EVREQ_HTTP_TIMEOUT);
 }
 
 /*
diff --git a/http-internal.h b/http-internal.h
index 0d4475f5..fe14a230 100644
--- a/http-internal.h
+++ b/http-internal.h
@@ -29,14 +29,6 @@ enum message_read_status {
 	DATA_TOO_LONG = -3
 };
 
-enum evhttp_connection_error {
-	EVCON_HTTP_TIMEOUT,
-	EVCON_HTTP_EOF,
-	EVCON_HTTP_INVALID_HEADER,
-	EVCON_HTTP_BUFFER_ERROR,
-	EVCON_HTTP_REQUEST_CANCEL
-};
-
 struct evbuffer;
 struct addrinfo;
 struct evhttp_request;
@@ -182,9 +174,10 @@ void evhttp_connection_reset_(struct evhttp_connection *);
 /* connects if necessary */
 int evhttp_connection_connect_(struct evhttp_connection *);
 
+enum evhttp_request_error;
 /* notifies the current request that it failed; resets connection */
 void evhttp_connection_fail_(struct evhttp_connection *,
-    enum evhttp_connection_error error);
+    enum evhttp_request_error error);
 
 enum message_read_status;
 
diff --git a/http.c b/http.c
index f571d356..22705075 100644
--- a/http.c
+++ b/http.c
@@ -625,11 +625,11 @@ evhttp_connection_set_max_body_size(struct evhttp_connection* evcon,
 
 static int
 evhttp_connection_incoming_fail(struct evhttp_request *req,
-    enum evhttp_connection_error error)
+    enum evhttp_request_error error)
 {
 	switch (error) {
-	case EVCON_HTTP_TIMEOUT:
-	case EVCON_HTTP_EOF:
+	case EVREQ_HTTP_TIMEOUT:
+	case EVREQ_HTTP_EOF:
 		/*
 		 * these are cases in which we probably should just
 		 * close the connection and not send a reply.  this
@@ -647,9 +647,10 @@ evhttp_connection_incoming_fail(struct evhttp_request *req,
 			req->evcon = NULL;
 		}
 		return (-1);
-	case EVCON_HTTP_INVALID_HEADER:
-	case EVCON_HTTP_BUFFER_ERROR:
-	case EVCON_HTTP_REQUEST_CANCEL:
+	case EVREQ_HTTP_INVALID_HEADER:
+	case EVREQ_HTTP_BUFFER_ERROR:
+	case EVREQ_HTTP_REQUEST_CANCEL:
+	case EVREQ_HTTP_DATA_TOO_LONG:
 	default:	/* xxx: probably should just error on default */
 		/* the callback looks at the uri to determine errors */
 		if (req->uri) {
@@ -677,12 +678,14 @@ evhttp_connection_incoming_fail(struct evhttp_request *req,
  * delegates to evhttp_connection_incoming_fail(). */
 void
 evhttp_connection_fail_(struct evhttp_connection *evcon,
-    enum evhttp_connection_error error)
+    enum evhttp_request_error error)
 {
 	const int errsave = EVUTIL_SOCKET_ERROR();
 	struct evhttp_request* req = TAILQ_FIRST(&evcon->requests);
 	void (*cb)(struct evhttp_request *, void *);
 	void *cb_arg;
+	void (*error_cb)(enum evhttp_request_error, void *);
+	void *error_cb_arg;
 	EVUTIL_ASSERT(req != NULL);
 
 	bufferevent_disable(evcon->bufev, EV_READ|EV_WRITE);
@@ -701,8 +704,10 @@ evhttp_connection_fail_(struct evhttp_connection *evcon,
 		return;
 	}
 
+	error_cb = req->error_cb;
+	error_cb_arg = req->cb_arg;
 	/* when the request was canceled, the callback is not executed */
-	if (error != EVCON_HTTP_REQUEST_CANCEL) {
+	if (error != EVREQ_HTTP_REQUEST_CANCEL) {
 		/* save the callback for later; the cb might free our object */
 		cb = req->cb;
 		cb_arg = req->cb_arg;
@@ -732,6 +737,8 @@ evhttp_connection_fail_(struct evhttp_connection *evcon,
 	EVUTIL_SET_SOCKET_ERROR(errsave);
 
 	/* inform the user */
+	if (error_cb != NULL)
+		error_cb(error, error_cb_arg);
 	if (cb != NULL)
 		(*cb)(NULL, cb_arg);
 }
@@ -925,7 +932,7 @@ evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req)
 	switch (evhttp_parse_headers_(req, buf)) {
 	case DATA_CORRUPTED:
 	case DATA_TOO_LONG:
-		evhttp_connection_fail_(evcon, EVCON_HTTP_INVALID_HEADER);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_DATA_TOO_LONG);
 		break;
 	case ALL_DATA_READ:
 		bufferevent_disable(evcon->bufev, EV_READ);
@@ -952,10 +959,10 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
 			evhttp_read_trailer(evcon, req);
 			return;
 		case DATA_CORRUPTED:
-		case DATA_TOO_LONG:/*separate error for this? XXX */
+		case DATA_TOO_LONG:
 			/* corrupted data */
 			evhttp_connection_fail_(evcon,
-			    EVCON_HTTP_INVALID_HEADER);
+			    EVREQ_HTTP_DATA_TOO_LONG);
 			return;
 		case REQUEST_CANCELED:
 			/* request canceled */
@@ -968,7 +975,7 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
 	} else if (req->ntoread < 0) {
 		/* Read until connection close. */
 		if ((size_t)(req->body_size + evbuffer_get_length(buf)) < req->body_size) {
-			evhttp_connection_fail_(evcon, EVCON_HTTP_INVALID_HEADER);
+			evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER);
 			return;
 		}
 
@@ -994,7 +1001,7 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
 		/* failed body length test */
 		event_debug(("Request body is too long"));
 		evhttp_connection_fail_(evcon,
-				       EVCON_HTTP_INVALID_HEADER);
+				       EVREQ_HTTP_DATA_TOO_LONG);
 		return;
 	}
 
@@ -1375,12 +1382,12 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
 	}
 
 	if (what & BEV_EVENT_TIMEOUT) {
-		evhttp_connection_fail_(evcon, EVCON_HTTP_TIMEOUT);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_TIMEOUT);
 	} else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) {
-		evhttp_connection_fail_(evcon, EVCON_HTTP_EOF);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_EOF);
 	} else if (what == BEV_EVENT_CONNECTED) {
 	} else {
-		evhttp_connection_fail_(evcon, EVCON_HTTP_BUFFER_ERROR);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_BUFFER_ERROR);
 	}
 }
 
@@ -2032,7 +2039,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req)
 	} else {
 		if (evhttp_get_body_length(req) == -1) {
 			evhttp_connection_fail_(evcon,
-			    EVCON_HTTP_INVALID_HEADER);
+			    EVREQ_HTTP_INVALID_HEADER);
 			return;
 		}
 		if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) {
@@ -2088,7 +2095,7 @@ evhttp_read_firstline(struct evhttp_connection *evcon,
 		/* Error while reading, terminate */
 		event_debug(("%s: bad header lines on "EV_SOCK_FMT"\n",
 			__func__, EV_SOCK_ARG(evcon->fd)));
-		evhttp_connection_fail_(evcon, EVCON_HTTP_INVALID_HEADER);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER);
 		return;
 	} else if (res == MORE_DATA_EXPECTED) {
 		/* Need more header lines */
@@ -2111,7 +2118,7 @@ evhttp_read_header(struct evhttp_connection *evcon,
 		/* Error while reading, terminate */
 		event_debug(("%s: bad header lines on "EV_SOCK_FMT"\n",
 			__func__, EV_SOCK_ARG(fd)));
-		evhttp_connection_fail_(evcon, EVCON_HTTP_INVALID_HEADER);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER);
 		return;
 	} else if (res == MORE_DATA_EXPECTED) {
 		/* Need more header lines */
@@ -2153,7 +2160,7 @@ evhttp_read_header(struct evhttp_connection *evcon,
 	default:
 		event_warnx("%s: bad header on "EV_SOCK_FMT, __func__,
 		    EV_SOCK_ARG(fd));
-		evhttp_connection_fail_(evcon, EVCON_HTTP_INVALID_HEADER);
+		evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER);
 		break;
 	}
 	/* request may have been freed above */
@@ -2454,7 +2461,7 @@ evhttp_cancel_request(struct evhttp_request *req)
 			 * the connection.
 			 */
 			evhttp_connection_fail_(evcon,
-			    EVCON_HTTP_REQUEST_CANCEL);
+			    EVREQ_HTTP_REQUEST_CANCEL);
 
 			/* connection fail freed the request */
 			return;
@@ -3764,6 +3771,13 @@ evhttp_request_set_chunked_cb(struct evhttp_request *req,
 	req->chunk_cb = cb;
 }
 
+void
+evhttp_request_set_error_cb(struct evhttp_request *req,
+    void (*cb)(enum evhttp_request_error, void *))
+{
+	req->error_cb = cb;
+}
+
 /*
  * Allows for inspection of the request URI
  */
diff --git a/include/event2/http.h b/include/event2/http.h
index 04338a7b..0428ea9e 100644
--- a/include/event2/http.h
+++ b/include/event2/http.h
@@ -470,6 +470,47 @@ struct evhttp_request *evhttp_request_new(
 void evhttp_request_set_chunked_cb(struct evhttp_request *,
     void (*cb)(struct evhttp_request *, void *));
 
+/**
+ * The different error types supported by evhttp
+ *
+ * @see evhttp_request_set_error_cb()
+ */
+enum evhttp_request_error {
+  /**
+   * Timeout reached, also @see evhttp_connection_set_timeout()
+   */
+  EVREQ_HTTP_TIMEOUT,
+  /**
+   * EOF reached
+   */
+  EVREQ_HTTP_EOF,
+  /**
+   * Error while reading header, or invalid header
+   */
+  EVREQ_HTTP_INVALID_HEADER,
+  /**
+   * Error encountered while reading or writing
+   */
+  EVREQ_HTTP_BUFFER_ERROR,
+  /**
+   * The evhttp_cancel_request() called on this request.
+   */
+  EVREQ_HTTP_REQUEST_CANCEL,
+  /**
+   * Body is greater then evhttp_connection_set_max_body_size()
+   */
+  EVREQ_HTTP_DATA_TOO_LONG
+};
+/**
+ * Set a callback for errors
+ * @see evhttp_request_error for error types.
+ *
+ * On error, both the error callback and the regular callback will be called,
+ * error callback is called before the regular callback.
+ **/
+void evhttp_request_set_error_cb(struct evhttp_request *,
+    void (*)(enum evhttp_request_error, void *));
+
 /** Frees the request object and removes associated events. */
 void evhttp_request_free(struct evhttp_request *req);
 
diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h
index f245dff8..25e19bd9 100644
--- a/include/event2/http_struct.h
+++ b/include/event2/http_struct.h
@@ -120,6 +120,13 @@ struct {
 	 * the regular callback.
 	 */
 	void (*chunk_cb)(struct evhttp_request *, void *);
+	/*
+	 * Error callback - called when error is occured.
+	 * @see evhttp_request_error for error types.
+	 *
+	 * @see evhttp_request_set_error_cb()
+	 */
+	void (*error_cb)(enum evhttp_request_error, void *);
 };
 
 #ifdef __cplusplus
diff --git a/test/regress_http.c b/test/regress_http.c
index 70b40d17..eff97950 100644
--- a/test/regress_http.c
+++ b/test/regress_http.c
@@ -56,6 +56,7 @@
 
 #include "event2/event.h"
 #include "event2/http.h"
+#include "event2/http_struct.h"
 #include "event2/buffer.h"
 #include "event2/bufferevent.h"
 #include "event2/util.h"
@@ -647,7 +648,7 @@ http_large_delay_cb(struct evhttp_request *req, void *arg)
 	tv.tv_usec = 500000;
 
 	event_base_once(arg, -1, EV_TIMEOUT, http_delay_reply, req, &tv);
-	evhttp_connection_fail_(delayed_client, EVCON_HTTP_EOF);
+	evhttp_connection_fail_(delayed_client, EVREQ_HTTP_EOF);
 }
 
 /*
-- 
2.40.0