From ba7262ebdf82da9c285f39200d69aac54013c16a Mon Sep 17 00:00:00 2001 From: Niels Provos Date: Mon, 17 Jul 2006 00:33:57 +0000 Subject: [PATCH] reorganization of the http functionality; we separate http handling into a connection object and a request object; also make it clear which buffers are used for input and output; unittests not complete yet. svn:r217 --- evhttp.h | 23 +++ http-internal.h | 70 ++++--- http.c | 458 +++++++++++++++++++++++++++++--------------- test/Makefile.am | 2 +- test/regress_http.c | 111 +++++------ 5 files changed, 410 insertions(+), 254 deletions(-) diff --git a/evhttp.h b/evhttp.h index ac1889e9..620ea3fd 100644 --- a/evhttp.h +++ b/evhttp.h @@ -82,9 +82,32 @@ void evhttp_send_reply(struct evhttp_request *, int, const char *, /* Interfaces for making requests */ enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; +/* Creates a new request object that needs to be filled in with the request + * parameters. The callback is executed when the request completed or an + * error occurred. + */ struct evhttp_request *evhttp_request_new( void (*cb)(struct evhttp_request *, void *), void *arg); + +/* Frees the request object and removes associated events. */ void evhttp_request_free(struct evhttp_request *req); + +/* + * A connection object that can be used to for making HTTP requests. The + * connection object tries to establish the connection when it is given an + * http request object. + */ +struct evhttp_connection *evhttp_connection_new( + const char *address, unsigned short port); + +/* Frees an http connection */ +void evhttp_connection_free(struct evhttp_connection *evcon); + +/* The connection gets ownership of the request */ +int evhttp_make_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri); + const char *evhttp_request_uri(struct evhttp_request *req); /* Interfaces for dealing with HTTP headers */ diff --git a/http-internal.h b/http-internal.h index 29e87b20..f5ad2861 100644 --- a/http-internal.h +++ b/http-internal.h @@ -19,15 +19,32 @@ struct evbuffer; struct addrinfo; +struct evhttp_request; /* A stupid connection object - maybe make this a bufferevent later */ +enum evhttp_connection_state { + EVCON_DISCONNECTED, /* not currently connected not trying either */ + EVCON_CONNECTING, /* tries to currently connect */ + EVCON_CONNECTED /* connection is established */ +}; + struct evhttp_connection { int fd; struct event ev; - + struct evbuffer *input_buffer; + struct evbuffer *output_buffer; + char *address; u_short port; + + int flags; +#define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ +#define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ + + enum evhttp_connection_state state; + + TAILQ_HEAD(evcon_requestq, evhttp_request) requests; void (*cb)(struct evhttp_connection *, void *); void *cb_arg; @@ -36,9 +53,17 @@ struct evhttp_connection { enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; struct evhttp_request { + TAILQ_ENTRY(evhttp_request) next; + + /* the connection object that this request belongs to */ + struct evhttp_connection *evcon; + int flags; +#define EVHTTP_REQ_OWN_CONNECTION 0x0001 + struct evkeyvalq *input_headers; struct evkeyvalq *output_headers; + /* xxx: do we still need these? */ char *remote_host; u_short remote_port; @@ -54,19 +79,14 @@ struct evhttp_request { int response_code; /* HTTP Response code */ char *response_code_line; /* Readable response */ - int fd; - - struct event ev; - - struct evbuffer *buffer; + struct evbuffer *input_buffer; /* read data */ int ntoread; + struct evbuffer *output_buffer; /* outgoing post or data */ + /* Callback */ void (*cb)(struct evhttp_request *, void *); void *cb_arg; - - void (*save_cb)(struct evhttp_request *, void *); - void *save_cbarg; }; struct evhttp_cb { @@ -87,39 +107,31 @@ struct evhttp { void *gencbarg; }; -void evhttp_get_request(int, struct sockaddr *, socklen_t, - void (*)(struct evhttp_request *, void *), void *); +/* resets the connection; can be reused for more requests */ +void evhttp_connection_reset(struct evhttp_connection *); -/* - * Starts a connection to the specified address and invokes the callback - * if everything is fine. - */ -struct evhttp_connection *evhttp_connect( - const char *address, unsigned short port, - void (*cb)(struct evhttp_connection *, void *), void *cb_arg); +/* connects if necessary */ +int evhttp_connection_connect(struct evhttp_connection *); -/* Frees an http connection */ -void evhttp_connection_free(struct evhttp_connection *evcon); +/* notifies the current request that it failed; resets connection */ +void evhttp_connection_fail(struct evhttp_connection *); -int evhttp_make_request(struct evhttp_connection *evcon, - struct evhttp_request *req, - enum evhttp_cmd_type type, const char *uri); +void evhttp_get_request(int, struct sockaddr *, socklen_t, + void (*)(struct evhttp_request *, void *), void *); int evhttp_hostportfile(char *, char **, u_short *, char **); int evhttp_parse_lines(struct evhttp_request *, struct evbuffer*); -void evhttp_start_read(struct evhttp_request *); +void evhttp_start_read(struct evhttp_connection *); void evhttp_read_header(int, short, void *); -void evhttp_make_header(struct evbuffer *, struct evhttp_request *); +void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); -void evhttp_form_response(struct evbuffer *, struct evhttp_request *); -void evhttp_write_buffer(struct evhttp_request *, struct evbuffer *, - void (*)(struct evhttp_request *, void *), void *); +void evhttp_write_buffer(struct evhttp_connection *, + void (*)(struct evhttp_connection *, void *), void *); /* response sending HTML the data in the buffer */ void evhttp_response_code(struct evhttp_request *, int, const char *); void evhttp_send_page(struct evhttp_request *, struct evbuffer *); -void evhttp_fail(struct evhttp_request *); #endif /* _HTTP_H */ diff --git a/http.c b/http.c index fa591121..97104f3f 100644 --- a/http.c +++ b/http.c @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -162,49 +163,30 @@ evhttp_method(enum evhttp_cmd_type type) } void -evhttp_form_response(struct evbuffer *buf, struct evhttp_request *req) -{ - /* Clean out the buffer */ - evbuffer_drain(buf, buf->off); - - /* Create the header fields */ - evhttp_make_header(buf, req); - - /* Append the response buffer */ - evbuffer_add(buf, - EVBUFFER_DATA(req->buffer), EVBUFFER_LENGTH(req->buffer)); -} - -void -evhttp_write_buffer(struct evhttp_request *req, struct evbuffer *buffer, - void (*cb)(struct evhttp_request *, void *), void *arg) +evhttp_write_buffer(struct evhttp_connection *evcon, + void (*cb)(struct evhttp_connection *, void *), void *arg) { struct timeval tv; event_debug(("%s: preparing to write buffer\n", __func__)); - if (req->buffer != buffer) { - if (req->buffer != NULL) - evbuffer_free(req->buffer); - req->buffer = buffer; - } - /* Set call back */ + evcon->cb = cb; + evcon->cb_arg = arg; - req->cb = cb; - req->cb_arg = arg; - - event_set(&req->ev, req->fd, EV_WRITE, evhttp_write, req); + /* xxx: maybe check if the event is still pending? */ + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_write, evcon); timerclear(&tv); tv.tv_sec = HTTP_WRITE_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); } /* * Create the headers need for an HTTP reply */ static void -evhttp_make_header_request(struct evbuffer *buf, struct evhttp_request *req) +evhttp_make_header_request(struct evhttp_connection *evcon, + struct evhttp_request *req) { static char line[1024]; const char *method; @@ -219,14 +201,14 @@ evhttp_make_header_request(struct evbuffer *buf, struct evhttp_request *req) method = evhttp_method(req->type); snprintf(line, sizeof(line), "%s %s HTTP/%d.%d\r\n", method, req->uri, req->major, req->minor); - evbuffer_add(buf, line, strlen(line)); + evbuffer_add(evcon->output_buffer, line, strlen(line)); /* Add the content length on a post request if missing */ if (req->type == EVHTTP_REQ_POST && evhttp_find_header(req->output_headers, "Content-Length") == NULL){ char size[12]; snprintf(size, sizeof(size), "%ld", - EVBUFFER_LENGTH(req->buffer)); + EVBUFFER_LENGTH(req->output_buffer)); evhttp_add_header(req->output_headers, "Content-Length", size); } } @@ -235,13 +217,14 @@ evhttp_make_header_request(struct evbuffer *buf, struct evhttp_request *req) * Create the headers needed for an HTTP reply */ static void -evhttp_make_header_response(struct evbuffer *buf, struct evhttp_request *req) +evhttp_make_header_response(struct evhttp_connection *evcon, + struct evhttp_request *req) { static char line[1024]; snprintf(line, sizeof(line), "HTTP/%d.%d %d %s\r\n", req->major, req->minor, req->response_code, req->response_code_line); - evbuffer_add(buf, line, strlen(line)); + evbuffer_add(evcon->output_buffer, line, strlen(line)); /* Potentially add headers */ if (evhttp_find_header(req->output_headers, "Content-Type") == NULL) { @@ -251,7 +234,7 @@ evhttp_make_header_response(struct evbuffer *buf, struct evhttp_request *req) } void -evhttp_make_header(struct evbuffer *buf, struct evhttp_request *req) +evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) { static char line[1024]; struct evkeyval *header; @@ -261,24 +244,24 @@ evhttp_make_header(struct evbuffer *buf, struct evhttp_request *req) * add some new headers or remove existing headers. */ if (req->kind == EVHTTP_REQUEST) { - evhttp_make_header_request(buf, req); + evhttp_make_header_request(evcon, req); } else { - evhttp_make_header_response(buf, req); + evhttp_make_header_response(evcon, req); } TAILQ_FOREACH(header, req->output_headers, next) { snprintf(line, sizeof(line), "%s: %s\r\n", header->key, header->value); - evbuffer_add(buf, line, strlen(line)); + evbuffer_add(evcon->output_buffer, line, strlen(line)); } - evbuffer_add(buf, "\r\n", 2); - - if (req->kind == EVHTTP_REQUEST) { - int len = EVBUFFER_LENGTH(req->buffer); - - /* Add the POST data */ - if (len > 0) - evbuffer_add(buf, EVBUFFER_DATA(req->buffer), len); + evbuffer_add(evcon->output_buffer, "\r\n", 2); + + if (EVBUFFER_LENGTH(req->output_buffer) >= 0) { + /* + * For a request, we add the POST data, for a reply, this + * is the regular data. + */ + evbuffer_add_buffer(evcon->output_buffer, req->output_buffer); } } @@ -338,45 +321,98 @@ evhttp_hostportfile(char *url, char **phost, u_short *pport, char **pfile) } void -evhttp_fail(struct evhttp_request *req) +evhttp_connection_fail(struct evhttp_connection *evcon) { + struct evhttp_request* req = TAILQ_FIRST(&evcon->requests); + assert(req != NULL); + + /* reset the connection */ + evhttp_connection_reset(evcon); + + if (req->cb != NULL) { + /* xxx: maybe we need to pass the request here? */ + (*req->cb)(NULL, req->cb_arg); + } + + TAILQ_REMOVE(&evcon->requests, req, next); evhttp_request_free(req); + + /* xxx: maybe we should fail all requests??? */ + + /* We are trying the next request that was queued on us */ + if (TAILQ_FIRST(&evcon->requests) != NULL) + evhttp_connection_connect(evcon); } void evhttp_write(int fd, short what, void *arg) { - struct evhttp_request *req = arg; + struct evhttp_connection *evcon = arg; struct timeval tv; int n; if (what == EV_TIMEOUT) { - evhttp_fail(req); + evhttp_connection_fail(evcon); return; } - n = evbuffer_write(req->buffer, fd); + n = evbuffer_write(evcon->output_buffer, fd); if (n == -1) { event_warn("%s: evbuffer_write", __func__); - evhttp_fail(req); + evhttp_connection_fail(evcon); return; } if (n == 0) { event_warnx("%s: write nothing\n", __func__); - evhttp_fail(req); + evhttp_connection_fail(evcon); return; } - if (EVBUFFER_LENGTH(req->buffer) != 0) { + if (EVBUFFER_LENGTH(evcon->output_buffer) != 0) { timerclear(&tv); tv.tv_sec = HTTP_WRITE_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); return; } /* Activate our call back */ + (*evcon->cb)(evcon, evcon->cb_arg); +} + +void +evhttp_connection_done(struct evhttp_connection *evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + + /* + * if this is an incoming connection, we need to leave the request + * on the connection, so that we can reply to it. + */ + if (evcon->flags & EVHTTP_CON_OUTGOING) { + TAILQ_REMOVE(&evcon->requests, req, next); + req->evcon = NULL; + + if (TAILQ_FIRST(&evcon->requests) != NULL) { + /* + * We have more requests; reset the connection + * and deal with the next request. xxx: no + * persistent connection right now + */ + evhttp_connection_connect(evcon); + } + } + + /* hand what ever we read over to the request */ + evbuffer_add_buffer(req->input_buffer, evcon->input_buffer); + + /* notify the user of the request */ (*req->cb)(req, req->cb_arg); + + /* if this was an outgoing request, we own and it's done. so free it */ + if (evcon->flags & EVHTTP_CON_OUTGOING) { + evhttp_request_free(req); + } } /* @@ -389,21 +425,23 @@ evhttp_write(int fd, short what, void *arg) void evhttp_read(int fd, short what, void *arg) { - struct evhttp_request *req = arg; + struct evhttp_connection *evcon = arg; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); struct timeval tv; int n; if (what == EV_TIMEOUT) { - evhttp_fail(req); + evhttp_connection_fail(evcon); return; } - n = evbuffer_read(req->buffer, fd, req->ntoread); + n = evbuffer_read(req->input_buffer, fd, req->ntoread); event_debug(("%s: got %d on %d\n", __func__, n, req->fd)); if (n == -1) { event_warn("%s: evbuffer_read", __func__); - evhttp_fail(req); + evhttp_connection_fail(evcon); + return; } /* Adjust the amount of data that we have left to read */ @@ -411,28 +449,26 @@ evhttp_read(int fd, short what, void *arg) req->ntoread -= n; if (n == 0 || req->ntoread == 0) { - (*req->cb)(req, req->cb_arg); + evhttp_connection_done(evcon); return; } - + timerclear(&tv); tv.tv_sec = HTTP_READ_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); } void -evhttp_write_requestcb(struct evhttp_request *req, void *arg) +evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) { - /* Restore the original callbacks */ - req->cb = req->save_cb; - req->cb_arg = req->save_cbarg; - /* This is after writing the request to the server */ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + assert(req != NULL); /* We are done writing our header and are now expecting the response */ req->kind = EVHTTP_RESPONSE; - evhttp_start_read(req); + evhttp_start_read(evcon); } /* @@ -442,7 +478,8 @@ evhttp_write_requestcb(struct evhttp_request *req, void *arg) void evhttp_connection_free(struct evhttp_connection *evcon) { - event_del(&evcon->ev); + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); if (evcon->fd != -1) close(evcon->fd); @@ -450,9 +487,47 @@ evhttp_connection_free(struct evhttp_connection *evcon) if (evcon->address != NULL) free(evcon->address); + if (evcon->input_buffer != NULL) + evbuffer_free(evcon->input_buffer); + + if (evcon->output_buffer != NULL) + evbuffer_free(evcon->output_buffer); + free(evcon); } +void +evhttp_request_dispatch(struct evhttp_connection* evcon) +{ + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + + /* this should not usually happy but it's possible */ + if (req == NULL) + return; + + /* we assume that the connection is connected already */ + assert(evcon->state = EVCON_CONNECTED); + + /* Create the header from the store arguments */ + evhttp_make_header(evcon, req); + + evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); +} + +/* Reset our connection state */ +void +evhttp_connection_reset(struct evhttp_connection *evcon) +{ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) { + close(evcon->fd); + evcon->fd = -1; + } + evcon->state = EVCON_DISCONNECTED; +} + /* * Call back for asynchronous connection attempt. */ @@ -489,16 +564,24 @@ evhttp_connectioncb(int fd, short what, void *arg) event_debug(("%s: connected to \"%s:%d\" on %d\n", __func__, evcon->address, evcon->port, evcon->fd)); - /* We are turning the connection object over to the user */ - (*evcon->cb)(evcon, evcon->cb_arg); + evcon->state = EVCON_CONNECTED; + + /* try to start requests that have queued up on this connection */ + evhttp_request_dispatch(evcon); return; cleanup: - /* Signal error to the user */ - (*evcon->cb)(NULL, evcon->cb_arg); + evhttp_connection_reset(evcon); - evhttp_connection_free(evcon); - return; + /* for now, we just signal all requests by executing their callbacks */ + while (TAILQ_FIRST(&evcon->requests) != NULL) { + struct evhttp_request *request = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, request, next); + request->evcon = NULL; + + /* we might want to set an error here */ + request->cb(request, request->cb_arg); + } } /* @@ -538,15 +621,15 @@ evhttp_parse_response_line(struct evhttp_request *req, char *line) req->major = 1; req->minor = 1; } else { - event_warnx("%s: bad protocol \"%s\" on %d\n", - __func__, protocol, req->fd); + event_warnx("%s: bad protocol \"%s\"\n", + __func__, protocol); return (-1); } req->response_code = atoi(number); if (!evhttp_valid_response_code(req->response_code)) { - event_warnx("%s: bad response code \"%s\" on %d\n", - __func__, number, req->fd); + event_warnx("%s: bad response code \"%s\"\n", + __func__, number); return (-1); } @@ -584,8 +667,8 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line) } else if (strcmp(method, "HEAD") == 0) { req->type = EVHTTP_REQ_HEAD; } else { - event_warnx("%s: bad method %s on fd %d\n", - __func__, method, req->fd); + event_warnx("%s: bad method %s on request %p\n", + __func__, method, req); return (-1); } @@ -596,8 +679,8 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line) req->major = 1; req->minor = 1; } else { - event_warnx("%s: bad version %s on %d\n", - __func__, version, req->fd); + event_warnx("%s: bad version %s on request %p\n", + __func__, version, req); return (-1); } @@ -759,7 +842,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) } void -evhttp_get_body(struct evhttp_request *req) +evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) { struct timeval tv; const char *content_length; @@ -768,7 +851,7 @@ evhttp_get_body(struct evhttp_request *req) /* If this is a request without a body, then we are done */ if (req->kind == EVHTTP_REQUEST && req->type != EVHTTP_REQ_POST) { - (*req->cb)(req, req->cb_arg); + evhttp_connection_done(evcon); return; } @@ -783,7 +866,7 @@ evhttp_get_body(struct evhttp_request *req) event_warnx("%s: we got no content length, but the server" " wants to keep the connection open: %s.\n", __func__, connection); - evhttp_fail(req); + evhttp_connection_fail(evcon); return; } else if (content_length == NULL) req->ntoread = -1; @@ -791,58 +874,60 @@ evhttp_get_body(struct evhttp_request *req) req->ntoread = atoi(content_length); event_debug(("%s: bytes to read: %d (in buffer %d)\n", - __func__, req->ntoread, EVBUFFER_LENGTH(req->buffer))); + __func__, req->ntoread, EVBUFFER_LENGTH(evcon->buffer))); if (req->ntoread > 0) - req->ntoread -= EVBUFFER_LENGTH(req->buffer); + req->ntoread -= EVBUFFER_LENGTH(evcon->input_buffer); if (req->ntoread == 0) { - (*req->cb)(req, req->cb_arg); + evhttp_connection_done(evcon); return; } - event_set(&req->ev, req->fd, EV_READ, evhttp_read, req); + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read, evcon); timerclear(&tv); tv.tv_sec = HTTP_READ_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); + return; } void evhttp_read_header(int fd, short what, void *arg) { struct timeval tv; - struct evhttp_request *req = arg; + struct evhttp_connection *evcon = arg; + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); int n, res; if (what == EV_TIMEOUT) { event_warnx("%s: timeout on %d\n", __func__, fd); - evhttp_request_free(req); + evhttp_connection_fail(evcon); return; } - n = evbuffer_read(req->buffer, fd, -1); + n = evbuffer_read(evcon->input_buffer, fd, -1); if (n == 0) { event_warnx("%s: no more data on %d\n", __func__, fd); - evhttp_request_free(req); + evhttp_connection_fail(evcon); return; } if (n == -1) { event_warnx("%s: bad read on %d\n", __func__, fd); - evhttp_request_free(req); + evhttp_connection_fail(evcon); return; } - res = evhttp_parse_lines(req, req->buffer); + res = evhttp_parse_lines(req, evcon->input_buffer); if (res == -1) { /* Error while reading, terminate */ event_warnx("%s: bad header lines on %d\n", __func__, fd); - evhttp_request_free(req); + evhttp_connection_fail(evcon); return; } else if (res == 0) { /* Need more header lines */ timerclear(&tv); tv.tv_sec = HTTP_READ_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); return; } @@ -850,19 +935,19 @@ evhttp_read_header(int fd, short what, void *arg) switch (req->kind) { case EVHTTP_REQUEST: event_debug(("%s: checking for post data on %d\n", - __func__, req->fd)); - evhttp_get_body(req); + __func__, fd)); + evhttp_get_body(evcon, req); break; case EVHTTP_RESPONSE: event_debug(("%s: starting to read body for \"%s\" on %d\n", - __func__, req->remote_host, req->fd)); - evhttp_get_body(req); + __func__, req->remote_host, fd)); + evhttp_get_body(evcon, req); break; default: event_warnx("%s: bad header on %d\n", __func__, fd); - evhttp_fail(req); + evhttp_connection_fail(evcon); break; } } @@ -878,11 +963,9 @@ evhttp_read_header(int fd, short what, void *arg) */ struct evhttp_connection * -evhttp_connect(const char *address, unsigned short port, - void (*cb)(struct evhttp_connection *, void *), void *cb_arg) +evhttp_connection_new(const char *address, unsigned short port) { struct evhttp_connection *evcon = NULL; - struct timeval tv; event_debug(("Attempting connection to %s:%d\n", address, port)); @@ -893,20 +976,52 @@ evhttp_connect(const char *address, unsigned short port, evcon->fd = -1; evcon->port = port; + if ((evcon->address = strdup(address)) == NULL) { event_warn("%s: strdup failed", __func__); goto error; } + + if ((evcon->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + if ((evcon->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } - /* Let the user name when something interesting happened */ - evcon->cb = cb; - evcon->cb_arg = cb_arg; + evcon->state = EVCON_DISCONNECTED; + TAILQ_INIT(&evcon->requests); + + return (evcon); + + error: + if (evcon != NULL) + evhttp_connection_free(evcon); + return (NULL); +} + +int +evhttp_connection_connect(struct evhttp_connection *evcon) +{ + struct timeval tv; + + if (evcon->state == EVCON_CONNECTING) + return (0); + + evhttp_connection_reset(evcon); + + assert(!(evcon->flags & EVHTTP_CON_INCOMING)); + evcon->flags |= EVHTTP_CON_OUTGOING; /* Do async connection to HTTP server */ - if ((evcon->fd = make_socket(connect, address, port)) == -1) { + if ((evcon->fd = make_socket( + connect, evcon->address, evcon->port)) == -1) { event_warn("%s: failed to connect to \"%s:%d\"", - __func__, address, port); - goto error; + __func__, evcon->address, evcon->port); + return (-1); } /* Set up a callback for successful connection setup */ @@ -915,19 +1030,15 @@ evhttp_connect(const char *address, unsigned short port, tv.tv_sec = HTTP_CONNECT_TIMEOUT; event_add(&evcon->ev, &tv); - return (evcon); - - error: - if (evcon != NULL) - evhttp_connection_free(evcon); - return (NULL); + evcon->state = EVCON_CONNECTING; + + return (0); } /* * Starts an HTTP request on the provided evhttp_connection object. - * - * In theory we might use this to queue requests on the connection - * object. + * If the connection object is not connected to the web server already, + * this will start the connection. */ int @@ -935,19 +1046,13 @@ evhttp_make_request(struct evhttp_connection *evcon, struct evhttp_request *req, enum evhttp_cmd_type type, const char *uri) { - struct evbuffer *evbuf = evbuffer_new(); - - if (evbuf == NULL) - return (-1); - /* We are making a request */ - req->fd = evcon->fd; req->kind = EVHTTP_REQUEST; req->type = type; if (req->uri != NULL) free(req->uri); if ((req->uri = strdup(uri)) == NULL) - goto error; + event_err(1, "%s: strdup", __func__); /* Set the protocol version if it is not supplied */ if (!req->major && !req->minor) { @@ -955,20 +1060,25 @@ evhttp_make_request(struct evhttp_connection *evcon, req->minor = 1; } - /* Create the header from the store arguments */ - evhttp_make_header(evbuf, req); + assert(req->evcon == NULL); + req->evcon = evcon; + assert(!(req->flags && EVHTTP_REQ_OWN_CONNECTION)); + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); - /* Schedule the write */ - req->save_cb = req->cb; - req->save_cbarg = req->cb_arg; + /* If the connection object is not connected; make it so */ + if (evcon->state != EVCON_CONNECTED) + return (evhttp_connection_connect(evcon)); - /* evbuf is being freed when the request finishes */ - evhttp_write_buffer(req, evbuf, evhttp_write_requestcb, NULL); - return (0); + /* + * If it's connected already and we are the first in the queue, + * then we can dispatch this request immediately. Otherwise, it + * will be dispatched once the pending requests are completed. + */ + if (TAILQ_FIRST(&evcon->requests) == req) + evhttp_request_dispatch(evcon); - error: - evbuffer_free(evbuf); - return (-1); + return (0); } /* @@ -977,21 +1087,29 @@ evhttp_make_request(struct evhttp_connection *evcon, */ void -evhttp_start_read(struct evhttp_request *req) +evhttp_start_read(struct evhttp_connection *evcon) { struct timeval tv; /* Set up an event to read the headers */ - event_set(&req->ev, req->fd, EV_READ, evhttp_read_header, req); + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + event_set(&evcon->ev, evcon->fd, EV_READ, evhttp_read_header, evcon); timerclear(&tv); tv.tv_sec = HTTP_READ_TIMEOUT; - event_add(&req->ev, &tv); + event_add(&evcon->ev, &tv); } void -evhttp_send_done(struct evhttp_request *req, void *arg) +evhttp_send_done(struct evhttp_connection *evcon, void *arg) { + struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, req, next); + + if (req->flags & EVHTTP_REQ_OWN_CONNECTION) + evhttp_connection_free(evcon); + evhttp_request_free(req); } @@ -1025,16 +1143,17 @@ evhttp_send_error(struct evhttp_request *req, int error, const char *reason) static __inline void evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) { - struct evbuffer *buf = req->buffer; + struct evhttp_connection *evcon = req->evcon; - evbuffer_drain(buf, -1); + assert(TAILQ_FIRST(&evcon->requests) == req); - /* Adds headers to the response */ - evhttp_make_header(buf, req); + /* xxx: not sure if we really should expost the data buffer this way */ + evbuffer_add_buffer(req->output_buffer, databuf); - evbuffer_add_buffer(buf, databuf); + /* Adds headers to the response */ + evhttp_make_header(evcon, req); - evhttp_write_buffer(req, buf, evhttp_send_done, NULL); + evhttp_write_buffer(evcon, evhttp_send_done, NULL); } void @@ -1285,7 +1404,6 @@ evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) goto error; } - req->fd = -1; req->kind = EVHTTP_RESPONSE; req->input_headers = calloc(1, sizeof(struct evkeyvalq)); if (req->input_headers == NULL) { @@ -1301,7 +1419,15 @@ evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) } TAILQ_INIT(req->output_headers); - req->buffer = evbuffer_new(); + if ((req->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + if ((req->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } req->cb = cb; req->cb_arg = arg; @@ -1317,8 +1443,6 @@ evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) void evhttp_request_free(struct evhttp_request *req) { - if (req->fd != -1) - close(req->fd); if (req->remote_host != NULL) free(req->remote_host); if (req->uri != NULL) @@ -1326,17 +1450,18 @@ evhttp_request_free(struct evhttp_request *req) if (req->response_code_line != NULL) free(req->response_code_line); - if (event_initialized(&req->ev)) - event_del(&req->ev); - evhttp_clear_headers(req->input_headers); free(req->input_headers); evhttp_clear_headers(req->output_headers); free(req->output_headers); - if (req->buffer != NULL) - evbuffer_free(req->buffer); + if (req->input_buffer != NULL) + evbuffer_free(req->input_buffer); + + if (req->output_buffer != NULL) + evbuffer_free(req->output_buffer); + free(req); } @@ -1360,6 +1485,7 @@ void evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, void (*cb)(struct evhttp_request *, void *), void *arg) { + struct evhttp_connection *evcon; struct evhttp_request *req; char *hostname, *portname; @@ -1367,17 +1493,31 @@ evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, event_debug(("%s: new request from %s:%s on %d\n", __func__, hostname, portname, fd)); - if ((req = evhttp_request_new(cb, arg)) == NULL) + /* we need a connection object to put the http request on */ + if ((evcon = evhttp_connection_new(hostname, atoi(portname))) == NULL) + return; + evcon->flags |= EVHTTP_CON_INCOMING; + evcon->state = EVCON_CONNECTED; + + if ((req = evhttp_request_new(cb, arg)) == NULL) { + evhttp_connection_free(evcon); return; + } + + evcon->fd = fd; - req->fd = fd; + req->evcon = evcon; /* the request ends up owning the connection */ + req->flags |= EVHTTP_REQ_OWN_CONNECTION; + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + req->kind = EVHTTP_REQUEST; if ((req->remote_host = strdup(hostname)) == NULL) event_err(1, "%s: strdup", __func__); req->remote_port = atoi(portname); - evhttp_start_read(req); + evhttp_start_read(evcon); } diff --git a/test/Makefile.am b/test/Makefile.am index 157d261e..7be0e054 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -18,7 +18,7 @@ regress_SOURCES = regress.c regress.h regress_http.c \ bench_SOURCES = bench.c regress.gen.c regress.gen.h: regress.rpc - ../event_rpcgen.py regress.rpc + ../event_rpcgen.py regress.rpc || echo "No Python installed" DISTCLEANFILES = *~ CLEANFILES = regress.gen.h regress.gen.c diff --git a/test/regress_http.c b/test/regress_http.c index 3b4001f2..58f919eb 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -214,50 +214,26 @@ http_basic_test(void) fprintf(stdout, "OK\n"); } -void http_connectcb(struct evhttp_connection *evcon, void *arg); +void http_request_done(struct evhttp_request *, void *); void http_connection_test(void) { short port = -1; struct evhttp_connection *evcon = NULL; - + struct evhttp_request *req = NULL; + test_ok = 0; fprintf(stdout, "Testing Basic HTTP Connection: "); http = http_setup(&port); - evcon = evhttp_connect("127.0.0.1", port, http_connectcb, NULL); + evcon = evhttp_connection_new("127.0.0.1", port); if (evcon == NULL) { fprintf(stdout, "FAILED\n"); exit(1); } - event_dispatch(); - - evhttp_connection_free(evcon); - evhttp_free(http); - - if (test_ok != 1) { - fprintf(stdout, "FAILED\n"); - exit(1); - } - - fprintf(stdout, "OK\n"); -} - -void http_request_done(struct evhttp_request *, void *); - -void -http_connectcb(struct evhttp_connection *evcon, void *arg) -{ - struct evhttp_request *req = NULL; - - if (evcon == NULL) { - fprintf(stdout, "FAILED\n"); - exit (1); - } - /* * At this point, we want to schedule a request to the HTTP * server using our make request method. @@ -267,11 +243,24 @@ http_connectcb(struct evhttp_connection *evcon, void *arg) /* Add the information that we care about */ evhttp_add_header(req->output_headers, "Host", "somehost"); - + + /* We give ownership of the request to the connection */ if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { fprintf(stdout, "FAILED\n"); exit(1); } + + event_dispatch(); + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); } void @@ -290,12 +279,12 @@ http_request_done(struct evhttp_request *req, void *arg) exit(1); } - if (EVBUFFER_LENGTH(req->buffer) != strlen(what)) { + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) { fprintf(stderr, "FAILED\n"); exit(1); } - if (memcmp(EVBUFFER_DATA(req->buffer), what, strlen(what)) != 0) { + if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) { fprintf(stderr, "FAILED\n"); exit(1); } @@ -308,67 +297,59 @@ http_request_done(struct evhttp_request *req, void *arg) * HTTP POST test. */ -void http_connect_forpostcb(struct evhttp_connection *evcon, void *arg); +void http_postrequest_done(struct evhttp_request *, void *); + +#define POST_DATA "Okay. Not really printf" void http_post_test(void) { short port = -1; struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; test_ok = 0; fprintf(stdout, "Testing HTTP POST Request: "); http = http_setup(&port); - evcon = evhttp_connect("127.0.0.1", port, http_connect_forpostcb, NULL); + evcon = evhttp_connection_new("127.0.0.1", port); if (evcon == NULL) { fprintf(stdout, "FAILED\n"); exit(1); } - event_dispatch(); - - evhttp_connection_free(evcon); - evhttp_free(http); - - if (test_ok != 1) { - fprintf(stdout, "FAILED\n"); - exit(1); - } - - fprintf(stdout, "OK\n"); -} - -void http_postrequest_done(struct evhttp_request *, void *); - -#define POST_DATA "Okay. Not really printf" - -void -http_connect_forpostcb(struct evhttp_connection *evcon, void *arg) -{ - struct evhttp_request *req = NULL; - - if (evcon == NULL) { - fprintf(stdout, "FAILED\n"); - exit (1); - } - /* * At this point, we want to schedule an HTTP POST request * server using our make request method. */ req = evhttp_request_new(http_postrequest_done, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } /* Add the information that we care about */ evhttp_add_header(req->output_headers, "Host", "somehost"); - evbuffer_add_printf(req->buffer, POST_DATA); + evbuffer_add_printf(req->output_buffer, POST_DATA); if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { fprintf(stdout, "FAILED\n"); exit(1); } + + event_dispatch(); + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); } void @@ -382,12 +363,12 @@ http_post_cb(struct evhttp_request *req, void *arg) exit(1); } - if (EVBUFFER_LENGTH(req->buffer) != strlen(POST_DATA)) { + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(POST_DATA)) { fprintf(stdout, "FAILED\n"); exit(1); } - if (strcmp(EVBUFFER_DATA(req->buffer), POST_DATA)) { + if (strcmp(EVBUFFER_DATA(req->input_buffer), POST_DATA)) { fprintf(stdout, "FAILED\n"); exit(1); } @@ -416,12 +397,12 @@ http_postrequest_done(struct evhttp_request *req, void *arg) exit(1); } - if (EVBUFFER_LENGTH(req->buffer) != strlen(what)) { + if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) { fprintf(stderr, "FAILED\n"); exit(1); } - if (memcmp(EVBUFFER_DATA(req->buffer), what, strlen(what)) != 0) { + if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) { fprintf(stderr, "FAILED\n"); exit(1); } -- 2.40.0