http: allow passing in pre-existing connection bev (#1722)

This patch adds functionality to pass a pre-existing connection
as a bufferevent to `evhttp_connection_base_bufferevent_reuse_new`.

When the bufferevent has an existing fd, the evcon starts
in state `EVCON_IDLE` so that requests can be made immediately.

Signed-off-by: Kirill Rodriguez <theoden8@tutamail.com>
Signed-off-by: Kirill Rodriguez <theoden8@tutamail.com>
Co-authored-by: Wladimir J. van der Laan <laanwj@gmail.com>
Co-authored-by: Azat Khuzhin <a3at.mail@gmail.com>
This commit is contained in:
Kirill Rd 2024-11-02 21:39:57 +00:00 committed by GitHub
parent 6b7fb78c99
commit 98b7ca30b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 174 additions and 19 deletions

41
http.c
View File

@ -2534,6 +2534,18 @@ evhttp_connection_new(const char *address, ev_uint16_t port)
return (evhttp_connection_base_new(NULL, NULL, address, port));
}
/* We were passed a bev with file descriptor set.
* Assume that this is an already-open connection that we
* can start sending requests on.
*/
static int
evhttp_connection_set_existing_(struct evhttp_connection *evcon, struct bufferevent* bev)
{
evcon->state = EVCON_IDLE;
evcon->flags |= EVHTTP_CON_OUTGOING;
return 0;
}
static struct evhttp_connection *
evhttp_connection_new_(struct event_base *base, struct bufferevent* bev)
{
@ -2588,6 +2600,35 @@ evhttp_connection_new_(struct event_base *base, struct bufferevent* bev)
return (NULL);
}
struct evhttp_connection *
evhttp_connection_base_bufferevent_reuse_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev)
{
struct evhttp_connection *evcon = NULL;
if (bev == NULL)
goto error;
evcon = evhttp_connection_new_(base, bev);
if (evcon == NULL)
goto error;
if (evhttp_connection_set_existing_(evcon, bev))
goto error;
evcon->dns_base = dnsbase;
evcon->address = NULL;
evcon->port = 0;
#ifndef _WIN32
evcon->unixsocket = NULL;
#endif
return (evcon);
error:
if (evcon != NULL)
evhttp_connection_free(evcon);
return (NULL);
}
#ifndef _WIN32
struct evhttp_connection *
evhttp_connection_base_bufferevent_unix_new(struct event_base *base, struct bufferevent* bev, const char *unixsocket)

View File

@ -699,6 +699,24 @@ EVENT2_EXPORT_SYMBOL
struct evhttp_connection *evhttp_connection_base_bufferevent_unix_new(
struct event_base *base, struct bufferevent* bev, const char *path);
/**
* Create and return a connection object that can be used to for making HTTP
* requests. The connection attempts to reuse an existing connection that was
* already established with 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 bev a bufferevent to use for connecting to the server. A fd
* is already set on the bufferevent, it will be assumed that this
* connection is already open and ready to send requests.
* @return an evhttp_connection object that can be used for making requests or
* NULL on error
*/
EVENT2_EXPORT_SYMBOL
struct evhttp_connection *
evhttp_connection_base_bufferevent_reuse_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev);
/**
* Return the bufferevent that an evhttp_connection is using.
*/

View File

@ -561,7 +561,7 @@ create_bev(struct event_base *base, evutil_socket_t fd, int ssl_mask, int flags_
mbedtls_dyncontext *ssl = bufferevent_mbedtls_dyncontext_new(get_mbedtls_config(MBEDTLS_SSL_IS_CLIENT));
if (ssl_mask & HTTP_SSL_FILTER) {
struct bufferevent *underlying =
bufferevent_socket_new(base, fd, flags);
bufferevent_socket_new(base, fd, flags);
bev = bufferevent_mbedtls_filter_new(
base, underlying, ssl, BUFFEREVENT_SSL_CONNECTING, flags);
} else {
@ -2267,8 +2267,38 @@ http_dispatcher_test(void *arg)
/* test unix socket */
#include <sys/un.h>
static evutil_socket_t
http_connect_unixsocket(const char *path) {
struct sockaddr_un local;
struct stat st;
int fd;
socklen_t socklen = sizeof(local);
local.sun_family = AF_UNIX;
strcpy(local.sun_path, path);
if (stat(path, &st) != 0 || !S_ISSOCK(st.st_mode)) {
return EVUTIL_INVALID_SOCKET;
}
fd = evutil_socket_(AF_UNIX,
EVUTIL_SOCK_CLOEXEC | EVUTIL_SOCK_NONBLOCK | SOCK_STREAM, 0);
if (fd == EVUTIL_INVALID_SOCKET)
return EVUTIL_INVALID_SOCKET;
if (connect(fd, (struct sockaddr *)&local, socklen) < 0) {
perror("connect");
event_debug(("errno:%d (EBADF:%d,ENOTSOCK:%d,ECONNREFUSED:%d,EFAULT:%d)", errno, EBADF, ENOTSOCK, ECONNREFUSED, EFAULT));
close(fd);
return EVUTIL_INVALID_SOCKET;
}
return fd;
}
/* Should this be part of the libevent library itself? */
static int evhttp_bind_unixsocket(struct evhttp *httpd, const char *path)
static int
evhttp_bind_unixsocket(struct evhttp *httpd, const char *path)
{
struct sockaddr_un local;
struct stat st;
@ -2311,15 +2341,17 @@ static int evhttp_bind_unixsocket(struct evhttp *httpd, const char *path)
return 0;
}
static void http_unix_socket_test(void *arg)
static void http_unix_socket_test_(struct basic_test_data *data, int preexisting)
{
struct basic_test_data *data = arg;
struct evhttp_uri *uri = NULL;
struct bufferevent *bev = NULL;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req;
struct evhttp *myhttp;
char tmp_sock_path[512];
char uri_loc[1024];
evutil_socket_t client_fd = EVUTIL_INVALID_SOCKET;
// Avoid overlap with parallel runs
evutil_snprintf(tmp_sock_path, sizeof(tmp_sock_path), "/tmp/eventtmp.%i.sock", getpid());
@ -2333,7 +2365,14 @@ static void http_unix_socket_test(void *arg)
uri = evhttp_uri_parse_with_flags(uri_loc, EVHTTP_URI_UNIX_SOCKET);
tt_assert(uri);
evcon = evhttp_connection_base_bufferevent_unix_new(data->base, NULL, evhttp_uri_get_unixsocket(uri));
if (preexisting) {
client_fd = http_connect_unixsocket(tmp_sock_path);
tt_fd_op(client_fd, !=, EVUTIL_INVALID_SOCKET);
bev = create_bev(data->base, client_fd, 0x00, BEV_OPT_CLOSE_ON_FREE);
evcon = evhttp_connection_base_bufferevent_reuse_new(data->base, NULL, bev);
} else {
evcon = evhttp_connection_base_bufferevent_unix_new(data->base, NULL, evhttp_uri_get_unixsocket(uri));
}
tt_assert(evcon);
/*
* At this point, we want to schedule an HTTP GET request
@ -2351,6 +2390,10 @@ static void http_unix_socket_test(void *arg)
event_base_dispatch(data->base);
if (preexisting) {
tt_fd_op(bufferevent_getfd(bev), ==, client_fd);
}
end:
if (evcon)
evhttp_connection_free(evcon);
@ -2363,6 +2406,18 @@ static void http_unix_socket_test(void *arg)
if (!strstr(tmp_sock_path, "XXXXXX"))
unlink(tmp_sock_path);
}
static void http_unix_socket_test(void *arg)
{
struct basic_test_data *data = arg;
http_unix_socket_test_(data, 0);
}
static void http_unix_socket_preexist_test(void *arg)
{
struct basic_test_data *data = arg;
http_unix_socket_test_(data, 1);
}
#endif
/*
@ -4383,30 +4438,46 @@ http_make_web_server(evutil_socket_t fd, short what, void *arg)
}
static void
http_simple_test_impl(void *arg, int ssl, int dirty, const char *uri)
http_simple_test_impl(void *arg, int ssl, int dirty, int preexisting, const char *uri)
{
struct basic_test_data *data = arg;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req = NULL;
struct bufferevent *bev;
struct bufferevent *bev = NULL;
struct http_server hs = { 0, ssl, NULL, };
struct evhttp *http = http_setup(&hs.port, data->base, ssl);
evutil_socket_t client_fd = EVUTIL_INVALID_SOCKET;
exit_base = data->base;
test_ok = 0;
bev = create_bev(data->base, -1, ssl, BEV_OPT_CLOSE_ON_FREE);
if (preexisting) {
client_fd = http_connect("127.0.0.1", hs.port);
tt_fd_op(client_fd, !=, EVUTIL_INVALID_SOCKET);
}
bev = create_bev(data->base, client_fd, ssl, BEV_OPT_CLOSE_ON_FREE);
tt_fd_op(bufferevent_getfd(bev), ==, client_fd);
#ifdef EVENT__HAVE_OPENSSL
bufferevent_openssl_set_allow_dirty_shutdown(bev, dirty);
if (ssl == HTTP_OPENSSL) {
bufferevent_openssl_set_allow_dirty_shutdown(bev, dirty);
}
#endif
#ifdef EVENT__HAVE_MBEDTLS
bufferevent_mbedtls_set_allow_dirty_shutdown(bev, dirty);
if (ssl == HTTP_MBEDTLS) {
bufferevent_mbedtls_set_allow_dirty_shutdown(bev, dirty);
}
#endif
evcon = evhttp_connection_base_bufferevent_new(
data->base, NULL, bev, "127.0.0.1", hs.port);
if (preexisting) {
evcon = evhttp_connection_base_bufferevent_reuse_new(data->base, NULL, bev);
} else {
evcon = evhttp_connection_base_bufferevent_new(
data->base, NULL, bev, "127.0.0.1", hs.port);
}
tt_assert(evcon);
evhttp_connection_set_local_address(evcon, "127.0.0.1");
if (!preexisting) {
evhttp_connection_set_local_address(evcon, "127.0.0.1");
}
req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
tt_assert(req);
@ -4417,6 +4488,10 @@ http_simple_test_impl(void *arg, int ssl, int dirty, const char *uri)
event_base_dispatch(data->base);
tt_int_op(test_ok, ==, 1);
if (preexisting) {
tt_fd_op(bufferevent_getfd(bev),==,client_fd);
}
end:
if (evcon)
evhttp_connection_free(evcon);
@ -4424,9 +4499,13 @@ http_simple_test_impl(void *arg, int ssl, int dirty, const char *uri)
evhttp_free(http);
}
static void http_simple_test(void *arg)
{ http_simple_test_impl(arg, 0, 0, "/test"); }
{ http_simple_test_impl(arg, 0, 0, 0, "/test"); }
static void http_simple_preexisting_test(void *arg)
{ http_simple_test_impl(arg, 0, 0, 1, "/test"); }
static void http_simple_nonconformant_test(void *arg)
{ http_simple_test_impl(arg, 0, 0, "/test nonconformant"); }
{ http_simple_test_impl(arg, 0, 0, 0, "/test nonconformant"); }
static void http_simple_nonconformant_preexisting_test(void *arg)
{ http_simple_test_impl(arg, 0, 0, 1, "/test nonconformant"); }
static int
https_bind_ssl_bevcb(struct evhttp *http, ev_uint16_t port, ev_uint16_t *pport, int mask)
@ -5954,9 +6033,13 @@ static void https_incomplete_test(void *arg)
static void https_incomplete_timeout_test(void *arg)
{ http_incomplete_test_(arg, 1, HTTP_OPENSSL); }
static void https_simple_test(void *arg)
{ http_simple_test_impl(arg, HTTP_OPENSSL, 0, "/test"); }
{ http_simple_test_impl(arg, HTTP_OPENSSL, 0, 0, "/test"); }
static void https_simple_preexisting_test(void *arg)
{ http_simple_test_impl(arg, HTTP_OPENSSL, 0, 1, "/test"); }
static void https_simple_dirty_test(void *arg)
{ http_simple_test_impl(arg, HTTP_OPENSSL, 1, "/test"); }
{ http_simple_test_impl(arg, HTTP_OPENSSL, 1, 0, "/test"); }
static void https_simple_dirty_preexisting_test(void *arg)
{ http_simple_test_impl(arg, HTTP_OPENSSL, 1, 1, "/test"); }
static void https_connection_retry_conn_address_test(void *arg)
{ http_connection_retry_conn_address_test_impl(arg, HTTP_OPENSSL); }
static void https_connection_retry_test(void *arg)
@ -5989,9 +6072,13 @@ static void https_mbedtls_incomplete_test(void *arg)
static void https_mbedtls_incomplete_timeout_test(void *arg)
{ http_incomplete_test_(arg, 1, HTTP_MBEDTLS); }
static void https_mbedtls_simple_test(void *arg)
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 0, "/test"); }
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 0, 0, "/test"); }
static void https_mbedtls_simple_preexisting_test(void *arg)
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 0, 1, "/test"); }
static void https_mbedtls_simple_dirty_test(void *arg)
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 1, "/test"); }
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 1, 0, "/test"); }
static void https_mbedtls_simple_dirty_preexisting_test(void *arg)
{ http_simple_test_impl(arg, HTTP_MBEDTLS, 1, 1, "/test"); }
static void https_mbedtls_connection_retry_conn_address_test(void *arg)
{ http_connection_retry_conn_address_test_impl(arg, HTTP_MBEDTLS); }
static void https_mbedtls_connection_retry_test(void *arg)
@ -6029,7 +6116,9 @@ struct testcase_t http_testcases[] = {
HTTP(basic),
HTTP(basic_trailing_space),
HTTP(simple),
HTTP(simple_preexisting),
HTTP(simple_nonconformant),
HTTP(simple_nonconformant_preexisting),
HTTP_N(cancel, cancel, 0, BASIC),
HTTP_RET_N(cancel_by_host, cancel, 0, BY_HOST),
@ -6047,6 +6136,7 @@ struct testcase_t http_testcases[] = {
HTTP(virtual_host),
#ifndef _WIN32
HTTP(unix_socket),
HTTP(unix_socket_preexist),
#endif
HTTP(post),
HTTP(put),
@ -6120,7 +6210,9 @@ struct testcase_t http_testcases[] = {
HTTPS(basic),
HTTPS(filter_basic),
HTTPS(simple),
HTTPS(simple_preexisting),
HTTPS(simple_dirty),
HTTPS(simple_dirty_preexisting),
HTTPS(incomplete),
HTTPS(incomplete_timeout),
{ "https_connection_retry", https_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL },
@ -6140,7 +6232,9 @@ struct testcase_t http_testcases[] = {
HTTPS_MBEDTLS(basic),
HTTPS_MBEDTLS(filter_basic),
HTTPS_MBEDTLS(simple),
HTTPS_MBEDTLS(simple_preexisting),
HTTPS_MBEDTLS(simple_dirty),
HTTPS_MBEDTLS(simple_dirty_preexisting),
HTTPS_MBEDTLS(incomplete),
HTTPS_MBEDTLS(incomplete_timeout),
{ "https_mbedtls_connection_retry", https_mbedtls_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL },
@ -6163,9 +6257,11 @@ struct testcase_t http_iocp_testcases[] = {
{ "simple", http_simple_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL },
#ifdef EVENT__HAVE_OPENSSL
{ "https_openssl_simple", https_simple_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL },
{ "https_openssl_simple_preexisting", https_simple_preexisting_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL },
#endif
#ifdef EVENT__HAVE_MBEDTLS
{ "https_mbedtls_simple", https_mbedtls_simple_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &mbedtls_setup, NULL },
{ "https_mbedtls_simple_preexisting", https_mbedtls_simple_preexisting_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &mbedtls_setup, NULL },
#endif
END_OF_TESTCASES
};