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
This commit is contained in:
Niels Provos 2006-07-17 00:33:57 +00:00
parent 00bc7e37fd
commit ba7262ebdf
5 changed files with 415 additions and 259 deletions

View File

@ -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 */

View File

@ -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;
};
/* resets the connection; can be reused for more requests */
void evhttp_connection_reset(struct evhttp_connection *);
/* connects if necessary */
int evhttp_connection_connect(struct evhttp_connection *);
/* notifies the current request that it failed; resets connection */
void evhttp_connection_fail(struct evhttp_connection *);
void evhttp_get_request(int, struct sockaddr *, socklen_t,
void (*)(struct evhttp_request *, void *), void *);
/*
* 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);
/* Frees an http connection */
void evhttp_connection_free(struct evhttp_connection *evcon);
int evhttp_make_request(struct evhttp_connection *evcon,
struct evhttp_request *req,
enum evhttp_cmd_type type, const char *uri);
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 */

456
http.c
View File

@ -48,6 +48,7 @@
#include <netinet/in.h>
#include <netdb.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
@ -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);
evbuffer_add(evcon->output_buffer, "\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);
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));
/*
* 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);
/* evbuf is being freed when the request finishes */
evhttp_write_buffer(req, evbuf, evhttp_write_requestcb, NULL);
return (0);
error:
evbuffer_free(evbuf);
return (-1);
}
/*
@ -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);
/* xxx: not sure if we really should expost the data buffer this way */
evbuffer_add_buffer(req->output_buffer, databuf);
/* Adds headers to the response */
evhttp_make_header(buf, req);
evhttp_make_header(evcon, req);
evbuffer_add_buffer(buf, databuf);
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;
}
req->fd = fd;
evcon->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);
}

View File

@ -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

View File

@ -214,25 +214,42 @@ 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);
}
/*
* At this point, we want to schedule a request to the HTTP
* server using our make request method.
*/
req = evhttp_request_new(http_request_done, NULL);
/* 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);
@ -246,34 +263,6 @@ http_connection_test(void)
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.
*/
req = evhttp_request_new(http_request_done, NULL);
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost");
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
}
void
http_request_done(struct evhttp_request *req, void *arg)
{
@ -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,25 +297,48 @@ 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);
}
/*
* 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->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);
@ -340,37 +352,6 @@ http_post_test(void)
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);
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Host", "somehost");
evbuffer_add_printf(req->buffer, POST_DATA);
if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
}
void
http_post_cb(struct evhttp_request *req, void *arg)
{
@ -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);
}