[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [Libevent-users] [PATCH] client HTTPS with evhttp



On 12/12/11 12:21, Mark Ellzey wrote:
Would you mind submitting the patch branched against patches-2.0 on github?
Easier to pull in / comment / test.

On Mon, Dec 12, 2011 at 11:57:17AM -0800, Myk Taylor wrote:
I've written up a patch that allows evhttp to work for HTTPS where a
second (or further) request is sent on a evhttp_connection that has
previously timed out and closed its underlying TCP connection.

Alright, now that I understand a little more about how to do this, let me try this again : )

The attached diffs are against patches-2.0. Detailed descriptions are at the tops of the patch files themselves.

evhttp-ssl.patch:
This patch is functionally the same as 8d3a8500f420bab66b94265285ab9c096f1c7a87 from 2.1 (Add evhttp callback for bufferevent creation), but changed minimally to conform to the formatting in 2.0.

evhttp-get-new-bufferevent-for-new-tcp-connection.patch:
Adds a facility to evhttp for getting a new bufferevent every time a tcp connection is closed. This patch depends on evhttp-ssl.patch.

Many thanks,
Myk
This patch is functionally the same as 8d3a8500f420bab66b94265285ab9c096f1c7a87 from 2.1, but changed minimally to apply to 2.0

---

From: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date: Mon, 12 Sep 2011 14:46:17 +0000 (-0400)
Subject: Add evhttp callback for bufferevent creation; this lets evhttp support SSL.
X-Git-Url: http://levent.git.sourceforge.net/git/gitweb.cgi?p=levent%2Flibevent;a=commitdiff_plain;h=8d3a8500f420bab66b94265285ab9c096f1c7a87;hp=caf133f90198961c1f9b8bdc74dd6f5385ec00b3

Add evhttp callback for bufferevent creation; this lets evhttp support SSL.

Based on a patch uploaded anonymously to sourceforge; cleaned up
by Graham Leggett to work with current libevents.

The original libevent 2.1 patch is available at:
http://levent.git.sourceforge.net/git/gitweb.cgi?p=levent/libevent;a=commit;h=8d3a8500f420bab66b94265285ab9c096f1c7a87

diff --git a/http-internal.h b/http-internal.h
index 5f66673..2b4df8c 100644
--- a/http-internal.h
+++ b/http-internal.h
@@ -165,6 +165,8 @@ struct evhttp {
 	   don't match. */
 	void (*gencb)(struct evhttp_request *req, void *);
 	void *gencbarg;
+	struct bufferevent* (*bevcb)(struct event_base *, void *);
+	void *bevcbarg;
 
 	struct event_base *base;
 };
diff --git a/http.c b/http.c
index b9687df..771078e 100644
--- a/http.c
+++ b/http.c
@@ -1374,6 +1374,7 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
 		evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
 	} else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) {
 		evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
+	} else if (what == BEV_EVENT_CONNECTED) {
 	} else {
 		evhttp_connection_fail(evcon, EVCON_HTTP_BUFFER_ERROR);
 	}
@@ -2061,7 +2062,7 @@ evhttp_connection_new(const char *address, unsigned short port)
 }
 
 struct evhttp_connection *
-evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase,
+evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
     const char *address, unsigned short port)
 {
 	struct evhttp_connection *evcon = NULL;
@@ -2087,12 +2088,18 @@ evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase,
 		goto error;
 	}
 
-	if ((evcon->bufev = bufferevent_new(-1,
-		    evhttp_read_cb,
-		    evhttp_write_cb,
-		    evhttp_error_cb, evcon)) == NULL) {
-		event_warn("%s: bufferevent_new failed", __func__);
-		goto error;
+	if (bev == NULL) {
+		if ((evcon->bufev = bufferevent_new(-1,
+					evhttp_read_cb,
+					evhttp_write_cb,
+					evhttp_error_cb, evcon)) == NULL) {
+			event_warn("%s: bufferevent_new failed", __func__);
+			goto error;
+		}
+	}
+	else {
+		bufferevent_setcb(bev, evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon);
+		evcon->bufev = bev;
 	}
 
 	evcon->state = EVCON_DISCONNECTED;
@@ -2123,6 +2130,13 @@ evhttp_connection_get_bufferevent(struct evhttp_connection *evcon)
 	return evcon->bufev;
 }
 
+struct evhttp_connection *
+evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase,
+    const char *address, unsigned short port)
+{
+	return evhttp_connection_base_bufferevent_new(base, dnsbase, NULL, address, port);
+}
+
 void
 evhttp_connection_set_base(struct evhttp_connection *evcon,
     struct event_base *base)
@@ -3444,6 +3458,14 @@ evhttp_set_gencb(struct evhttp *http,
 	http->gencbarg = cbarg;
 }
 
+void
+evhttp_set_bevcb(struct evhttp *http,
+    struct bufferevent* (*cb)(struct event_base *, void *), void *cbarg)
+{
+	http->bevcb = cb;
+	http->bevcbarg = cbarg;
+}
+
 /*
  * Request related functions
  */
@@ -3668,6 +3690,7 @@ evhttp_get_request_connection(
 {
 	struct evhttp_connection *evcon;
 	char *hostname = NULL, *portname = NULL;
+	struct bufferevent* bev = NULL;
 
 	name_from_addr(sa, salen, &hostname, &portname);
 	if (hostname == NULL || portname == NULL) {
@@ -3680,8 +3703,11 @@ evhttp_get_request_connection(
 			__func__, hostname, portname, fd));
 
 	/* we need a connection object to put the http request on */
-	evcon = evhttp_connection_base_new(
-		http->base, NULL, hostname, atoi(portname));
+	if (http->bevcb != NULL) {
+		bev = (*http->bevcb)(http->base, http->bevcbarg);
+	}
+	evcon = evhttp_connection_base_bufferevent_new(
+		http->base, NULL, bev, hostname, atoi(portname));
 	mm_free(hostname);
 	mm_free(portname);
 	if (evcon == NULL)
diff --git a/include/event2/http.h b/include/event2/http.h
index c6ee1db..fd89d34 100644
--- a/include/event2/http.h
+++ b/include/event2/http.h
@@ -37,6 +37,7 @@ extern "C" {
 /* In case we haven't included the right headers yet. */
 struct evbuffer;
 struct event_base;
+struct bufferevent;
 
 /** @file event2/http.h
  *
@@ -69,6 +70,7 @@ struct evhttp_request;
 struct evkeyvalq;
 struct evhttp_bound_socket;
 struct evconnlistener;
+struct evdns_base;
 
 /**
  * Create a new HTTP server.
@@ -234,6 +236,23 @@ void evhttp_set_gencb(struct evhttp *http,
     void (*cb)(struct evhttp_request *, void *), void *arg);
 
 /**
+   Set a callback used to create new bufferevents for connections
+   to a given evhttp object.
+
+   You can use this to override the default bufferevent type -- for example,
+   to make this evhttp object use SSL bufferevents rather than unencrypted
+   ones.
+
+   New bufferevents must be allocated with no fd set on them.
+
+   @param http the evhttp server object for which to set the callback
+   @param cb the callback to invoke for incoming connections
+   @param arg an context argument for the callback
+ */
+void evhttp_set_bevcb(struct evhttp *http,
+    struct bufferevent *(*cb)(struct event_base *, void *), void *arg);
+
+/**
    Adds a virtual host to the http server.
 
    A virtual host is a newly initialized evhttp object that has request
@@ -410,12 +429,10 @@ void evhttp_request_set_chunked_cb(struct evhttp_request *,
 /** Frees the request object and removes associated events. */
 void evhttp_request_free(struct evhttp_request *req);
 
-struct evdns_base;
-
 /**
- * A connection object that can be used to for making HTTP requests.  The
- * connection object tries to resolve address and establish the connection
- * when it is given an http request object.
+ * Create and return a connection object that can be used to for making HTTP
+ * requests.  The connection object tries to resolve address and establish the
+ * connection when it is given an http request object.
  *
  * @param base the event_base to use for handling the connection
  * @param dnsbase the dns_base to use for resolving host names; if not
@@ -429,6 +446,24 @@ struct evhttp_connection *evhttp_connection_base_new(
 	const char *address, unsigned short port);
 
 /**
+ * Create and return a connection object that can be used to for making HTTP
+ * requests.  The connection object tries to resolve address and establish the
+ * connection when it is given an http request object.
+ *
+ * @param base the event_base to use for handling the connection
+ * @param dnsbase the dns_base to use for resolving host names; if not
+ *     specified host name resolution will block.
+ * @param bev a bufferevent to use for connecting to the server; if NULL, a
+ *     socket-based bufferevent will be created.  This buffrevent will be freed
+ *     when the connection closes.  It must have no fd set on it.
+ * @param address the address to which to connect
+ * @param port the port to connect to
+ * @return an evhttp_connection object that can be used for making requests
+ */
+struct evhttp_connection *evhttp_connection_base_bufferevent_new(
+	struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev, const char *address, unsigned short port);
+
+/**
  * Return the bufferevent that an evhttp_connection is using.
  */
 struct bufferevent *evhttp_connection_get_bufferevent(
Adds a facility to evhttp for getting a new bufferevent every time a tcp connection is closed.  This allows evhttp to work for HTTPS where a second (or further) request is sent on a evhttp_connection that has previously timed out.  Here's a rundown of what the patch does:

1) adds the evhttp_connection_base_bufferevent_factory_new() API call that takes a callback function to create new bufferevents.
2) adds the callback (and the user-supplied void * argument to the callback) to the evhttp_connection struct
3) if the callback is set, calls it to generate a new bufferevent for the connection in evhttp_connection_reset().  the callback is not called if the bufferevent hasn't been used yet.

diff --git a/http-internal.h b/http-internal.h
index 2b4df8c..662a5a2 100644
--- a/http-internal.h
+++ b/http-internal.h
@@ -13,6 +13,7 @@
 #include "event2/event_struct.h"
 #include "util-internal.h"
 #include "defer-internal.h"
+#include "event2/http.h"
 
 #define HTTP_CONNECT_TIMEOUT	45
 #define HTTP_WRITE_TIMEOUT	50
@@ -66,6 +67,8 @@ struct evhttp_connection {
 
 	evutil_socket_t fd;
 	struct bufferevent *bufev;
+	bev_factory_cb bufcb;
+	void *bufcb_arg;
 
 	struct event retry_ev;		/* for retrying connects */
 
diff --git a/http.c b/http.c
index 771078e..a0749e2 100644
--- a/http.c
+++ b/http.c
@@ -1239,6 +1239,20 @@ evhttp_connection_reset(struct evhttp_connection *evcon)
 		if (evhttp_connected(evcon) && evcon->closecb != NULL)
 			(*evcon->closecb)(evcon, evcon->closecb_arg);
 
+		/* if we have a bufferevent factory callback set, get a new bufferevent */
+		if (NULL != evcon->bufcb && -1 != bufferevent_getfd(evcon->bufev)) {
+			struct bufferevent *bev = (*evcon->bufcb)(evcon->bufcb_arg);
+
+			if (NULL != bev) {
+				if (bufferevent_get_base(bev) != evcon->base) {
+					bufferevent_base_set(evcon->base, bev);
+				}
+
+				bufferevent_free(evcon->bufev);
+				evcon->bufev = bev;
+			}
+		}
+
 		shutdown(evcon->fd, EVUTIL_SHUT_WR);
 		evutil_closesocket(evcon->fd);
 		evcon->fd = -1;
@@ -2124,6 +2138,30 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
 	return (NULL);
 }
 
+struct evhttp_connection *evhttp_connection_base_bufferevent_factory_new(
+	struct event_base *base, struct evdns_base *dnsbase,
+	bev_factory_cb cb, void * arg, const char *address, unsigned short port)
+{
+	struct bufferevent *bev = NULL;
+
+	if (NULL != cb) {
+		if (NULL == (bev = (*cb)(arg))) {
+			event_warn("%s: bufferevent factory callback failed", __func__);
+			return (NULL);
+		}
+	}
+
+	struct evhttp_connection *ret =
+		evhttp_connection_base_bufferevent_new(base, dnsbase, bev, address, port);
+    
+	if (NULL != ret) {
+		ret->bufcb = cb;
+		ret->bufcb_arg = arg;
+	}
+
+	return (ret);
+}
+
 struct bufferevent *
 evhttp_connection_get_bufferevent(struct evhttp_connection *evcon)
 {
diff --git a/include/event2/http.h b/include/event2/http.h
index fd89d34..ec1248a 100644
--- a/include/event2/http.h
+++ b/include/event2/http.h
@@ -430,9 +430,11 @@ void evhttp_request_set_chunked_cb(struct evhttp_request *,
 void evhttp_request_free(struct evhttp_request *req);
 
 /**
- * Create and return a connection object that can be used to for making HTTP
+ * Create and return a connection object that can be used for making HTTP
  * requests.  The connection object tries to resolve address and establish the
- * connection when it is given an http request object.
+ * connection when it is given an http request object.  This function is
+ * equivalent to calling evhttp_connection_base_bufferevent_new with a NULL
+ * bufferevent.
  *
  * @param base the event_base to use for handling the connection
  * @param dnsbase the dns_base to use for resolving host names; if not
@@ -446,15 +448,17 @@ struct evhttp_connection *evhttp_connection_base_new(
 	const char *address, unsigned short port);
 
 /**
- * Create and return a connection object that can be used to for making HTTP
+ * Create and return a connection object that can be used for making HTTP
  * requests.  The connection object tries to resolve address and establish the
- * connection when it is given an http request object.
+ * connection when it is given an http request object.  The specified
+ * bufferevent will be reused even if the underlying persistent HTTP connection
+ * goes down and needs to be reestablished.
  *
  * @param base the event_base to use for handling the connection
  * @param dnsbase the dns_base to use for resolving host names; if not
  *     specified host name resolution will block.
  * @param bev a bufferevent to use for connecting to the server; if NULL, a
- *     socket-based bufferevent will be created.  This buffrevent will be freed
+ *     socket-based bufferevent will be created.  This bufferevent will be freed
  *     when the connection closes.  It must have no fd set on it.
  * @param address the address to which to connect
  * @param port the port to connect to
@@ -464,6 +468,38 @@ struct evhttp_connection *evhttp_connection_base_bufferevent_new(
 	struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev, const char *address, unsigned short port);
 
 /**
+ * Creates and returns a new bufferevent object.
+ */
+typedef struct bufferevent* (*bev_factory_cb)(void *);
+
+/**
+ * Create and return a connection object that can be used for making HTTP
+ * requests.  The connection object tries to resolve address and establish the
+ * connection when it is given an http request object.  The specified factory
+ * function is called with the user-supplied argument to retrieve a new
+ * bufferevent whenever the underlying HTTP connection needs to be
+ * reestablished.  This is what you want if, for example, you have a bufferevent
+ * that needs to perform some setup for new connections, such as an SSL
+ * bufferevent.
+ *
+ * @param base the event_base to use for handling the connection
+ * @param dnsbase the dns_base to use for resolving host names; if not
+ *     specified host name resolution will block.
+ * @param cb a callback that returns a new bufferevent to use for connecting to
+ *     the server; if NULL, behavior is the same as in calling
+ *     evhttp_connection_base_bufferevent_new with a NULL bufferevent.  The
+ *     returned bufferevents will be freed as necessary.  The returned
+ *     bufferevents must have no fd set on them.
+ * @param arg the argument to supply to the callback
+ * @param address the address to which to connect
+ * @param port the port to connect to
+ * @return an evhttp_connection object that can be used for making requests
+ */
+struct evhttp_connection *evhttp_connection_base_bufferevent_factory_new(
+	struct event_base *base, struct evdns_base *dnsbase,
+	bev_factory_cb cb, void * arg, const char *address, unsigned short port);
+
+/**
  * Return the bufferevent that an evhttp_connection is using.
  */
 struct bufferevent *evhttp_connection_get_bufferevent(