#include "event2/event-config.h"
#include "evconfig-private.h"
+#define member_size(type, member) sizeof(((type *)0)->member)
+
#ifdef EVENT__HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
if (evcon->address != NULL)
mm_free(evcon->address);
+ if (evcon->unixsocket != NULL)
+ mm_free(evcon->unixsocket);
+
mm_free(evcon);
}
return (evhttp_connection_base_new(NULL, NULL, address, port));
}
-struct evhttp_connection *
-evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
- const char *address, ev_uint16_t port)
+static struct evhttp_connection *
+evhttp_connection_new_(struct event_base *base, struct bufferevent* bev)
{
- struct evhttp_connection *evcon = NULL;
-
- event_debug(("Attempting connection to %s:%d\n", address, port));
+ struct evhttp_connection *evcon;
if ((evcon = mm_calloc(1, sizeof(struct evhttp_connection))) == NULL) {
event_warn("%s: calloc failed", __func__);
goto error;
}
- evcon->port = port;
-
evcon->max_headers_size = EV_SIZE_MAX;
evcon->max_body_size = EV_SIZE_MAX;
evcon->retry_cnt = evcon->retry_max = 0;
- if ((evcon->address = mm_strdup(address)) == NULL) {
- event_warn("%s: strdup failed", __func__);
- goto error;
- }
-
if (bev == NULL) {
if (!(bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE))) {
event_warn("%s: bufferevent_socket_new failed", __func__);
bufferevent_get_priority(bev),
evhttp_deferred_read_cb, evcon);
- evcon->dns_base = dnsbase;
evcon->ai_family = AF_UNSPEC;
return (evcon);
return (NULL);
}
+#ifndef _WIN32
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_unix_new(struct event_base *base, struct bufferevent* bev, const char *unixsocket)
+{
+ struct evhttp_connection *evcon;
+
+ if (strlen(unixsocket) >= member_size(struct sockaddr_un, sun_path)) {
+ event_warn("%s: unix socket too long", __func__);
+ return NULL;
+ }
+
+ evcon = evhttp_connection_new_(base, bev);
+ if (evcon == NULL)
+ goto error;
+
+ if ((evcon->unixsocket = mm_strdup(unixsocket)) == NULL) {
+ event_warn("%s: strdup failed", __func__);
+ goto error;
+ }
+
+ evcon->ai_family = AF_UNIX;
+
+ return (evcon);
+ error:
+ if (evcon != NULL)
+ evhttp_connection_free(evcon);
+ return (NULL);
+}
+#endif
+
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
+ const char *address, unsigned short port)
+{
+ struct evhttp_connection *evcon;
+
+ event_debug(("Attempting connection to %s:%d\n", address, port));
+
+ evcon = evhttp_connection_new_(base, bev);
+ if (evcon == NULL)
+ goto error;
+
+ if ((evcon->address = mm_strdup(address)) == NULL) {
+ event_warn("%s: strdup failed", __func__);
+ goto error;
+ }
+ evcon->port = port;
+ evcon->dns_base = dnsbase;
+
+ return (evcon);
+error:
+ if (evcon != NULL)
+ evhttp_connection_free(evcon);
+ return (NULL);
+}
+
+
struct bufferevent* evhttp_connection_get_bufferevent(struct evhttp_connection *evcon)
{
return evcon->bufev;
socklen = sizeof(struct sockaddr_in6);
}
ret = bufferevent_socket_connect(evcon->bufev, sa, socklen);
- } else {
+ }
+#ifndef _WIN32
+ else if (evcon->unixsocket) {
+ struct sockaddr_un sockaddr;
+ sockaddr.sun_family = AF_UNIX;
+ strcpy(sockaddr.sun_path, evcon->unixsocket);
+ ret = bufferevent_socket_connect(evcon->bufev, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
+ }
+#endif
+ else {
ret = bufferevent_socket_connect_hostname(evcon->bufev,
evcon->dns_base, evcon->ai_family, address, evcon->port);
}
evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen)
{
struct evhttp_connection *evcon;
- char *hostname = NULL, *portname = NULL;
struct bufferevent* bev = NULL;
#ifdef EVENT__HAVE_STRUCT_SOCKADDR_UN
}
#endif
- name_from_addr(sa, salen, &hostname, &portname);
- if (hostname == NULL || portname == NULL) {
- if (hostname) mm_free(hostname);
- if (portname) mm_free(portname);
- return (NULL);
+#ifndef _WIN32
+ if (sa->sa_family == AF_UNIX) {
+ struct sockaddr_un *sockaddr = (struct sockaddr_un *)sa;
+
+ event_debug(("%s: new request from unix socket on "
+ EV_SOCK_FMT"\n", __func__, EV_SOCK_ARG(fd)));
+
+ /* we need a connection object to put the http request on */
+ if (http->bevcb != NULL) {
+ bev = (*http->bevcb)(http->base, http->bevcbarg);
+ }
+
+ evcon = evhttp_connection_base_bufferevent_unix_new(http->base,
+ bev, sockaddr->sun_path);
}
+ else
+#endif
+ {
+ char *hostname = NULL, *portname = NULL;
+
+ name_from_addr(sa, salen, &hostname, &portname);
+ if (hostname == NULL || portname == NULL) {
+ if (hostname) mm_free(hostname);
+ if (portname) mm_free(portname);
+ return (NULL);
+ }
- event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
- __func__, hostname, portname, EV_SOCK_ARG(fd)));
+ event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
+ __func__, hostname, portname, EV_SOCK_ARG(fd)));
- /* we need a connection object to put the http request on */
- if (http->bevcb != NULL) {
- bev = (*http->bevcb)(http->base, http->bevcbarg);
+ /* we need a connection object to put the http request on */
+ if (http->bevcb != NULL) {
+ bev = (*http->bevcb)(http->base, http->bevcbarg);
+ }
+ evcon = evhttp_connection_base_bufferevent_new(
+ http->base, NULL, bev, hostname, atoi(portname));
+ mm_free(hostname);
+ mm_free(portname);
}
- evcon = evhttp_connection_base_bufferevent_new(
- http->base, NULL, bev, hostname, atoi(portname));
- mm_free(hostname);
- mm_free(portname);
if (evcon == NULL)
return (NULL);
if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL)
return (-1);
- if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
- event_warn("%s: strdup", __func__);
- evhttp_request_free(req);
- return (-1);
+ if (evcon->address != NULL) {
+ if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
+ event_warn("%s: strdup", __func__);
+ evhttp_request_free(req);
+ return (-1);
+ }
}
req->remote_port = evcon->port;
char *userinfo; /* userinfo (typically username:pass), or NULL */
char *host; /* hostname, IP address, or NULL */
int port; /* port, or zero */
+#ifndef _WIN32
+ char *unixsocket; /* unix domain socket or NULL */
+#endif
char *path; /* path, or "". */
char *query; /* query, or NULL */
char *fragment; /* fragment or NULL */
} else {
cp = s;
}
+
+#ifndef _WIN32
+ if (*flags & EVHTTP_URI_UNIX_SOCKET && !strncmp(cp, "unix:", 5)) {
+ char *e = strchr(cp + 5, ':');
+ if (e) {
+ *e = '\0';
+ uri->unixsocket = mm_strdup(cp + 5);
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+#endif
+
/* Optionally, we end with ":port" */
for (port=eos-1; port >= cp && EVUTIL_ISDIGIT_(*port); --port)
;
URI_FREE_STR_(scheme);
URI_FREE_STR_(userinfo);
URI_FREE_STR_(host);
+#ifndef _WIN32
+ URI_FREE_STR_(unixsocket);
+#endif
URI_FREE_STR_(path);
URI_FREE_STR_(query);
URI_FREE_STR_(fragment);
URI_ADD_(scheme);
evbuffer_add(tmp, ":", 1);
}
+#ifndef _WIN32
+ if (uri->unixsocket) {
+ evbuffer_add(tmp, "//", 2);
+ if (uri->userinfo)
+ evbuffer_add_printf(tmp, "%s@", uri->userinfo);
+ evbuffer_add_printf(tmp, "unix:%s:", uri->unixsocket);
+ }
+ else
+#endif
if (uri->host) {
evbuffer_add(tmp, "//", 2);
if (uri->userinfo)
{
return uri->host;
}
+#ifndef _WIN32
+const char *
+evhttp_uri_get_unixsocket(const struct evhttp_uri *uri)
+{
+ return uri->unixsocket;
+}
+#endif
int
evhttp_uri_get_port(const struct evhttp_uri *uri)
{
return 0;
}
+#ifndef _WIN32
+int
+evhttp_uri_set_unixsocket(struct evhttp_uri *uri, const char *unixsocket)
+{
+ URI_SET_STR_(unixsocket);
+ return 0;
+}
+#endif
int
evhttp_uri_set_port(struct evhttp_uri *uri, int port)
{
* @param dnsbase the dns_base to use for resolving host names; if not
* specified host name resolution will block.
* @param bev a bufferevent to use for connecting to the server; if NULL, a
- * socket-based bufferevent will be created. This buffrevent will be freed
+ * socket-based bufferevent will be created. This bufferevent will be freed
* when the connection closes. It must have no fd set on it.
* @param address the address to which to connect
* @param port the port to connect to
struct evhttp_connection *evhttp_connection_base_bufferevent_new(
struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev, const char *address, ev_uint16_t port);
+/**
+ * Create and return a connection object that can be used to for making HTTP
+ * requests over an unix domain socket.
+ *
+ * @param base the event_base to use for handling the connection
+ * @param bev a bufferevent to use for connecting to the server; if NULL, a
+ * socket-based bufferevent will be created. This bufferevent will be freed
+ * when the connection closes. It must have no fd set on it.
+ * @param path path of unix domain socket
+ * @return an evhttp_connection object that can be used for making requests
+ */
+EVENT2_EXPORT_SYMBOL
+struct evhttp_connection *evhttp_connection_base_bufferevent_unix_new(
+ struct event_base *base, struct bufferevent* bev, const char *path);
+
/**
* Return the bufferevent that an evhttp_connection is using.
*/
*/
EVENT2_EXPORT_SYMBOL
const char *evhttp_uri_get_host(const struct evhttp_uri *uri);
+/** Return the unix socket part of an evhttp_uri, or NULL if there is no unix
+ * socket set */
+EVENT2_EXPORT_SYMBOL
+const char *evhttp_uri_get_unixsocket(const struct evhttp_uri *uri);
/** Return the port part of an evhttp_uri, or -1 if there is no port set. */
EVENT2_EXPORT_SYMBOL
int evhttp_uri_get_port(const struct evhttp_uri *uri);
* Returns 0 on success, -1 if host is not well-formed. */
EVENT2_EXPORT_SYMBOL
int evhttp_uri_set_host(struct evhttp_uri *uri, const char *host);
+/** Set the unix socket of an evhttp_uri, or clear the unix socket if unixsocket==NULL.
+ * Returns 0 on success, -1 if unixsocket is not well-formed */
+EVENT2_EXPORT_SYMBOL
+int evhttp_uri_set_unixsocket(struct evhttp_uri *uri, const char *unixsocket);
+
/** Set the port of an evhttp_uri, or clear the port if port==-1.
* Returns 0 on success, -1 if port is not well-formed. */
EVENT2_EXPORT_SYMBOL
* </ul>
*/
#define EVHTTP_URI_NONCONFORMANT 0x01
+
/**
* Strip brackets from the IPv6 address and only for evhttp_uri_get_host(),
* evhttp_uri_join() returns the host with brackets.
*/
#define EVHTTP_URI_HOST_STRIP_BRACKETS 0x04
+/**
+ * Parse unix domain socket URIs, for example:
+ *
+ * http://unix:/run/control.sock:/controller
+ */
+#define EVHTTP_URI_UNIX_SOCKET 0x08
+
/** Alias for evhttp_uri_parse_with_flags(source_uri, 0) */
EVENT2_EXPORT_SYMBOL
struct evhttp_uri *evhttp_uri_parse(const char *source_uri);
evhttp_free(http);
}
-
/* test date header and content length */
static void
evhttp_free(http);
}
+#ifndef _WIN32
+/* test unix socket */
+#include <sys/un.h>
+
+/* Should this be part of the libevent library itself? */
+static int evhttp_bind_unixsocket(struct evhttp *httpd, const char *path)
+{
+ struct sockaddr_un local;
+ struct stat st;
+ int fd;
+
+ local.sun_family = AF_UNIX;
+ strcpy(local.sun_path, path);
+
+ /* if the file exists and it is a socket, remove it. Someone
+ could create a symlink and get us to remove random files */
+ if (stat(path, &st) == 0 && S_ISSOCK(st.st_mode))
+ unlink(path);
+
+ fd = socket(AF_UNIX, SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_STREAM, 0);
+ if (fd == -1)
+ return -1;
+
+ if (bind(fd, (struct sockaddr*)&local, sizeof(local))) {
+ close(fd);
+ return -1;
+ }
+
+ /* fchmod(fd, 0777) does nothing */
+ if (chmod(path, 0777)) {
+ close(fd);
+ return -1;
+ }
+
+ if (listen(fd, 128)) {
+ close(fd);
+ return -1;
+ }
+
+ if (evhttp_accept_socket(httpd, fd)) {
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void http_unix_socket_test(void *arg)
+{
+ struct basic_test_data *data = arg;
+ struct evhttp_uri *uri;
+ struct evhttp_connection *evcon = NULL;
+ struct evhttp_request *req;
+
+ struct evhttp *myhttp = evhttp_new(data->base);
+
+ tt_assert(!evhttp_bind_unixsocket(myhttp, "foo"));
+
+ evhttp_set_cb(myhttp, "/", http_dispatcher_cb, data->base);
+
+ uri = evhttp_uri_parse_with_flags("http://unix:./foo:/?arg=val", EVHTTP_URI_UNIX_SOCKET);
+ tt_assert(uri);
+
+ evcon = evhttp_connection_base_bufferevent_unix_new(data->base, NULL, evhttp_uri_get_unixsocket(uri));
+
+ /*
+ * At this point, we want to schedule an HTTP GET request
+ * server using our make request method.
+ */
+ req = evhttp_request_new(http_dispatcher_test_done, data->base);
+ tt_assert(req);
+
+ /* Add the information that we care about */
+ evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
+
+ if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) {
+ tt_abort_msg("Couldn't make request");
+ }
+
+ event_base_dispatch(data->base);
+
+ end:
+ if (evcon)
+ evhttp_connection_free(evcon);
+ if (myhttp)
+ evhttp_free(myhttp);
+ if (uri)
+ evhttp_uri_free(uri);
+}
+#endif
+
/*
* HTTP POST test.
*/
static void
http_parse_uri_test(void *ptr)
{
- const int nonconform = (ptr != NULL);
- const unsigned parse_flags =
- nonconform ? EVHTTP_URI_NONCONFORMANT : 0;
+ int nonconform = 0, unixsock = 0;
+ int parse_flags = 0;
struct evhttp_uri *uri = NULL;
char url_tmp[4096];
#define URI_PARSE_FLAGS(uri, flags) \
TT_FAIL(("\"%s\" != \"%s\"",ret,want)); \
} while(0)
+ if (ptr) {
+ if (strstr(ptr, "nc") != NULL) {
+ nonconform = 1;
+ parse_flags |= EVHTTP_URI_NONCONFORMANT;
+ }
+ if (strstr(ptr, "un") != NULL) {
+ unixsock = 1;
+ parse_flags |= EVHTTP_URI_UNIX_SOCKET;
+ }
+ }
+
tt_want(evhttp_uri_join(NULL, 0, 0) == NULL);
tt_want(evhttp_uri_join(NULL, url_tmp, 0) == NULL);
tt_want(evhttp_uri_join(NULL, url_tmp, sizeof(url_tmp)) == NULL);
evhttp_uri_free(uri); \
} \
} while(0)
+#define UNI(s) do { \
+ uri = URI_PARSE(s); \
+ if (uri == NULL && unixsock) { \
+ TT_FAIL(("Couldn't parse unix socket URI \"%s\"", \
+ s)); \
+ } \
+ if (uri) { \
+ tt_want(evhttp_uri_join(uri, url_tmp, \
+ sizeof(url_tmp))); \
+ evhttp_uri_free(uri); \
+ } \
+ } while(0)
NCF("http://www.test.com/ why hello");
NCF("http://www.test.com/why-hello\x01");
BAD("http://www.example.com:hihi/");
BAD("://www.example.com/");
+ UNI("http://unix:/tmp/foobar/:/foo");
+ UNI("http://user:pass@unix:/tmp/foobar/:/foo");
+ UNI("http://unix:a:");
+
/* bad URIs: joining */
uri = evhttp_uri_new();
tt_want(0==evhttp_uri_set_host(uri, "www.example.com"));
{ "parse_query_str_flags", http_parse_query_str_flags_test, 0, NULL, NULL },
{ "parse_uri", http_parse_uri_test, 0, NULL, NULL },
{ "parse_uri_nc", http_parse_uri_test, 0, &basic_setup, (void*)"nc" },
+ { "parse_uri_un", http_parse_uri_test, 0, &basic_setup, (void*)"un" },
+ { "parse_uri_un_nc", http_parse_uri_test, 0, &basic_setup, (void*)"un_nc" },
{ "uriencode", http_uriencode_test, 0, NULL, NULL },
HTTP(basic),
HTTP(basic_trailing_space),
HTTP_RET_N(cancel_by_host_ns_timeout_inactive_server, cancel, TT_NO_LOGS, BY_HOST | NO_NS | NS_TIMEOUT | INACTIVE_SERVER),
HTTP(virtual_host),
+#ifndef _WIN32
+ HTTP(unix_socket),
+#endif
HTTP(post),
HTTP(put),
HTTP(delete),