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_read ,
.Nm evbuffer_find , .Nm evbuffer_find ,
.Nm evbuffer_readline , .Nm evbuffer_readline ,
.Nm evhttp_start , .Nm evhttp_new ,
.Nm evhttp_bind_socket ,
.Nm evhttp_free .Nm evhttp_free
.Nd execute a function when a specific event occurs .Nd execute a function when a specific event occurs
.Sh SYNOPSIS .Sh SYNOPSIS
@ -181,9 +182,11 @@
.Ft "char *" .Ft "char *"
.Fn "evbuffer_readline" "struct evbuffer *buf" .Fn "evbuffer_readline" "struct evbuffer *buf"
.Ft "struct evhttp *" .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" .Ft "void"
.Fn "evhttp_free" "struct evhttp* http" .Fn "evhttp_free" "struct evhttp *http"
.Ft int .Ft int
.Fa (*event_sigcb)(void) ; .Fa (*event_sigcb)(void) ;
.Ft volatile sig_atomic_t .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 provides a very thin HTTP layer that can be used both to host an HTTP
server and also to make HTTP requests. server and also to make HTTP requests.
An HTTP server can be created by calling 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 When the HTTP server is no longer used, it can be freed via
.Fn evhttp_free . .Fn evhttp_free .
.Pp .Pp

View File

@ -62,11 +62,20 @@ struct evhttp;
struct evhttp_request; struct evhttp_request;
struct evkeyvalq; 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);
/*
* 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 create HTTP server. Works only if no requests are * Free the previously created HTTP server. Works only if no requests are
* currently being served. * currently being served.
*/ */
void evhttp_free(struct evhttp* http); 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_chunk(struct evhttp_request *, struct evbuffer *);
void evhttp_send_reply_end(struct evhttp_request *); 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_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD };
enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; 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 evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void (*)(struct evhttp_connection *, void *), void *); 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. */ /* Get the remote address and port associated with this connection. */
void evhttp_connection_get_peer(struct evhttp_connection *evcon, void evhttp_connection_get_peer(struct evhttp_connection *evcon,
char **address, u_short *port); char **address, u_short *port);

View File

@ -35,6 +35,8 @@ enum evhttp_connection_state {
EVCON_CONNECTED /* connection is established */ EVCON_CONNECTED /* connection is established */
}; };
struct event_base;
struct evhttp_connection { struct evhttp_connection {
/* we use tailq only if they were created for an http server */ /* we use tailq only if they were created for an http server */
TAILQ_ENTRY(evhttp_connection) next; TAILQ_ENTRY(evhttp_connection) next;
@ -69,6 +71,8 @@ struct evhttp_connection {
void (*closecb)(struct evhttp_connection *, void *); void (*closecb)(struct evhttp_connection *, void *);
void *closecb_arg; void *closecb_arg;
struct event_base *base;
}; };
struct evhttp_cb { struct evhttp_cb {
@ -93,6 +97,8 @@ struct evhttp {
void (*gencb)(struct evhttp_request *req, void *); void (*gencb)(struct evhttp_request *req, void *);
void *gencbarg; void *gencbarg;
struct event_base *base;
}; };
/* resets the connection; can be reused for more requests */ /* 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)) #define MIN(a,b) (((a)<(b))?(a):(b))
#endif #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 static int
event_make_socket_nonblocking(int fd) event_make_socket_nonblocking(int fd)
{ {
@ -277,6 +282,7 @@ evhttp_write_buffer(struct evhttp_connection *evcon,
event_del(&evcon->ev); event_del(&evcon->ev);
event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_write, evcon); 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); 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! */ /* Read more! */
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); 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); 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_del(&evcon->close_ev);
event_set(&evcon->close_ev, evcon->fd, EV_READ, event_set(&evcon->close_ev, evcon->fd, EV_READ,
evhttp_detect_close_cb, evcon); evhttp_detect_close_cb, evcon);
EVHTTP_BASE_SET(evcon, &evcon->ev);
event_add(&evcon->close_ev, NULL); event_add(&evcon->close_ev, NULL);
} }
@ -931,6 +939,7 @@ evhttp_connectioncb(int fd, short what, void *arg)
cleanup: cleanup:
if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) { if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) {
evtimer_set(&evcon->ev, evhttp_connection_retry, evcon); 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), evhttp_add_event(&evcon->ev, MIN(3600, 2 << evcon->retry_cnt),
HTTP_CONNECT_TIMEOUT); HTTP_CONNECT_TIMEOUT);
evcon->retry_cnt++; evcon->retry_cnt++;
@ -1403,6 +1412,14 @@ evhttp_connection_new(const char *address, unsigned short port)
return (NULL); 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 void
evhttp_connection_set_timeout(struct evhttp_connection *evcon, evhttp_connection_set_timeout(struct evhttp_connection *evcon,
int timeout_in_secs) int timeout_in_secs)
@ -1454,6 +1471,7 @@ evhttp_connection_connect(struct evhttp_connection *evcon)
/* Set up a callback for successful connection setup */ /* Set up a callback for successful connection setup */
event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_connectioncb, evcon); 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); evhttp_add_event(&evcon->ev, evcon->timeout, HTTP_CONNECT_TIMEOUT);
evcon->state = EVCON_CONNECTING; evcon->state = EVCON_CONNECTING;
@ -1519,6 +1537,7 @@ evhttp_start_read(struct evhttp_connection *evcon)
if (event_initialized(&evcon->ev)) if (event_initialized(&evcon->ev))
event_del(&evcon->ev); event_del(&evcon->ev);
event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read_header, evcon); 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); 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); evhttp_get_request(http, nfd, (struct sockaddr *)&ss, addrlen);
} }
static int int
bind_socket(struct evhttp *http, const char *address, u_short port) evhttp_bind_socket(struct evhttp *http, const char *address, u_short port)
{ {
struct event *ev = &http->bind_ev; struct event *ev = &http->bind_ev;
int fd; int fd;
@ -1908,11 +1927,13 @@ bind_socket(struct evhttp *http, const char *address, u_short port)
if (listen(fd, 10) == -1) { if (listen(fd, 10) == -1) {
event_warn("%s: listen", __func__); event_warn("%s: listen", __func__);
close(fd);
return (-1); return (-1);
} }
/* Schedule the socket for accepting */ /* Schedule the socket for accepting */
event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http); event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http);
EVHTTP_BASE_SET(http, ev);
event_add(ev, NULL); event_add(ev, NULL);
event_debug(("Bound to port %d - Awaiting connections ... ", port)); 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); return (0);
} }
/* static struct evhttp*
* Start a web server on the specified address and port. evhttp_new_object()
*/
struct evhttp *
evhttp_start(const char *address, u_short port)
{ {
struct evhttp *http; struct evhttp *http = NULL;
if ((http = calloc(1, sizeof(struct evhttp))) == NULL) { if ((http = calloc(1, sizeof(struct evhttp))) == NULL) {
event_warn("%s: calloc", __func__); event_warn("%s: calloc", __func__);
@ -1939,7 +1956,29 @@ evhttp_start(const char *address, u_short port)
TAILQ_INIT(&http->callbacks); TAILQ_INIT(&http->callbacks);
TAILQ_INIT(&http->connections); 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); free(http);
return (NULL); return (NULL);
} }
@ -2122,6 +2161,7 @@ evhttp_request_uri(struct evhttp_request *req) {
static struct evhttp_connection* static struct evhttp_connection*
evhttp_get_request_connection( evhttp_get_request_connection(
struct evhttp* http,
int fd, struct sockaddr *sa, socklen_t salen) int fd, struct sockaddr *sa, socklen_t salen)
{ {
struct evhttp_connection *evcon; struct evhttp_connection *evcon;
@ -2134,6 +2174,10 @@ evhttp_get_request_connection(
/* we need a connection object to put the http request on */ /* we need a connection object to put the http request on */
if ((evcon = evhttp_connection_new(hostname, atoi(portname))) == NULL) if ((evcon = evhttp_connection_new(hostname, atoi(portname))) == NULL)
return (NULL); return (NULL);
/* associate the base if we have one*/
evhttp_connection_set_base(evcon, http->base);
evcon->flags |= EVHTTP_CON_INCOMING; evcon->flags |= EVHTTP_CON_INCOMING;
evcon->state = EVCON_CONNECTED; evcon->state = EVCON_CONNECTED;
@ -2172,7 +2216,7 @@ evhttp_get_request(struct evhttp *http, int fd,
{ {
struct evhttp_connection *evcon; struct evhttp_connection *evcon;
evcon = evhttp_get_request_connection(fd, sa, salen); evcon = evhttp_get_request_connection(http, fd, sa, salen);
if (evcon == NULL) if (evcon == NULL)
return; return;

View File

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

View File

@ -61,22 +61,24 @@ extern int pair[];
extern int test_ok; extern int test_ok;
static struct evhttp *http; 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_basic_cb(struct evhttp_request *req, void *arg);
void http_post_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); void http_dispatcher_cb(struct evhttp_request *req, void *arg);
static struct evhttp * static struct evhttp *
http_setup(short *pport) http_setup(short *pport, struct event_base *base)
{ {
int i; int i;
struct evhttp *myhttp; struct evhttp *myhttp;
short port = -1; short port = -1;
/* Try a few different ports */ /* Try a few different ports */
myhttp = evhttp_new(base);
for (i = 0; i < 50; ++i) { for (i = 0; i < 50; ++i) {
myhttp = evhttp_start("127.0.0.1", 8080 + i); if (evhttp_bind_socket(myhttp, "127.0.0.1", 8080 + i) != -1) {
if (myhttp != NULL) {
port = 8080 + i; port = 8080 + i;
break; break;
} }
@ -130,7 +132,8 @@ http_readcb(struct bufferevent *bev, void *arg)
event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(bev->input))); 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); struct evhttp_request *req = evhttp_request_new(NULL, NULL);
int done; int done;
@ -143,7 +146,10 @@ http_readcb(struct bufferevent *bev, void *arg)
test_ok++; test_ok++;
evhttp_request_free(req); evhttp_request_free(req);
bufferevent_disable(bev, EV_READ); 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; test_ok = 0;
fprintf(stdout, "Testing Basic HTTP Server: "); fprintf(stdout, "Testing Basic HTTP Server: ");
http = http_setup(&port); http = http_setup(&port, NULL);
fd = http_connect("127.0.0.1", port); fd = http_connect("127.0.0.1", port);
@ -232,7 +238,7 @@ http_connection_test(int persistent)
fprintf(stdout, "Testing Request Connection Pipeline %s: ", fprintf(stdout, "Testing Request Connection Pipeline %s: ",
persistent ? "(persistent)" : ""); persistent ? "(persistent)" : "");
http = http_setup(&port); http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port); evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) { if (evcon == NULL) {
@ -379,7 +385,7 @@ http_dispatcher_test(void)
test_ok = 0; test_ok = 0;
fprintf(stdout, "Testing HTTP Dispatcher: "); fprintf(stdout, "Testing HTTP Dispatcher: ");
http = http_setup(&port); http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port); evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) { if (evcon == NULL) {
@ -437,7 +443,7 @@ http_post_test(void)
test_ok = 0; test_ok = 0;
fprintf(stdout, "Testing HTTP POST Request: "); fprintf(stdout, "Testing HTTP POST Request: ");
http = http_setup(&port); http = http_setup(&port, NULL);
evcon = evhttp_connection_new("127.0.0.1", port); evcon = evhttp_connection_new("127.0.0.1", port);
if (evcon == NULL) { if (evcon == NULL) {
@ -573,7 +579,7 @@ http_failure_test(void)
test_ok = 0; test_ok = 0;
fprintf(stdout, "Testing Bad HTTP Request: "); fprintf(stdout, "Testing Bad HTTP Request: ");
http = http_setup(&port); http = http_setup(&port, NULL);
fd = http_connect("127.0.0.1", port); fd = http_connect("127.0.0.1", port);
@ -661,7 +667,7 @@ http_close_detection(void)
test_ok = 0; test_ok = 0;
fprintf(stdout, "Testing Connection Close Detection: "); fprintf(stdout, "Testing Connection Close Detection: ");
http = http_setup(&port); http = http_setup(&port, NULL);
/* 2 second timeout */ /* 2 second timeout */
evhttp_set_timeout(http, 2); evhttp_set_timeout(http, 2);
@ -754,9 +760,64 @@ fail:
exit(1); 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 void
http_suite(void) http_suite(void)
{ {
http_base_test();
http_bad_header_test(); http_bad_header_test();
http_basic_test(); http_basic_test();
http_connection_test(0 /* not-persistent */); http_connection_test(0 /* not-persistent */);