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) {
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);
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.
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 */
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)
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;
event_loopexit(NULL);
}
-void
+static void
http_basic_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = evbuffer_new();
evbuffer_free(evb);
}
-void
+static void
http_chunked_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = evbuffer_new();
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();
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)
{
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();