[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(