struct event_base *base;
struct evdns_base *dns_base;
int ai_family;
+
+ evhttp_ext_method_cb ext_method_cmp;
};
/* A callback for an http server */
void *newreqcbarg;
struct event_base *base;
+
+ evhttp_ext_method_cb ext_method_cmp;
};
/* XXX most of these functions could be static. */
static void evhttp_write_buffer(struct evhttp_connection *,
void (*)(struct evhttp_connection *, void *), void *);
static void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *);
+static int evhttp_method_may_have_body_(struct evhttp_connection *, enum evhttp_cmd_type);
/* callbacks for bufferevent */
static void evhttp_read_cb(struct bufferevent *, void *);
static void evhttp_write_cb(struct bufferevent *, void *);
static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg);
-static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp,
- const char *hostname);
+static int evhttp_find_vhost(struct evhttp *http, struct evhttp **outhttp, const char *hostname);
+static const char *evhttp_method_(struct evhttp_connection *evcon,
+ enum evhttp_cmd_type type, ev_uint16_t *flags);
#ifndef EVENT__HAVE_STRSEP
/* strsep replacement for platforms that lack it. Only works if
}
/** Given an evhttp_cmd_type, returns a constant string containing the
- * equivalent HTTP command, or NULL if the evhttp_command_type is
+ * equivalent HTTP command, or NULL if the evhttp_cmd_type is
* unrecognized. */
static const char *
-evhttp_method(enum evhttp_cmd_type type)
+evhttp_method_(struct evhttp_connection *evcon,
+ enum evhttp_cmd_type type, ev_uint16_t *flags)
{
- const char *method;
+ struct evhttp_ext_method ext_method;
+ const char *method = NULL;
+ ev_uint16_t tmp_flags = EVHTTP_METHOD_HAS_BODY;
switch (type) {
case EVHTTP_REQ_GET:
break;
case EVHTTP_REQ_HEAD:
method = "HEAD";
+ tmp_flags &= ~EVHTTP_METHOD_HAS_BODY;
break;
case EVHTTP_REQ_PUT:
method = "PUT";
break;
case EVHTTP_REQ_TRACE:
method = "TRACE";
+ tmp_flags &= ~EVHTTP_METHOD_HAS_BODY;
break;
case EVHTTP_REQ_CONNECT:
method = "CONNECT";
method = "MOVE";
break;
default:
- method = NULL;
+ /* setup the structure to allow for the cmp.
+ *
+ * if the cmp function is set, it has the ability to
+ * modify method and flags. Other fields will be
+ * ignored.
+ *
+ * NOTE: the flags returned are OR'd with the current
+ * flags.
+ */
+ tmp_flags = 0;
+ ext_method.method = NULL;
+ ext_method.type = type;
+ ext_method.flags = tmp_flags;
+
+ if (evcon->ext_method_cmp != NULL &&
+ evcon->ext_method_cmp(&ext_method) == 0) {
+
+ if (ext_method.type != type) {
+ event_debug(("%s: callback modified type from %u to %u, not allowed",
+ __func__, type, ext_method.type));
+ return NULL;
+ }
+
+ method = ext_method.method;
+ tmp_flags |= ext_method.flags;
+ }
+
break;
}
+ event_debug(("%s: type=%04x => '%s' flags=%04x",
+ __func__, (int)type, method, tmp_flags));
+
+ if (flags)
+ *flags = tmp_flags;
return (method);
}
struct evhttp_request *req)
{
const char *method;
+ ev_uint16_t flags;
evhttp_remove_header(req->output_headers, "Proxy-Connection");
/* Generate request line */
- if (!(method = evhttp_method(req->type))) {
+ if (!(method = evhttp_method_(evcon, req->type, &flags))) {
method = "NULL";
}
"%s %s HTTP/%d.%d\r\n",
method, req->uri, req->major, req->minor);
- /* Add the content length on a post or put request if missing */
- if ((req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) &&
- evhttp_find_header(req->output_headers, "Content-Length") == NULL){
+ /* Add the content length on a request if missing
+ * Always add it for POST and PUT requests as clients expect it */
+ if ((flags & EVHTTP_METHOD_HAS_BODY) &&
+ (evbuffer_get_length(req->output_buffer) > 0 ||
+ req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) &&
+ evhttp_find_header(req->output_headers, "Content-Length") == NULL) {
char size[22];
evutil_snprintf(size, sizeof(size), EV_SIZE_FMT,
EV_SIZE_ARG(evbuffer_get_length(req->output_buffer)));
break;
} /* switch */
+ if (!type) {
+ /* check extended methods, we only care about the
+ * type set by the cmp function if the cmp function
+ * returns a 0 value.
+ */
+ struct evhttp_ext_method ext_method;
+
+ ext_method.method = method;
+ ext_method.type = 0;
+
+ if (req->evcon->ext_method_cmp &&
+ req->evcon->ext_method_cmp(&ext_method) == 0) {
+ /* TODO: make sure the other fields in ext_method are
+ * not changed by the callback.
+ */
+ type = ext_method.type;
+ }
+ }
+
if (!type) {
event_debug(("%s: bad method %s on request %p from %s",
- __func__, method, req, req->remote_host));
- /* No error yet; we'll give a better error later when we see that
- * req->type is unsupported in evhttp_handle_request(). */
+ __func__, method, req, req->remote_host));
+ /* No error yet; we'll give a better error later when
+ * we see that req->type is unsupported. */
}
req->type = type;
}
static int
-evhttp_method_may_have_body(enum evhttp_cmd_type type)
+evhttp_method_may_have_body_(struct evhttp_connection *evcon, enum evhttp_cmd_type type)
{
- switch (type) {
- case EVHTTP_REQ_POST:
- case EVHTTP_REQ_PUT:
- case EVHTTP_REQ_PATCH:
- case EVHTTP_REQ_PROPFIND:
- case EVHTTP_REQ_PROPPATCH:
- case EVHTTP_REQ_MKCOL:
- case EVHTTP_REQ_LOCK:
- case EVHTTP_REQ_UNLOCK:
- case EVHTTP_REQ_COPY:
- case EVHTTP_REQ_MOVE:
-
- case EVHTTP_REQ_GET:
- case EVHTTP_REQ_DELETE:
- case EVHTTP_REQ_OPTIONS:
- case EVHTTP_REQ_CONNECT:
- return 1;
-
- case EVHTTP_REQ_TRACE:
- case EVHTTP_REQ_HEAD:
- default:
- return 0;
- }
+ ev_uint16_t flags;
+ evhttp_method_(evcon, type, &flags);
+ return (flags & EVHTTP_METHOD_HAS_BODY) ? 1 : 0;
}
static void
/* If this is a request without a body, then we are done */
if (req->kind == EVHTTP_REQUEST &&
- !evhttp_method_may_have_body(req->type)) {
+ !evhttp_method_may_have_body_(evcon, req->type)) {
evhttp_connection_done(evcon);
return;
}
return 0;
}
+void
+evhttp_connection_set_ext_method_cmp(struct evhttp_connection *evcon,
+ evhttp_ext_method_cb cmp)
+{
+ evcon->ext_method_cmp = cmp;
+}
+
void
evhttp_connection_set_base(struct evhttp_connection *evcon,
struct event_base *base)
http->allowed_methods = methods;
}
+void
+evhttp_set_ext_method_cmp(struct evhttp *http, evhttp_ext_method_cb cmp)
+{
+ http->ext_method_cmp = cmp;
+}
+
int
evhttp_set_cb(struct evhttp *http, const char *uri,
void (*cb)(struct evhttp_request *, void *), void *cbarg)
* we need to know which http server it belongs to.
*/
evcon->http_server = http;
+ evcon->ext_method_cmp = http->ext_method_cmp;
TAILQ_INSERT_TAIL(&http->connections, evcon, next);
if (evhttp_associate_new_request_with_connection(evcon) == -1)
struct evhttp_bound_socket;
struct evconnlistener;
struct evdns_base;
+struct evhttp_ext_method;
/**
* Create a new HTTP server.
EVENT2_EXPORT_SYMBOL
void evhttp_set_allowed_methods(struct evhttp* http, ev_uint32_t methods);
+typedef int (*evhttp_ext_method_cb)(struct evhttp_ext_method *);
+/**
+ Sets the callback function which allows HTTP extended methods
+ to be supported by this server.
+
+ The callback should :
+ - if method field is NULL : set method field according to type field
+ - else : set type and flags fields according to method string
+ - return 0 for success (known method / type)
+ - return -1 for error (unknown method / type)
+
+ evhttp_set_allowed_methods still needs to be called.
+
+ @param http the http server on which to add support to the methods
+ @param cmp the extended method callback
+ @see evhttp_ext_method
+*/
+EVENT2_EXPORT_SYMBOL
+void evhttp_set_ext_method_cmp(struct evhttp *http, evhttp_ext_method_cb cmp);
+
/**
Set a callback for a specified URI
EVHTTP_REQ_MOVE = 1 << 15,
};
+#define EVHTTP_REQ_MAX EVHTTP_REQ_MOVE
+
+/**
+ * @brief stucture that is passed to (and modified by) the
+ * extended method callback function
+ *
+ * @see evhttp_set_ext_method_cmp
+ * @see evhttp_connection_set_ext_method_cmp
+ */
+struct evhttp_ext_method {
+ const char *method;
+ ev_uint32_t type; /* @see enum evhttp_cmd_type */
+ ev_uint16_t flags; /* Available flag : EVHTTP_METHOD_HAS_BODY */
+};
+
+#define EVHTTP_METHOD_HAS_BODY 0x0001
+
/** a request object can represent either a request or a reply */
enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE };
EVENT2_EXPORT_SYMBOL
int evhttp_request_is_owned(struct evhttp_request *req);
+/**
+ * Sets extended method cmp callback for this http connection.
+ *
+ * @see evhttp_set_ext_method_cmp
+ */
+EVENT2_EXPORT_SYMBOL
+void evhttp_connection_set_ext_method_cmp(struct evhttp_connection *evcon,
+ evhttp_ext_method_cb cmp);
+
/**
* Returns the connection object associated with the request or NULL
*
static char const BASIC_REQUEST_BODY[] = "This is funny";
+/* defines an extended HTTP method "CUSTOM"
+ * without body */
+#define EVHTTP_REQ_CUSTOM ((EVHTTP_REQ_MAX) << 1)
+
+static int ext_method_cb(struct evhttp_ext_method *p)
+{
+ if (p == NULL)
+ return -1;
+ if (p->method) {
+ if (strcmp(p->method, "CUSTOM") == 0) {
+ p->type = EVHTTP_REQ_CUSTOM;
+ p->flags = 0; /*EVHTTP_METHOD_HAS_BODY*/
+ return 0;
+ }
+ } else {
+ if (p->type == EVHTTP_REQ_CUSTOM) {
+ p->method = "CUSTOM";
+ return 0;
+ }
+ }
+ return -1;
+}
+
static void http_basic_cb(struct evhttp_request *req, void *arg);
static void http_timeout_cb(struct evhttp_request *req, void *arg);
static void http_large_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_genmethod_cb(struct evhttp_request *req, void *arg);
+static void http_custom_cb(struct evhttp_request *req, void *arg);
static void http_delay_cb(struct evhttp_request *req, void *arg);
static void http_large_delay_cb(struct evhttp_request *req, void *arg);
static void http_badreq_cb(struct evhttp_request *req, void *arg);
evhttp_set_gencb(myhttp, cb, cbarg);
+ /* add support for extended HTTP methods */
+ evhttp_set_ext_method_cmp(myhttp, ext_method_cb);
+
/* Register a callback for certain types of requests */
evhttp_set_cb(myhttp, "/test", http_basic_cb, myhttp);
evhttp_set_cb(myhttp, "/test nonconformant", http_basic_cb, myhttp);
evhttp_set_cb(myhttp, "/unlockit", http_genmethod_cb, base);
evhttp_set_cb(myhttp, "/copyit", http_genmethod_cb, base);
evhttp_set_cb(myhttp, "/moveit", http_genmethod_cb, base);
+ evhttp_set_cb(myhttp, "/custom", http_custom_cb, base);
evhttp_set_cb(myhttp, "/delay", http_delay_cb, base);
evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, base);
evhttp_set_cb(myhttp, "/badrequest", http_badreq_cb, base);
evutil_closesocket(fd);
}
+/*
+ * HTTP CUSTOM test, just piggyback on the basic test
+ */
+static void
+http_custom_cb(struct evhttp_request *req, void *arg)
+{
+ struct evbuffer *evb = evbuffer_new();
+ int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL;
+
+ /* Expecting a CUSTOM request */
+ if (evhttp_request_get_command(req) != EVHTTP_REQ_CUSTOM) {
+ fprintf(stdout, "FAILED (custom type)\n");
+ exit(1);
+ }
+
+ TT_BLATHER(("%s: called\n", __func__));
+ evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
+
+ /* allow sending of an empty reply */
+ evhttp_send_reply(req, HTTP_OK, "Everything is fine",
+ !empty ? evb : NULL);
+
+ evbuffer_free(evb);
+}
+
+static void
+http_custom_test(void *arg)
+{
+ struct basic_test_data *data = arg;
+ struct bufferevent *bev;
+ evutil_socket_t fd = -1;
+ const char *http_request;
+ ev_uint16_t port = 0;
+ struct evhttp *http;
+
+ test_ok = 0;
+
+ http = http_setup(&port, data->base, 0);
+ /* Allow custom */
+ evhttp_set_allowed_methods(http, EVHTTP_REQ_CUSTOM);
+
+ tt_assert(http);
+ fd = http_connect("127.0.0.1", port);
+ tt_int_op(fd, >=, 0);
+
+ /* Stupid thing to send a request */
+ bev = bufferevent_socket_new(data->base, fd, 0);
+ bufferevent_setcb(bev, http_readcb, http_writecb,
+ http_errorcb, data->base);
+
+ http_request =
+ "CUSTOM /custom 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(data->base);
+
+ bufferevent_free(bev);
+ evutil_closesocket(fd);
+ fd = -1;
+
+ evhttp_free(http);
+
+ tt_int_op(test_ok, ==, 2);
+ end:
+ if (fd >= 0)
+ evutil_closesocket(fd);
+}
+
static void
http_delete_test(void *arg)
{
http_allowed_methods_test(void *arg)
{
struct basic_test_data *data = arg;
- struct bufferevent *bev1, *bev2, *bev3;
- evutil_socket_t fd1=-1, fd2=-1, fd3=-1;
+ struct bufferevent *bev1, *bev2, *bev3, *bev4;
+ evutil_socket_t fd1=-1, fd2=-1, fd3=-1, fd4=-1;
const char *http_request;
- char *result1=NULL, *result2=NULL, *result3=NULL;
+ char *result1=NULL, *result2=NULL, *result3=NULL, *result4=NULL;
ev_uint16_t port = 0;
struct evhttp *http = http_setup(&port, data->base, 0);
fd1 = http_connect("127.0.0.1", port);
tt_assert(fd1 != EVUTIL_INVALID_SOCKET);
- /* GET is out; PATCH is in. */
- evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH);
+ /* GET is out; PATCH & CUSTOM are in. */
+ evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH | EVHTTP_REQ_CUSTOM);
/* Stupid thing to send a request */
bev1 = bufferevent_socket_new(data->base, fd1, 0);
event_base_dispatch(data->base);
+ fd4 = http_connect("127.0.0.1", port);
+ tt_int_op(fd4, >=, 0);
+
+ bev4 = bufferevent_socket_new(data->base, fd4, 0);
+ bufferevent_enable(bev4, EV_READ|EV_WRITE);
+ bufferevent_setcb(bev4, NULL, NULL,
+ http_allowed_methods_eventcb, &result4);
+
+ http_request =
+ "CUSTOM /test HTTP/1.1\r\n"
+ "Host: somehost\r\n"
+ "Connection: close\r\n"
+ "\r\n";
+
+ bufferevent_write(bev4, http_request, strlen(http_request));
+
+ event_base_dispatch(data->base);
+
bufferevent_free(bev1);
bufferevent_free(bev2);
bufferevent_free(bev3);
+ bufferevent_free(bev4);
evhttp_free(http);
tt_assert(result3);
tt_assert(!strncmp(result3, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 ")));
+ /* Custom method (and allowed) */
+ tt_assert(result4);
+ tt_assert(!strncmp(result4, "HTTP/1.1 200 ", strlen("HTTP/1.1 200 ")));
+
end:
if (result1)
free(result1);
free(result2);
if (result3)
free(result3);
+ if (result4)
+ free(result4);
if (fd1 >= 0)
evutil_closesocket(fd1);
if (fd2 >= 0)
evutil_closesocket(fd2);
if (fd3 >= 0)
evutil_closesocket(fd3);
+ if (fd4 >= 0)
+ evutil_closesocket(fd4);
}
static void http_request_no_action_done(struct evhttp_request *, void *);
}
tt_assert(http);
+ evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_CUSTOM);
+
if (ssl) {
#ifdef EVENT__HAVE_OPENSSL
SSL *ssl = SSL_new(get_ssl_ctx());
tt_assert(evhttp_connection_get_server(evcon) == NULL);
+ /* add support for CUSTOM method */
+ evhttp_connection_set_ext_method_cmp(evcon, ext_method_cb);
+
/*
* At this point, we want to schedule a request to the HTTP
* server using our make request method.
event_base_dispatch(data->base);
+ /* make a CUSTOM request */
+ test_ok = 0;
+
+ req = evhttp_request_new(http_request_empty_done, data->base);
+
+ /* our CUSTOM method doesn't have Body */
+ evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis");
+
+ /* We give ownership of the request to the connection */
+ if (evhttp_make_request(evcon, req, EVHTTP_REQ_CUSTOM, "/test") == -1) {
+ tt_abort_msg("Couldn't make request");
+ }
+
+ event_base_dispatch(data->base);
+
end:
if (evcon)
evhttp_connection_free(evcon);
HTTP(unlock),
HTTP(copy),
HTTP(move),
+ HTTP(custom),
HTTP(allowed_methods),
HTTP(failure),
HTTP(connection),
The watcher API is defined in <event2/watch.h>. A concrete example of how
watchers can help monitor server performance is available in
"sample/watch-timing.c".
+
+* Support for custom HTTP methods
+
+Libevent HTTP code now supports defining custom HTTP methods. It is done
+through a callback:
+
+ #define EVHTTP_REQ_CUSTOM ((EVHTTP_REQ_MAX) << 1)
+ static int ext_method_cb(struct evhttp_ext_method *p)
+ {
+ if (p == NULL)
+ return -1;
+ if (p->method) {
+ if (strcmp(p->method, "CUSTOM") == 0) {
+ p->type = EVHTTP_REQ_CUSTOM;
+ p->flags = 0; /*EVHTTP_METHOD_HAS_BODY*/
+ return 0;
+ }
+ } else {
+ if (p->type == EVHTTP_REQ_CUSTOM) {
+ p->method = "CUSTOM";
+ return 0;
+ }
+ }
+ }
+
+And to register this callback with http server you can use:
+ evhttp_set_ext_method_cmp(http, ext_method_cb);
+
+Or registering callback with one client only:
+ evhttp_connection_set_ext_method_cmp(evcon, ext_method_cb);