provide evhttp_new and evhttp_bind_socket instead of evhttp_start;

using evhttp_new, it is possible to associate an event_base with
the http server so that multi-threaded applications can have their
own http server per thread; add appropriate testing.


svn:r397
This commit is contained in:
Niels Provos 2007-08-19 02:41:23 +00:00
parent 35983cd60f
commit 67947ce381
6 changed files with 181 additions and 39 deletions

14
event.3
View File

@ -78,7 +78,8 @@
.Nm evbuffer_read ,
.Nm evbuffer_find ,
.Nm evbuffer_readline ,
.Nm evhttp_start ,
.Nm evhttp_new ,
.Nm evhttp_bind_socket ,
.Nm evhttp_free
.Nd execute a function when a specific event occurs
.Sh SYNOPSIS
@ -181,9 +182,11 @@
.Ft "char *"
.Fn "evbuffer_readline" "struct evbuffer *buf"
.Ft "struct evhttp *"
.Fn "evhttp_start" "const char *address" "u_short port"
.Fn "evhttp_new"
.Ft int
.Fn "evhttp_bind_socket" "struct evhttp *http" "const char *address" "u_short port"
.Ft "void"
.Fn "evhttp_free" "struct evhttp* http"
.Fn "evhttp_free" "struct evhttp *http"
.Ft int
.Fa (*event_sigcb)(void) ;
.Ft volatile sig_atomic_t
@ -539,7 +542,10 @@ enabling the bufferevent for the first time.
provides a very thin HTTP layer that can be used both to host an HTTP
server and also to make HTTP requests.
An HTTP server can be created by calling
.Fn evhttp_start .
.Fn evhttp_new .
It can be bound to any port and address with the
.Fn evhttp_bind_socket
function.
When the HTTP server is no longer used, it can be freed via
.Fn evhttp_free .
.Pp

View File

@ -62,11 +62,20 @@ struct evhttp;
struct evhttp_request;
struct evkeyvalq;
/* Start an HTTP server on the specified address and port */
struct evhttp *evhttp_start(const char *address, u_short port);
/*
* creates a new HTTP server; if base is specified events from the http server
* are going to be created on that event base.
*/
struct evhttp *evhttp_new(struct event_base *base);
/*
* Free the previously create HTTP server. Works only if no requests are
* binds the http server to specific port and can be called multiple times
* to bind the same http server to multiple different ports.
*/
int evhttp_bind_socket(struct evhttp *http, const char *address, u_short port);
/*
* Free the previously created HTTP server. Works only if no requests are
* currently being served.
*/
void evhttp_free(struct evhttp* http);
@ -95,7 +104,15 @@ void evhttp_send_reply_start(struct evhttp_request *, int, const char *);
void evhttp_send_reply_chunk(struct evhttp_request *, struct evbuffer *);
void evhttp_send_reply_end(struct evhttp_request *);
/* Interfaces for making requests */
/*
* Start an HTTP server on the specified address and port
* DEPRECATED: it does not allow an event base to be specified
*/
struct evhttp *evhttp_start(const char *address, u_short port);
/*
* Interfaces for making requests
*/
enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD };
enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE };
@ -189,6 +206,13 @@ void evhttp_connection_set_retries(struct evhttp_connection *evcon,
void evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void (*)(struct evhttp_connection *, void *), void *);
/*
* Associates an event base with the connection - can only be called
* on a freshly created connection object that has not been used yet.
*/
void evhttp_connection_set_base(struct evhttp_connection *evcon,
struct event_base *base);
/* Get the remote address and port associated with this connection. */
void evhttp_connection_get_peer(struct evhttp_connection *evcon,
char **address, u_short *port);

View File

@ -35,6 +35,8 @@ enum evhttp_connection_state {
EVCON_CONNECTED /* connection is established */
};
struct event_base;
struct evhttp_connection {
/* we use tailq only if they were created for an http server */
TAILQ_ENTRY(evhttp_connection) next;
@ -69,6 +71,8 @@ struct evhttp_connection {
void (*closecb)(struct evhttp_connection *, void *);
void *closecb_arg;
struct event_base *base;
};
struct evhttp_cb {
@ -93,6 +97,8 @@ struct evhttp {
void (*gencb)(struct evhttp_request *req, void *);
void *gencbarg;
struct event_base *base;
};
/* resets the connection; can be reused for more requests */

66
http.c
View File

@ -116,6 +116,11 @@ fake_freeaddrinfo(struct addrinfo *ai)
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
/* wrapper for setting the base from the http server */
#define EVHTTP_BASE_SET(x, y) do { \
if ((x)->base != NULL) event_base_set((x)->base, y); \
} while (0)
static int
event_make_socket_nonblocking(int fd)
{
@ -277,6 +282,7 @@ evhttp_write_buffer(struct evhttp_connection *evcon,
event_del(&evcon->ev);
event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_write, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_WRITE_TIMEOUT);
}
@ -709,6 +715,7 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req)
}
/* Read more! */
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT);
}
@ -865,6 +872,7 @@ evhttp_connection_start_detectclose(struct evhttp_connection *evcon)
event_del(&evcon->close_ev);
event_set(&evcon->close_ev, evcon->fd, EV_READ,
evhttp_detect_close_cb, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
event_add(&evcon->close_ev, NULL);
}
@ -931,6 +939,7 @@ evhttp_connectioncb(int fd, short what, void *arg)
cleanup:
if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) {
evtimer_set(&evcon->ev, evhttp_connection_retry, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, MIN(3600, 2 << evcon->retry_cnt),
HTTP_CONNECT_TIMEOUT);
evcon->retry_cnt++;
@ -1403,6 +1412,14 @@ evhttp_connection_new(const char *address, unsigned short port)
return (NULL);
}
void evhttp_connection_set_base(struct evhttp_connection *evcon,
struct event_base *base)
{
assert(evcon->base == NULL);
assert(evcon->state == EVCON_DISCONNECTED);
evcon->base = base;
}
void
evhttp_connection_set_timeout(struct evhttp_connection *evcon,
int timeout_in_secs)
@ -1454,6 +1471,7 @@ evhttp_connection_connect(struct evhttp_connection *evcon)
/* Set up a callback for successful connection setup */
event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_connectioncb, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_CONNECT_TIMEOUT);
evcon->state = EVCON_CONNECTING;
@ -1519,6 +1537,7 @@ evhttp_start_read(struct evhttp_connection *evcon)
if (event_initialized(&evcon->ev))
event_del(&evcon->ev);
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read_header, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_READ_TIMEOUT);
}
@ -1897,8 +1916,8 @@ accept_socket(int fd, short what, void *arg)
evhttp_get_request(http, nfd, (struct sockaddr *)&ss, addrlen);
}
static int
bind_socket(struct evhttp *http, const char *address, u_short port)
int
evhttp_bind_socket(struct evhttp *http, const char *address, u_short port)
{
struct event *ev = &http->bind_ev;
int fd;
@ -1908,11 +1927,13 @@ bind_socket(struct evhttp *http, const char *address, u_short port)
if (listen(fd, 10) == -1) {
event_warn("%s: listen", __func__);
close(fd);
return (-1);
}
/* Schedule the socket for accepting */
event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http);
EVHTTP_BASE_SET(http, ev);
event_add(ev, NULL);
event_debug(("Bound to port %d - Awaiting connections ... ", port));
@ -1920,14 +1941,10 @@ bind_socket(struct evhttp *http, const char *address, u_short port)
return (0);
}
/*
* Start a web server on the specified address and port.
*/
struct evhttp *
evhttp_start(const char *address, u_short port)
static struct evhttp*
evhttp_new_object()
{
struct evhttp *http;
struct evhttp *http = NULL;
if ((http = calloc(1, sizeof(struct evhttp))) == NULL) {
event_warn("%s: calloc", __func__);
@ -1939,7 +1956,29 @@ evhttp_start(const char *address, u_short port)
TAILQ_INIT(&http->callbacks);
TAILQ_INIT(&http->connections);
if (bind_socket(http, address, port) == -1) {
return (http);
}
struct evhttp *
evhttp_new(struct event_base *base)
{
struct evhttp *http = evhttp_new_object();
http->base = base;
return (http);
}
/*
* Start a web server on the specified address and port.
*/
struct evhttp *
evhttp_start(const char *address, u_short port)
{
struct evhttp *http = evhttp_new_object();
if (evhttp_bind_socket(http, address, port) == -1) {
free(http);
return (NULL);
}
@ -2122,6 +2161,7 @@ evhttp_request_uri(struct evhttp_request *req) {
static struct evhttp_connection*
evhttp_get_request_connection(
struct evhttp* http,
int fd, struct sockaddr *sa, socklen_t salen)
{
struct evhttp_connection *evcon;
@ -2134,6 +2174,10 @@ evhttp_get_request_connection(
/* we need a connection object to put the http request on */
if ((evcon = evhttp_connection_new(hostname, atoi(portname))) == NULL)
return (NULL);
/* associate the base if we have one*/
evhttp_connection_set_base(evcon, http->base);
evcon->flags |= EVHTTP_CON_INCOMING;
evcon->state = EVCON_CONNECTED;
@ -2172,7 +2216,7 @@ evhttp_get_request(struct evhttp *http, int fd,
{
struct evhttp_connection *evcon;
evcon = evhttp_get_request_connection(fd, sa, salen);
evcon = evhttp_get_request_connection(http, fd, sa, salen);
if (evcon == NULL)
return;

View File

@ -71,7 +71,7 @@ static int roff;
static int usepersist;
static struct timeval tset;
static struct timeval tcalled;
static struct event_base *event_base;
static struct event_base *global_base;
#define TEST1 "this is a test"
#define SECONDS 1
@ -594,7 +594,7 @@ void
test_evbuffer(void) {
struct evbuffer *evb = evbuffer_new();
setup_test("Evbuffer: ");
setup_test("Testing Evbuffer: ");
evbuffer_add_printf(evb, "%s/%d", "hello", 1);
@ -742,10 +742,10 @@ test_priorities(int npriorities)
struct test_pri_event one, two;
struct timeval tv;
snprintf(buf, sizeof(buf), "Priorities %d: ", npriorities);
snprintf(buf, sizeof(buf), "Testing Priorities %d: ", npriorities);
setup_test(buf);
event_base_priority_init(event_base, npriorities);
event_base_priority_init(global_base, npriorities);
memset(&one, 0, sizeof(one));
memset(&two, 0, sizeof(two));
@ -1042,7 +1042,12 @@ main (int argc, char **argv)
setvbuf(stdout, NULL, _IONBF, 0);
/* Initalize the event library */
event_base = event_init();
global_base = event_init();
/* use the global event base and need to be called first */
test_priorities(1);
test_priorities(2);
test_priorities(3);
test_evbuffer();
test_evbuffer_find();
@ -1072,10 +1077,6 @@ main (int argc, char **argv)
#endif
test_loopexit();
test_priorities(1);
test_priorities(2);
test_priorities(3);
test_multiple_events_for_same_fd();
test_want_only_once();

View File

@ -61,22 +61,24 @@ extern int pair[];
extern int test_ok;
static struct evhttp *http;
/* set if a test needs to call loopexit on a base */
static struct event_base *base;
void http_basic_cb(struct evhttp_request *req, void *arg);
void http_post_cb(struct evhttp_request *req, void *arg);
void http_dispatcher_cb(struct evhttp_request *req, void *arg);
static struct evhttp *
http_setup(short *pport)
http_setup(short *pport, struct event_base *base)
{
int i;
struct evhttp *myhttp;
short port = -1;
/* Try a few different ports */
myhttp = evhttp_new(base);
for (i = 0; i < 50; ++i) {
myhttp = evhttp_start("127.0.0.1", 8080 + i);
if (myhttp != NULL) {
if (evhttp_bind_socket(myhttp, "127.0.0.1", 8080 + i) != -1) {
port = 8080 + i;
break;
}
@ -130,7 +132,8 @@ http_readcb(struct bufferevent *bev, void *arg)
event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(bev->input)));
if (evbuffer_find(bev->input, (const unsigned char*) what, strlen(what)) != NULL) {
if (evbuffer_find(bev->input,
(const unsigned char*) what, strlen(what)) != NULL) {
struct evhttp_request *req = evhttp_request_new(NULL, NULL);
int done;
@ -143,7 +146,10 @@ http_readcb(struct bufferevent *bev, void *arg)
test_ok++;
evhttp_request_free(req);
bufferevent_disable(bev, EV_READ);
event_loopexit(NULL);
if (base)
event_base_loopexit(base, NULL);
else
event_loopexit(NULL);
}
}
@ -188,7 +194,7 @@ http_basic_test(void)
test_ok = 0;
fprintf(stdout, "Testing Basic HTTP Server: ");
http = http_setup(&port);
http = http_setup(&port, NULL);
fd = http_connect("127.0.0.1", port);
@ -232,7 +238,7 @@ http_connection_test(int persistent)
fprintf(stdout, "Testing Request Connection Pipeline %s: ",
persistent ? "(persistent)" : "");
http = http_setup(&port);
http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) {
@ -379,7 +385,7 @@ http_dispatcher_test(void)
test_ok = 0;
fprintf(stdout, "Testing HTTP Dispatcher: ");
http = http_setup(&port);
http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) {
@ -437,7 +443,7 @@ http_post_test(void)
test_ok = 0;
fprintf(stdout, "Testing HTTP POST Request: ");
http = http_setup(&port);
http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) {
@ -573,7 +579,7 @@ http_failure_test(void)
test_ok = 0;
fprintf(stdout, "Testing Bad HTTP Request: ");
http = http_setup(&port);
http = http_setup(&port, NULL);
fd = http_connect("127.0.0.1", port);
@ -661,7 +667,7 @@ http_close_detection(void)
test_ok = 0;
fprintf(stdout, "Testing Connection Close Detection: ");
http = http_setup(&port);
http = http_setup(&port, NULL);
/* 2 second timeout */
evhttp_set_timeout(http, 2);
@ -754,9 +760,64 @@ fail:
exit(1);
}
void
http_base_test(void)
{
struct bufferevent *bev;
int fd;
char *http_request;
short port = -1;
test_ok = 0;
fprintf(stdout, "Testing HTTP Server Event Base: ");
base = event_init();
/*
* create another bogus base - which is being used by all subsequen
* tests - yuck!
*/
event_init();
http = http_setup(&port, base);
fd = http_connect("127.0.0.1", port);
/* Stupid thing to send a request */
bev = bufferevent_new(fd, http_readcb, http_writecb,
http_errorcb, NULL);
bufferevent_base_set(base, bev);
http_request =
"GET /test HTTP/1.1\r\n"
"Host: somehost\r\n"
"Connection: close\r\n"
"\r\n";
bufferevent_write(bev, http_request, strlen(http_request));
event_base_dispatch(base);
bufferevent_free(bev);
close(fd);
evhttp_free(http);
event_base_free(base);
base = NULL;
if (test_ok != 2) {
fprintf(stdout, "FAILED\n");
exit(1);
}
fprintf(stdout, "OK\n");
}
void
http_suite(void)
{
http_base_test();
http_bad_header_test();
http_basic_test();
http_connection_test(0 /* not-persistent */);