allow cancelation of user initiated http requests; this will allow cancelation of rpc requests eventually

svn:r812
This commit is contained in:
Niels Provos 2008-05-12 03:12:09 +00:00
parent 03fafae006
commit 1080852e91
5 changed files with 218 additions and 16 deletions

View File

@ -97,7 +97,8 @@ Changes in current version:
o deprecate timeout_* event functions by moving them to event_compat.h
o Move windows gettimeofday replacement into a new evutil_gettimeofday().
o Make configure script work on IRIX.
o provide a method for canceling ongoing http requests.
Changes in 1.4.0:
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.

View File

@ -23,7 +23,8 @@ enum evhttp_connection_error {
EVCON_HTTP_TIMEOUT,
EVCON_HTTP_EOF,
EVCON_HTTP_INVALID_HEADER,
EVCON_HTTP_BUFFER_ERROR
EVCON_HTTP_BUFFER_ERROR,
EVCON_HTTP_REQUEST_CANCEL
};
struct evbuffer;

44
http.c
View File

@ -483,6 +483,7 @@ evhttp_connection_incoming_fail(struct evhttp_request *req,
return (-1);
case EVCON_HTTP_INVALID_HEADER:
case EVCON_HTTP_BUFFER_ERROR:
case EVCON_HTTP_REQUEST_CANCEL:
default: /* xxx: probably should just error on default */
/* the callback looks at the uri to determine errors */
if (req->uri) {
@ -525,14 +526,23 @@ evhttp_connection_fail(struct evhttp_connection *evcon,
return;
}
/* save the callback for later; the cb might free our object */
cb = req->cb;
cb_arg = req->cb_arg;
/* when the request was canceled, the callback is not executed */
if (error != EVCON_HTTP_REQUEST_CANCEL) {
/* save the callback for later; the cb might free our object */
cb = req->cb;
cb_arg = req->cb_arg;
} else {
cb = NULL;
cb_arg = NULL;
}
TAILQ_REMOVE(&evcon->requests, req, next);
evhttp_request_free(req);
/* xxx: maybe we should fail all requests??? */
/* do not fail all requests; the next request is going to get
* send over a new connection. when a user cancels a request,
* all other pending requests should be processed as normal
*/
/* reset the connection */
evhttp_connection_reset(evcon);
@ -1574,6 +1584,32 @@ evhttp_make_request(struct evhttp_connection *evcon,
return (0);
}
void
evhttp_cancel_request(struct evhttp_request *req)
{
struct evhttp_connection *evcon = req->evcon;
if (evcon != NULL) {
/* We need to remove it from the connection */
if (TAILQ_FIRST(&evcon->requests) == req) {
/* it's currently being worked on, so reset
* the connection.
*/
evhttp_connection_fail(evcon,
EVCON_HTTP_REQUEST_CANCEL);
/* connection fail freed the request */
return;
} else {
/* otherwise, we can just remove it from the
* queue
*/
TAILQ_REMOVE(&evcon->requests, req, next);
}
}
evhttp_request_free(req);
}
/*
* Reads data from file descriptor into request structure
* Request structure needs to be set up correctly.

View File

@ -274,11 +274,37 @@ void evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void evhttp_connection_get_peer(struct evhttp_connection *evcon,
char **address, ev_uint16_t *port);
/** The connection gets ownership of the request */
/**
Make an HTTP request over the specified connection.
The connection gets ownership of the request.
@param evcon the evhttp_connection object over which to send the request
@param req the previously created and configured request object
@param type the request type EVHTTP_REQ_GET, EVHTTP_REQ_POST, etc.
@param uri the URI associated with the request
@return 0 on success, -1 on failure
@see evhttp_cancel_request()
*/
int evhttp_make_request(struct evhttp_connection *evcon,
struct evhttp_request *req,
enum evhttp_cmd_type type, const char *uri);
/**
Cancels a pending HTTP request.
Cancels an ongoing HTTP request. The callback associated with this request
is not executed and the request object is freed. If the request is
currently being processed, e.g. it is ongoing, the corresponding
evhttp_connection object is going to get reset.
A request cannot be canceled if its callback has executed already.
@param req the evhttp_request to cancel; req becomes invalid after this call.
*/
void evhttp_cancel_request(struct evhttp_request *req);
/** Returns the request URI */
const char *evhttp_request_get_uri(struct evhttp_request *req);
/** Returns the input headers */

View File

@ -66,12 +66,13 @@ static struct event_base *base;
void http_suite(void);
void http_basic_cb(struct evhttp_request *req, void *arg);
void http_chunked_cb(struct evhttp_request *req, void *arg);
void http_post_cb(struct evhttp_request *req, void *arg);
void http_put_cb(struct evhttp_request *req, void *arg);
void http_delete_cb(struct evhttp_request *req, void *arg);
void http_dispatcher_cb(struct evhttp_request *req, void *arg);
static void http_basic_cb(struct evhttp_request *req, void *arg);
static void http_chunked_cb(struct evhttp_request *req, void *arg);
static void http_post_cb(struct evhttp_request *req, void *arg);
static void http_put_cb(struct evhttp_request *req, void *arg);
static void http_delete_cb(struct evhttp_request *req, void *arg);
static void http_delay_cb(struct evhttp_request *req, void *arg);
static void http_dispatcher_cb(struct evhttp_request *req, void *arg);
static struct evhttp *
http_setup(short *pport, struct event_base *base)
@ -98,6 +99,7 @@ http_setup(short *pport, struct event_base *base)
evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL);
evhttp_set_cb(myhttp, "/putit", http_put_cb, NULL);
evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, NULL);
evhttp_set_cb(myhttp, "/delay", http_delay_cb, NULL);
evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL);
*pport = port;
@ -207,7 +209,7 @@ http_errorcb(struct bufferevent *bev, short what, void *arg)
event_loopexit(NULL);
}
void
static void
http_basic_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = evbuffer_new();
@ -222,7 +224,7 @@ http_basic_cb(struct evhttp_request *req, void *arg)
evbuffer_free(evb);
}
void
static void
http_chunked_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = evbuffer_new();
@ -322,11 +324,32 @@ http_basic_test(void)
fprintf(stdout, "OK\n");
}
static void
http_delay_reply(evutil_socket_t fd, short what, void *arg)
{
struct evhttp_request *req = arg;
evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL);
++test_ok;
}
static void
http_delay_cb(struct evhttp_request *req, void *arg)
{
struct timeval tv;
timerclear(&tv);
tv.tv_sec = 0;
tv.tv_usec = 200 * 1000;
event_once(-1, EV_TIMEOUT, http_delay_reply, req, &tv);
}
/*
* HTTP DELETE test, just piggyback on the basic test
*/
void
static void
http_delete_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = evbuffer_new();
@ -485,6 +508,120 @@ http_connection_test(int persistent)
fprintf(stdout, "OK\n");
}
static void
http_request_never_call(struct evhttp_request *req, void *arg)
{
fprintf(stdout, "FAILED\n");
exit(1);
}
static void
http_do_cancel(evutil_socket_t fd, short what, void *arg)
{
struct evhttp_request *req = arg;
struct timeval tv;
timerclear(&tv);
tv.tv_sec = 0;
tv.tv_usec = 500 * 1000;
evhttp_cancel_request(req);
event_loopexit(&tv);
++test_ok;
}
static void
http_cancel_test(void)
{
short port = -1;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req = NULL;
struct timeval tv;
test_ok = 0;
fprintf(stdout, "Testing Request Cancelation: ");
http = http_setup(&port, 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_never_call, 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, "/delay") == -1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
timerclear(&tv);
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000;
event_once(-1, EV_TIMEOUT, http_do_cancel, req, &tv);
event_dispatch();
if (test_ok != 2) {
fprintf(stdout, "FAILED\n");
exit(1);
}
/* try to make another request over the same connection */
test_ok = 0;
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();
/* make another request: request empty reply */
test_ok = 0;
req = evhttp_request_new(http_request_empty_done, NULL);
/* Add the information that we care about */
evhttp_add_header(req->output_headers, "Empty", "itis");
/* 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();
if (test_ok != 1) {
fprintf(stdout, "FAILED\n");
exit(1);
}
evhttp_connection_free(evcon);
evhttp_free(http);
fprintf(stdout, "OK\n");
}
static void
http_request_done(struct evhttp_request *req, void *arg)
{
@ -1811,6 +1948,7 @@ http_suite(void)
http_base_test();
http_bad_header_test();
http_basic_test();
http_cancel_test();
http_connection_test(0 /* not-persistent */);
http_connection_test(1 /* persistent */);
http_virtual_host_test();