1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2025-04-21 12:27:27 +03:00

[package] uhttpd:

- rewrite large parts of the server, use uloop event driven structure
	- support concurrent requests and make the upper limit configurable
	- implement initial version of HTTP-to-ubus JSON proxy and session.* namespace
	- add compile time support for debug information
	- code style changes
	- bump package revision

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@31931 3c298f89-4303-0410-b956-a3cf2f4a3e73
This commit is contained in:
jow
2012-05-28 00:52:24 +00:00
parent dfea949949
commit 8b4e17bfbc
18 changed files with 2637 additions and 1393 deletions

View File

@@ -42,11 +42,6 @@ static void uh_sigterm(int sig)
run = 0;
}
static void uh_sigchld(int sig)
{
while (waitpid(-1, NULL, WNOHANG) > 0) { }
}
static void uh_config_parse(struct config *conf)
{
FILE *c;
@@ -126,6 +121,8 @@ static void uh_config_parse(struct config *conf)
}
}
static void uh_listener_cb(struct uloop_fd *u, unsigned int events);
static int uh_socket_bind(fd_set *serv_fds, int *max_fd,
const char *host, const char *port,
struct addrinfo *hints, int do_tls,
@@ -221,6 +218,9 @@ static int uh_socket_bind(fd_set *serv_fds, int *max_fd,
fd_cloexec(sock);
*max_fd = max(*max_fd, sock);
l->fd.cb = uh_listener_cb;
uloop_fd_add(&l->fd, ULOOP_READ | ULOOP_WRITE);
bound++;
continue;
@@ -237,7 +237,7 @@ static int uh_socket_bind(fd_set *serv_fds, int *max_fd,
static struct http_request * uh_http_header_parse(struct client *cl,
char *buffer, int buflen)
{
char *method = &buffer[0];
char *method = buffer;
char *path = NULL;
char *version = NULL;
@@ -248,9 +248,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,
int i;
int hdrcount = 0;
static struct http_request req;
memset(&req, 0, sizeof(req));
struct http_request *req = &cl->request;
/* terminate initial header line */
@@ -282,15 +280,15 @@ static struct http_request * uh_http_header_parse(struct client *cl,
switch(method[0])
{
case 'G':
req.method = UH_HTTP_MSG_GET;
req->method = UH_HTTP_MSG_GET;
break;
case 'H':
req.method = UH_HTTP_MSG_HEAD;
req->method = UH_HTTP_MSG_HEAD;
break;
case 'P':
req.method = UH_HTTP_MSG_POST;
req->method = UH_HTTP_MSG_POST;
break;
}
}
@@ -304,7 +302,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,
}
else
{
req.url = path;
req->url = path;
}
/* check version */
@@ -317,9 +315,13 @@ static struct http_request * uh_http_header_parse(struct client *cl,
}
else
{
req.version = strtof(&version[5], NULL);
req->version = strtof(&version[5], NULL);
}
D("SRV: %s %s HTTP/%.1f\n",
(req->method == UH_HTTP_MSG_POST) ? "POST" :
(req->method == UH_HTTP_MSG_GET) ? "GET" : "HEAD",
req->url, req->version);
/* process header fields */
for (i = (int)(headers - buffer); i < buflen; i++)
@@ -330,10 +332,12 @@ static struct http_request * uh_http_header_parse(struct client *cl,
buffer[i] = 0;
/* store */
if ((hdrcount + 1) < array_size(req.headers))
if ((hdrcount + 1) < array_size(req->headers))
{
req.headers[hdrcount++] = hdrname;
req.headers[hdrcount++] = hdrdata;
D("SRV: HTTP: %s: %s\n", hdrname, hdrdata);
req->headers[hdrcount++] = hdrname;
req->headers[hdrcount++] = hdrdata;
hdrname = hdrdata = NULL;
}
@@ -341,6 +345,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,
/* too large */
else
{
D("SRV: HTTP: header too big (too many headers)\n");
uh_http_response(cl, 413, "Request Entity Too Large");
return NULL;
}
@@ -365,8 +370,8 @@ static struct http_request * uh_http_header_parse(struct client *cl,
}
/* valid enough */
req.redirect_status = 200;
return &req;
req->redirect_status = 200;
return req;
}
/* Malformed request */
@@ -377,64 +382,43 @@ static struct http_request * uh_http_header_parse(struct client *cl,
static struct http_request * uh_http_header_recv(struct client *cl)
{
static char buffer[UH_LIMIT_MSGHEAD];
char *bufptr = &buffer[0];
char *bufptr = cl->httpbuf.buf;
char *idxptr = NULL;
struct timeval timeout;
fd_set reader;
ssize_t blen = sizeof(buffer)-1;
ssize_t blen = sizeof(cl->httpbuf)-1;
ssize_t rlen = 0;
memset(buffer, 0, sizeof(buffer));
memset(bufptr, 0, sizeof(cl->httpbuf));
while (blen > 0)
{
FD_ZERO(&reader);
FD_SET(cl->socket, &reader);
/* receive data */
ensure_out(rlen = uh_tcp_recv(cl, bufptr, blen));
D("SRV: Client(%d) peek(%d) = %d\n", cl->fd.fd, blen, rlen);
/* fail after 0.1s */
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
/* check whether fd is readable */
if (select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0)
if (rlen <= 0)
{
/* receive data */
ensure_out(rlen = uh_tcp_peek(cl, bufptr, blen));
if ((idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4)))
{
ensure_out(rlen = uh_tcp_recv(cl, bufptr,
(int)(idxptr - bufptr) + 4));
/* header read complete ... */
blen -= rlen;
return uh_http_header_parse(cl, buffer,
sizeof(buffer) - blen - 1);
}
else
{
ensure_out(rlen = uh_tcp_recv(cl, bufptr, rlen));
/* unexpected eof - #7904 */
if (rlen == 0)
return NULL;
blen -= rlen;
bufptr += rlen;
}
}
else
{
/* invalid request (unexpected eof/timeout) */
D("SRV: Client(%d) dead [%s]\n", cl->fd.fd, strerror(errno));
return NULL;
}
blen -= rlen;
bufptr += rlen;
if ((idxptr = strfind(cl->httpbuf.buf, sizeof(cl->httpbuf.buf),
"\r\n\r\n", 4)))
{
/* header read complete ... */
cl->httpbuf.ptr = idxptr + 4;
cl->httpbuf.len = bufptr - cl->httpbuf.ptr;
return uh_http_header_parse(cl, cl->httpbuf.buf,
(cl->httpbuf.ptr - cl->httpbuf.buf));
}
}
/* request entity too large */
D("SRV: HTTP: header too big (buffer exceeded)\n");
uh_http_response(cl, 413, "Request Entity Too Large");
out:
@@ -456,197 +440,276 @@ static int uh_path_match(const char *prefix, const char *url)
}
#endif
static void uh_dispatch_request(struct client *cl, struct http_request *req,
struct path_info *pin)
static bool uh_dispatch_request(struct client *cl, struct http_request *req)
{
#ifdef HAVE_CGI
struct path_info *pin;
struct interpreter *ipr = NULL;
struct config *conf = cl->server->conf;
if (uh_path_match(cl->server->conf->cgi_prefix, pin->name) ||
(ipr = uh_interpreter_lookup(pin->phys)))
#ifdef HAVE_LUA
/* Lua request? */
if (conf->lua_state &&
uh_path_match(conf->lua_prefix, req->url))
{
uh_cgi_request(cl, req, pin, ipr);
return conf->lua_request(cl, conf->lua_state);
}
else
#endif
#ifdef HAVE_UBUS
/* ubus request? */
if (conf->ubus_state &&
uh_path_match(conf->ubus_prefix, req->url))
{
uh_file_request(cl, req, pin);
return conf->ubus_request(cl, conf->ubus_state);
}
else
#endif
/* dispatch request */
if ((pin = uh_path_lookup(cl, req->url)) != NULL)
{
/* auth ok? */
if (!pin->redirected && uh_auth_check(cl, req, pin))
{
#ifdef HAVE_CGI
if (uh_path_match(conf->cgi_prefix, pin->name) ||
(ipr = uh_interpreter_lookup(pin->phys)) != NULL)
{
return uh_cgi_request(cl, pin, ipr);
}
#endif
return uh_file_request(cl, pin);
}
}
/* 404 - pass 1 */
else
{
/* Try to invoke an error handler */
if ((pin = uh_path_lookup(cl, conf->error_handler)) != NULL)
{
/* auth ok? */
if (uh_auth_check(cl, req, pin))
{
req->redirect_status = 404;
#ifdef HAVE_CGI
if (uh_path_match(conf->cgi_prefix, pin->name) ||
(ipr = uh_interpreter_lookup(pin->phys)) != NULL)
{
return uh_cgi_request(cl, pin, ipr);
}
#endif
return uh_file_request(cl, pin);
}
}
/* 404 - pass 2 */
else
{
uh_http_sendhf(cl, 404, "Not Found", "No such file or directory");
}
}
return false;
}
static void uh_client_cb(struct uloop_fd *u, unsigned int events);
static void uh_listener_cb(struct uloop_fd *u, unsigned int events)
{
int new_fd;
struct listener *serv;
struct client *cl;
struct config *conf;
serv = container_of(u, struct listener, fd);
conf = serv->conf;
/* defer client if maximum number of requests is exceeded */
if (serv->n_clients >= conf->max_requests)
return;
/* handle new connections */
if ((new_fd = accept(u->fd, NULL, 0)) != -1)
{
D("SRV: Server(%d) accept => Client(%d)\n", u->fd, new_fd);
/* add to global client list */
if ((cl = uh_client_add(new_fd, serv)) != NULL)
{
/* add client socket to global fdset */
uloop_fd_add(&cl->fd, ULOOP_READ | ULOOP_WRITE);
#ifdef HAVE_TLS
/* setup client tls context */
if (conf->tls)
{
if (conf->tls_accept(cl) < 1)
{
D("SRV: Client(%d) SSL handshake failed, drop\n", new_fd);
/* remove from global client list */
uh_client_remove(cl);
return;
}
}
#endif
cl->fd.cb = uh_client_cb;
fd_cloexec(new_fd);
}
/* insufficient resources */
else
{
fprintf(stderr, "uh_client_add(): Cannot allocate memory\n");
close(new_fd);
}
}
}
static void uh_mainloop(struct config *conf, fd_set serv_fds, int max_fd)
static void uh_child_cb(struct uloop_process *p, int rv)
{
/* master file descriptor list */
fd_set used_fds, read_fds;
struct client *cl = container_of(p, struct client, proc);
/* working structs */
struct http_request *req;
struct path_info *pin;
struct client *cl;
D("SRV: Client(%d) child(%d) is dead\n", cl->fd.fd, cl->proc.pid);
/* maximum file descriptor number */
int new_fd, cur_fd = 0;
cl->dead = true;
cl->fd.eof = true;
uh_client_cb(&cl->fd, ULOOP_READ | ULOOP_WRITE);
}
/* clear the master and temp sets */
FD_ZERO(&used_fds);
FD_ZERO(&read_fds);
static void uh_kill9_cb(struct uloop_timeout *t)
{
struct client *cl = container_of(t, struct client, timeout);
/* backup server descriptor set */
used_fds = serv_fds;
/* loop */
while (run)
if (!kill(cl->proc.pid, 0))
{
/* create a working copy of the used fd set */
read_fds = used_fds;
D("SRV: Client(%d) child(%d) kill(SIGKILL)...\n",
cl->fd.fd, cl->proc.pid);
/* sleep until socket activity */
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1)
kill(cl->proc.pid, SIGKILL);
}
}
static void uh_timeout_cb(struct uloop_timeout *t)
{
struct client *cl = container_of(t, struct client, timeout);
D("SRV: Client(%d) child(%d) timed out\n", cl->fd.fd, cl->proc.pid);
if (!kill(cl->proc.pid, 0))
{
D("SRV: Client(%d) child(%d) kill(SIGTERM)...\n",
cl->fd.fd, cl->proc.pid);
kill(cl->proc.pid, SIGTERM);
cl->timeout.cb = uh_kill9_cb;
uloop_timeout_set(&cl->timeout, 1000);
}
}
static void uh_client_cb(struct uloop_fd *u, unsigned int events)
{
int i;
struct client *cl;
struct config *conf;
struct http_request *req;
cl = container_of(u, struct client, fd);
conf = cl->server->conf;
D("SRV: Client(%d) enter callback\n", u->fd);
/* undispatched yet */
if (!cl->dispatched)
{
/* we have no headers yet and this was a write event, ignore... */
if (!(events & ULOOP_READ))
{
perror("select()");
exit(1);
D("SRV: Client(%d) ignoring write event before headers\n", u->fd);
return;
}
/* run through the existing connections looking for data to be read */
for (cur_fd = 0; cur_fd <= max_fd; cur_fd++)
/* attempt to receive and parse headers */
if (!(req = uh_http_header_recv(cl)))
{
/* is a socket managed by us */
if (FD_ISSET(cur_fd, &read_fds))
D("SRV: Client(%d) failed to receive header\n", u->fd);
uh_client_shutdown(cl);
return;
}
/* process expect headers */
foreach_header(i, req->headers)
{
if (strcasecmp(req->headers[i], "Expect"))
continue;
if (strcasecmp(req->headers[i+1], "100-continue"))
{
/* is one of our listen sockets */
if (FD_ISSET(cur_fd, &serv_fds))
{
/* handle new connections */
if ((new_fd = accept(cur_fd, NULL, 0)) != -1)
{
/* add to global client list */
if ((cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL)
{
#ifdef HAVE_TLS
/* setup client tls context */
if (conf->tls)
{
if (conf->tls_accept(cl) < 1)
{
fprintf(stderr,
"tls_accept failed, "
"connection dropped\n");
D("SRV: Client(%d) unknown expect header (%s)\n",
u->fd, req->headers[i+1]);
/* close client socket */
close(new_fd);
uh_http_response(cl, 417, "Precondition Failed");
uh_client_shutdown(cl);
return;
}
else
{
D("SRV: Client(%d) sending HTTP/1.1 100 Continue\n", u->fd);
/* remove from global client list */
uh_client_remove(new_fd);
continue;
}
}
#endif
/* add client socket to global fdset */
FD_SET(new_fd, &used_fds);
fd_cloexec(new_fd);
max_fd = max(max_fd, new_fd);
}
/* insufficient resources */
else
{
fprintf(stderr,
"uh_client_add(): "
"Cannot allocate memory\n");
close(new_fd);
}
}
}
/* is a client socket */
else
{
if (!(cl = uh_client_lookup(cur_fd)))
{
/* this should not happen! */
fprintf(stderr,
"uh_client_lookup(): No entry for fd %i!\n",
cur_fd);
goto cleanup;
}
/* parse message header */
if ((req = uh_http_header_recv(cl)) != NULL)
{
/* RFC1918 filtering required? */
if (conf->rfc1918_filter &&
sa_rfc1918(&cl->peeraddr) &&
!sa_rfc1918(&cl->servaddr))
{
uh_http_sendhf(cl, 403, "Forbidden",
"Rejected request from RFC1918 IP "
"to public server address");
}
else
#ifdef HAVE_LUA
/* Lua request? */
if (conf->lua_state &&
uh_path_match(conf->lua_prefix, req->url))
{
conf->lua_request(cl, req, conf->lua_state);
}
else
#endif
/* dispatch request */
if ((pin = uh_path_lookup(cl, req->url)) != NULL)
{
/* auth ok? */
if (!pin->redirected && uh_auth_check(cl, req, pin))
uh_dispatch_request(cl, req, pin);
}
/* 404 */
else
{
/* Try to invoke an error handler */
pin = uh_path_lookup(cl, conf->error_handler);
if (pin && uh_auth_check(cl, req, pin))
{
req->redirect_status = 404;
uh_dispatch_request(cl, req, pin);
}
else
{
uh_http_sendhf(cl, 404, "Not Found",
"No such file or directory");
}
}
}
#ifdef HAVE_TLS
/* free client tls context */
if (conf->tls)
conf->tls_close(cl);
#endif
cleanup:
/* close client socket */
close(cur_fd);
FD_CLR(cur_fd, &used_fds);
/* remove from global client list */
uh_client_remove(cur_fd);
}
uh_http_sendf(cl, NULL, "HTTP/1.1 100 Continue\r\n\r\n");
cl->httpbuf.len = 0; /* client will re-send the body */
break;
}
}
/* RFC1918 filtering */
if (conf->rfc1918_filter &&
sa_rfc1918(&cl->peeraddr) && !sa_rfc1918(&cl->servaddr))
{
uh_http_sendhf(cl, 403, "Forbidden",
"Rejected request from RFC1918 IP "
"to public server address");
uh_client_shutdown(cl);
return;
}
/* dispatch request */
if (!uh_dispatch_request(cl, req))
{
D("SRV: Client(%d) failed to dispach request\n", u->fd);
uh_client_shutdown(cl);
return;
}
/* request handler spawned a child, register handler */
if (cl->proc.pid)
{
D("SRV: Client(%d) child(%d) spawned\n", u->fd, cl->proc.pid);
cl->proc.cb = uh_child_cb;
uloop_process_add(&cl->proc);
cl->timeout.cb = uh_timeout_cb;
uloop_timeout_set(&cl->timeout, conf->script_timeout * 1000);
}
/* header processing complete */
D("SRV: Client(%d) dispatched\n", u->fd);
cl->dispatched = true;
return;
}
#ifdef HAVE_LUA
/* destroy the Lua state */
if (conf->lua_state != NULL)
conf->lua_close(conf->lua_state);
#endif
if (!cl->cb(cl))
{
D("SRV: Client(%d) response callback signalized EOF\n", u->fd);
uh_client_shutdown(cl);
return;
}
}
#ifdef HAVE_TLS
@@ -710,9 +773,6 @@ int main (int argc, char **argv)
struct sigaction sa;
struct config conf;
/* signal mask */
sigset_t ss;
/* maximum file descriptor number */
int cur_fd, max_fd = 0;
@@ -736,25 +796,17 @@ int main (int argc, char **argv)
FD_ZERO(&serv_fds);
/* handle SIGPIPE, SIGINT, SIGTERM, SIGCHLD */
/* handle SIGPIPE, SIGINT, SIGTERM */
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
sa.sa_handler = uh_sigchld;
sigaction(SIGCHLD, &sa, NULL);
sa.sa_handler = uh_sigterm;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
/* defer SIGCHLD */
sigemptyset(&ss);
sigaddset(&ss, SIGCHLD);
sigprocmask(SIG_BLOCK, &ss, NULL);
/* prepare addrinfo hints */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
@@ -765,9 +817,10 @@ int main (int argc, char **argv)
memset(&conf, 0, sizeof(conf));
memset(bind, 0, sizeof(bind));
uloop_init();
while ((opt = getopt(argc, argv,
"fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:i:t:T:A:")) > 0)
"fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:n:x:i:t:T:A:u:U:")) > 0)
{
switch(opt)
{
@@ -894,6 +947,10 @@ int main (int argc, char **argv)
conf.rfc1918_filter = 1;
break;
case 'n':
conf.max_requests = atoi(optarg);
break;
#ifdef HAVE_CGI
/* cgi prefix */
case 'x':
@@ -928,6 +985,18 @@ int main (int argc, char **argv)
break;
#endif
#ifdef HAVE_UBUS
/* ubus prefix */
case 'u':
conf.ubus_prefix = optarg;
break;
/* ubus socket */
case 'U':
conf.ubus_socket = optarg;
break;
#endif
#if defined(HAVE_CGI) || defined(HAVE_LUA)
/* script timeout */
case 't':
@@ -1002,16 +1071,21 @@ int main (int argc, char **argv)
" -S Do not follow symbolic links outside of the docroot\n"
" -D Do not allow directory listings, send 403 instead\n"
" -R Enable RFC1918 filter\n"
" -n count Maximum allowed number of concurrent requests\n"
#ifdef HAVE_LUA
" -l string URL prefix for Lua handler, default is '/lua'\n"
" -L file Lua handler script, omit to disable Lua\n"
#endif
#ifdef HAVE_UBUS
" -u string URL prefix for HTTP/JSON handler, default is '/ubus'\n"
" -U file Override ubus socket path\n"
#endif
#ifdef HAVE_CGI
" -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
" -i .ext=path Use interpreter at path for files with the given extension\n"
#endif
#if defined(HAVE_CGI) || defined(HAVE_LUA)
" -t seconds CGI and Lua script timeout in seconds, default is 60\n"
#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
" -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n"
#endif
" -T seconds Network timeout in seconds, default is 30\n"
" -d string URL decode given string\n"
@@ -1053,11 +1127,15 @@ int main (int argc, char **argv)
/* config file */
uh_config_parse(&conf);
/* default max requests */
if (conf.max_requests <= 0)
conf.max_requests = 3;
/* default network timeout */
if (conf.network_timeout <= 0)
conf.network_timeout = 30;
#if defined(HAVE_CGI) || defined(HAVE_LUA)
#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
/* default script timeout */
if (conf.script_timeout <= 0)
conf.script_timeout = 60;
@@ -1103,6 +1181,36 @@ int main (int argc, char **argv)
}
#endif
#ifdef HAVE_UBUS
/* load ubus plugin */
if (!(lib = dlopen("uhttpd_ubus.so", RTLD_LAZY | RTLD_GLOBAL)))
{
fprintf(stderr,
"Notice: Unable to load ubus plugin - disabling ubus support! "
"(Reason: %s)\n", dlerror());
}
else
{
/* resolve functions */
if (!(conf.ubus_init = dlsym(lib, "uh_ubus_init")) ||
!(conf.ubus_close = dlsym(lib, "uh_ubus_close")) ||
!(conf.ubus_request = dlsym(lib, "uh_ubus_request")))
{
fprintf(stderr,
"Error: Failed to lookup required symbols "
"in ubus plugin: %s\n", dlerror()
);
exit(1);
}
/* default ubus prefix */
if (!conf.ubus_prefix)
conf.ubus_prefix = "/ubus";
conf.ubus_state = conf.ubus_init(&conf);
}
#endif
/* fork (if not disabled) */
if (!nofork)
{
@@ -1134,7 +1242,7 @@ int main (int argc, char **argv)
}
/* server main loop */
uh_mainloop(&conf, serv_fds, max_fd);
uloop_run();
#ifdef HAVE_LUA
/* destroy the Lua state */
@@ -1142,5 +1250,11 @@ int main (int argc, char **argv)
conf.lua_close(conf.lua_state);
#endif
#ifdef HAVE_UBUS
/* destroy the ubus state */
if (conf.ubus_state != NULL)
conf.ubus_close(conf.ubus_state);
#endif
return 0;
}