From 8b4e17bfbc92205fbeaeffee3783e5eceb65d0bf Mon Sep 17 00:00:00 2001 From: jow Date: Mon, 28 May 2012 00:52:24 +0000 Subject: [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 --- package/uhttpd/src/uhttpd-lua.c | 608 +++++++++++++++++++++------------------- 1 file changed, 317 insertions(+), 291 deletions(-) (limited to 'package/uhttpd/src/uhttpd-lua.c') diff --git a/package/uhttpd/src/uhttpd-lua.c b/package/uhttpd/src/uhttpd-lua.c index ea6f26cc9..7d602f7c5 100644 --- a/package/uhttpd/src/uhttpd-lua.c +++ b/package/uhttpd/src/uhttpd-lua.c @@ -1,7 +1,7 @@ /* * uhttpd - Tiny single-threaded httpd - Lua handler * - * Copyright (C) 2010 Jo-Philipp Wich + * Copyright (C) 2010-2012 Jo-Philipp Wich * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,54 +24,59 @@ static int uh_lua_recv(lua_State *L) { size_t length; + char buffer[UH_LIMIT_MSGHEAD]; - ssize_t rlen = 0; - fd_set reader; - struct timeval timeout; + + int to = 1; + int fd = fileno(stdin); + int rlen = 0; length = luaL_checknumber(L, 1); if ((length > 0) && (length <= sizeof(buffer))) { - FD_ZERO(&reader); - FD_SET(fileno(stdin), &reader); - - /* fail after 0.1s */ - timeout.tv_sec = 0; - timeout.tv_usec = 100000; + /* receive data */ + rlen = uh_raw_recv(fd, buffer, length, to); - /* check whether fd is readable */ - if (select(fileno(stdin) + 1, &reader, NULL, NULL, &timeout) > 0) + /* data read */ + if (rlen > 0) { - /* receive data */ - rlen = read(fileno(stdin), buffer, length); lua_pushnumber(L, rlen); + lua_pushlstring(L, buffer, rlen); + return 2; + } - if (rlen > 0) - { - lua_pushlstring(L, buffer, rlen); - return 2; - } - + /* eof */ + else if (rlen == 0) + { + lua_pushnumber(L, 0); return 1; } /* no, timeout and actually no data */ - lua_pushnumber(L, -2); - return 1; + else + { + lua_pushnumber(L, -1); + return 1; + } } /* parameter error */ - lua_pushnumber(L, -3); + lua_pushnumber(L, -2); return 1; } static int uh_lua_send_common(lua_State *L, int chunked) { size_t length; - const char *buffer; + char chunk[16]; - ssize_t slen = 0; + const char *buffer; + + int rv; + int to = 1; + int fd = fileno(stdout); + int slen = 0; buffer = luaL_checklstring(L, 1, &length); @@ -80,20 +85,27 @@ static int uh_lua_send_common(lua_State *L, int chunked) if (length > 0) { snprintf(chunk, sizeof(chunk), "%X\r\n", length); - slen = write(fileno(stdout), chunk, strlen(chunk)); - slen += write(fileno(stdout), buffer, length); - slen += write(fileno(stdout), "\r\n", 2); + + ensure_out(rv = uh_raw_send(fd, chunk, strlen(chunk), to)); + slen += rv; + + ensure_out(rv = uh_raw_send(fd, buffer, length, to)); + slen += rv; + + ensure_out(rv = uh_raw_send(fd, "\r\n", 2, to)); + slen += rv; } else { - slen = write(fileno(stdout), "0\r\n\r\n", 5); + slen = uh_raw_send(fd, "0\r\n\r\n", 5, to); } } else { - slen = write(fileno(stdout), buffer, length); + slen = uh_raw_send(fd, buffer, length, to); } +out: lua_pushnumber(L, slen); return 1; } @@ -118,8 +130,8 @@ static int uh_lua_str2str(lua_State *L, int (*xlate_func) (char *, int, const ch inbuf = luaL_checklstring(L, 1, &inlen); outlen = (* xlate_func)(outbuf, sizeof(outbuf), inbuf, inlen); if (outlen < 0) - luaL_error( L, "%s on URL-encode codec", - (outlen==-1) ? "buffer overflow" : "malformed string" ); + luaL_error(L, "%s on URL-encode codec", + (outlen==-1) ? "buffer overflow" : "malformed string"); lua_pushlstring(L, outbuf, outlen); return 1; @@ -181,17 +193,17 @@ lua_State * uh_lua_init(const struct config *conf) { case LUA_ERRSYNTAX: fprintf(stderr, - "Lua handler contains syntax errors, unable to continue\n"); + "Lua handler contains syntax errors, unable to continue\n"); exit(1); case LUA_ERRMEM: fprintf(stderr, - "Lua handler ran out of memory, unable to continue\n"); + "Lua handler ran out of memory, unable to continue\n"); exit(1); case LUA_ERRFILE: fprintf(stderr, - "Lua cannot open the handler script, unable to continue\n"); + "Lua cannot open the handler script, unable to continue\n"); exit(1); default: @@ -201,17 +213,17 @@ lua_State * uh_lua_init(const struct config *conf) case LUA_ERRRUN: err_str = luaL_checkstring(L, -1); fprintf(stderr, - "Lua handler had runtime error, unable to continue\n" - "Error: %s\n", err_str - ); + "Lua handler had runtime error, " + "unable to continue\n" + "Error: %s\n", err_str); exit(1); case LUA_ERRMEM: err_str = luaL_checkstring(L, -1); fprintf(stderr, - "Lua handler ran out of memory, unable to continue\n" - "Error: %s\n", err_str - ); + "Lua handler ran out of memory, " + "unable to continue\n" + "Error: %s\n", err_str); exit(1); default: @@ -221,7 +233,8 @@ lua_State * uh_lua_init(const struct config *conf) if (! lua_isfunction(L, -1)) { fprintf(stderr, - "Lua handler provides no " UH_LUA_CALLBACK "(), unable to continue\n"); + "Lua handler provides no "UH_LUA_CALLBACK"(), " + "unable to continue\n"); exit(1); } @@ -235,12 +248,107 @@ lua_State * uh_lua_init(const struct config *conf) return L; } -void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L) +static void uh_lua_shutdown(struct uh_lua_state *state) { - int i, data_sent; - int content_length = 0; - int buflen = 0; - int fd_max = 0; + close(state->rfd); + close(state->wfd); + free(state); +} + +static bool uh_lua_socket_cb(struct client *cl) +{ + int len; + char buf[UH_LIMIT_MSGHEAD]; + + struct uh_lua_state *state = (struct uh_lua_state *)cl->priv; + + /* there is unread post data waiting */ + while (state->content_length > 0) + { + /* remaining data in http head buffer ... */ + if (state->cl->httpbuf.len > 0) + { + len = min(state->content_length, state->cl->httpbuf.len); + + D("Lua: Child(%d) feed %d HTTP buffer bytes\n", + state->cl->proc.pid, len); + + memcpy(buf, state->cl->httpbuf.ptr, len); + + state->cl->httpbuf.len -= len; + state->cl->httpbuf.ptr += len; + } + + /* read it from socket ... */ + else + { + len = uh_tcp_recv(state->cl, buf, + min(state->content_length, sizeof(buf))); + + if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + break; + + D("Lua: Child(%d) feed %d/%d TCP socket bytes\n", + state->cl->proc.pid, len, + min(state->content_length, sizeof(buf))); + } + + if (len) + state->content_length -= len; + else + state->content_length = 0; + + /* ... write to CGI process */ + len = uh_raw_send(state->wfd, buf, len, + cl->server->conf->script_timeout); + } + + /* try to read data from child */ + while ((len = uh_raw_recv(state->rfd, buf, sizeof(buf), -1)) > 0) + { + /* pass through buffer to socket */ + D("Lua: Child(%d) relaying %d normal bytes\n", state->cl->proc.pid, len); + ensure_out(uh_tcp_send(state->cl, buf, len)); + state->data_sent = true; + } + + /* child has been marked dead by timeout or child handler, bail out */ + if (false && cl->dead) + { + D("Lua: Child(%d) is marked dead, returning\n", state->cl->proc.pid); + goto out; + } + + if ((len == 0) || + ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1))) + { + D("Lua: Child(%d) presumed dead [%s]\n", + state->cl->proc.pid, strerror(errno)); + + goto out; + } + + return true; + +out: + if (!state->data_sent) + { + if (state->cl->timeout.pending) + uh_http_sendhf(state->cl, 502, "Bad Gateway", + "The Lua process did not produce any response\n"); + else + uh_http_sendhf(state->cl, 504, "Gateway Timeout", + "The Lua process took too long to produce a " + "response\n"); + } + + uh_lua_shutdown(state); + return false; +} + +bool uh_lua_request(struct client *cl, lua_State *L) +{ + int i; char *query_string; const char *prefix = cl->server->conf->lua_prefix; const char *err_str = NULL; @@ -248,325 +356,243 @@ void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L) int rfd[2] = { 0, 0 }; int wfd[2] = { 0, 0 }; - char buf[UH_LIMIT_MSGHEAD]; - pid_t child; - fd_set reader; - fd_set writer; + struct uh_lua_state *state; + struct http_request *req = &cl->request; - struct sigaction sa; - struct timeval timeout; + int content_length = cl->httpbuf.len; + /* allocate state */ + if (!(state = malloc(sizeof(*state)))) + { + uh_client_error(cl, 500, "Internal Server Error", "Out of memory"); + return false; + } + /* spawn pipes for me->child, child->me */ if ((pipe(rfd) < 0) || (pipe(wfd) < 0)) { - uh_http_sendhf(cl, 500, "Internal Server Error", - "Failed to create pipe: %s", strerror(errno)); - if (rfd[0] > 0) close(rfd[0]); if (rfd[1] > 0) close(rfd[1]); if (wfd[0] > 0) close(wfd[0]); if (wfd[1] > 0) close(wfd[1]); - return; + uh_client_error(cl, 500, "Internal Server Error", + "Failed to create pipe: %s", strerror(errno)); + + return false; } switch ((child = fork())) { - case -1: - uh_http_sendhf(cl, 500, "Internal Server Error", - "Failed to fork child: %s", strerror(errno)); - break; + case -1: + uh_client_error(cl, 500, "Internal Server Error", + "Failed to fork child: %s", strerror(errno)); - case 0: - /* restore SIGTERM */ - sa.sa_flags = 0; - sa.sa_handler = SIG_DFL; - sigemptyset(&sa.sa_mask); - sigaction(SIGTERM, &sa, NULL); + return false; - /* close loose pipe ends */ - close(rfd[0]); - close(wfd[1]); + case 0: +#ifdef DEBUG + sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0")); +#endif - /* patch stdout and stdin to pipes */ - dup2(rfd[1], 1); - dup2(wfd[0], 0); + /* close loose pipe ends */ + close(rfd[0]); + close(wfd[1]); - /* put handler callback on stack */ - lua_getglobal(L, UH_LUA_CALLBACK); + /* patch stdout and stdin to pipes */ + dup2(rfd[1], 1); + dup2(wfd[0], 0); - /* build env table */ - lua_newtable(L); + /* avoid leaking our pipe into child-child processes */ + fd_cloexec(rfd[1]); + fd_cloexec(wfd[0]); - /* request method */ - switch(req->method) - { - case UH_HTTP_MSG_GET: - lua_pushstring(L, "GET"); - break; + /* put handler callback on stack */ + lua_getglobal(L, UH_LUA_CALLBACK); - case UH_HTTP_MSG_HEAD: - lua_pushstring(L, "HEAD"); - break; + /* build env table */ + lua_newtable(L); - case UH_HTTP_MSG_POST: - lua_pushstring(L, "POST"); - break; - } + /* request method */ + switch(req->method) + { + case UH_HTTP_MSG_GET: + lua_pushstring(L, "GET"); + break; - lua_setfield(L, -2, "REQUEST_METHOD"); + case UH_HTTP_MSG_HEAD: + lua_pushstring(L, "HEAD"); + break; - /* request url */ - lua_pushstring(L, req->url); - lua_setfield(L, -2, "REQUEST_URI"); + case UH_HTTP_MSG_POST: + lua_pushstring(L, "POST"); + break; + } - /* script name */ - lua_pushstring(L, cl->server->conf->lua_prefix); - lua_setfield(L, -2, "SCRIPT_NAME"); + lua_setfield(L, -2, "REQUEST_METHOD"); - /* query string, path info */ - if ((query_string = strchr(req->url, '?')) != NULL) - { - lua_pushstring(L, query_string + 1); - lua_setfield(L, -2, "QUERY_STRING"); + /* request url */ + lua_pushstring(L, req->url); + lua_setfield(L, -2, "REQUEST_URI"); - if ((int)(query_string - req->url) > strlen(prefix)) - { - lua_pushlstring(L, - &req->url[strlen(prefix)], - (int)(query_string - req->url) - strlen(prefix) - ); + /* script name */ + lua_pushstring(L, cl->server->conf->lua_prefix); + lua_setfield(L, -2, "SCRIPT_NAME"); - lua_setfield(L, -2, "PATH_INFO"); - } - } - else if (strlen(req->url) > strlen(prefix)) + /* query string, path info */ + if ((query_string = strchr(req->url, '?')) != NULL) + { + lua_pushstring(L, query_string + 1); + lua_setfield(L, -2, "QUERY_STRING"); + + if ((int)(query_string - req->url) > strlen(prefix)) { - lua_pushstring(L, &req->url[strlen(prefix)]); + lua_pushlstring(L, + &req->url[strlen(prefix)], + (int)(query_string - req->url) - strlen(prefix) + ); + lua_setfield(L, -2, "PATH_INFO"); } + } + else if (strlen(req->url) > strlen(prefix)) + { + lua_pushstring(L, &req->url[strlen(prefix)]); + lua_setfield(L, -2, "PATH_INFO"); + } - /* http protcol version */ - lua_pushnumber(L, floor(req->version * 10) / 10); - lua_setfield(L, -2, "HTTP_VERSION"); + /* http protcol version */ + lua_pushnumber(L, floor(req->version * 10) / 10); + lua_setfield(L, -2, "HTTP_VERSION"); - if (req->version > 1.0) - lua_pushstring(L, "HTTP/1.1"); - else - lua_pushstring(L, "HTTP/1.0"); + if (req->version > 1.0) + lua_pushstring(L, "HTTP/1.1"); + else + lua_pushstring(L, "HTTP/1.0"); - lua_setfield(L, -2, "SERVER_PROTOCOL"); + lua_setfield(L, -2, "SERVER_PROTOCOL"); - /* address information */ - lua_pushstring(L, sa_straddr(&cl->peeraddr)); - lua_setfield(L, -2, "REMOTE_ADDR"); + /* address information */ + lua_pushstring(L, sa_straddr(&cl->peeraddr)); + lua_setfield(L, -2, "REMOTE_ADDR"); - lua_pushinteger(L, sa_port(&cl->peeraddr)); - lua_setfield(L, -2, "REMOTE_PORT"); + lua_pushinteger(L, sa_port(&cl->peeraddr)); + lua_setfield(L, -2, "REMOTE_PORT"); - lua_pushstring(L, sa_straddr(&cl->servaddr)); - lua_setfield(L, -2, "SERVER_ADDR"); + lua_pushstring(L, sa_straddr(&cl->servaddr)); + lua_setfield(L, -2, "SERVER_ADDR"); - lua_pushinteger(L, sa_port(&cl->servaddr)); - lua_setfield(L, -2, "SERVER_PORT"); + lua_pushinteger(L, sa_port(&cl->servaddr)); + lua_setfield(L, -2, "SERVER_PORT"); - /* essential env vars */ - foreach_header(i, req->headers) + /* essential env vars */ + foreach_header(i, req->headers) + { + if (!strcasecmp(req->headers[i], "Content-Length")) { - if (!strcasecmp(req->headers[i], "Content-Length")) - { - lua_pushnumber(L, atoi(req->headers[i+1])); - lua_setfield(L, -2, "CONTENT_LENGTH"); - } - else if (!strcasecmp(req->headers[i], "Content-Type")) - { - lua_pushstring(L, req->headers[i+1]); - lua_setfield(L, -2, "CONTENT_TYPE"); - } + content_length = atoi(req->headers[i+1]); } - - /* misc. headers */ - lua_newtable(L); - - foreach_header(i, req->headers) + else if (!strcasecmp(req->headers[i], "Content-Type")) { - if( strcasecmp(req->headers[i], "Content-Length") && - strcasecmp(req->headers[i], "Content-Type") - ) { - lua_pushstring(L, req->headers[i+1]); - lua_setfield(L, -2, req->headers[i]); - } + lua_pushstring(L, req->headers[i+1]); + lua_setfield(L, -2, "CONTENT_TYPE"); } + } - lua_setfield(L, -2, "headers"); + lua_pushnumber(L, content_length); + lua_setfield(L, -2, "CONTENT_LENGTH"); + /* misc. headers */ + lua_newtable(L); - /* call */ - switch (lua_pcall(L, 1, 0, 0)) + foreach_header(i, req->headers) + { + if( strcasecmp(req->headers[i], "Content-Length") && + strcasecmp(req->headers[i], "Content-Type")) { - case LUA_ERRMEM: - case LUA_ERRRUN: - err_str = luaL_checkstring(L, -1); - - if (! err_str) - err_str = "Unknown error"; - - printf( - "HTTP/%.1f 500 Internal Server Error\r\n" - "Connection: close\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: %i\r\n\r\n" - "Lua raised a runtime error:\n %s\n", - req->version, 31 + strlen(err_str), err_str - ); - - break; - - default: - break; + lua_pushstring(L, req->headers[i+1]); + lua_setfield(L, -2, req->headers[i]); } + } - close(wfd[0]); - close(rfd[1]); - exit(0); + lua_setfield(L, -2, "headers"); - break; - /* parent; handle I/O relaying */ - default: - /* close unneeded pipe ends */ - close(rfd[1]); - close(wfd[0]); + /* call */ + switch (lua_pcall(L, 1, 0, 0)) + { + case LUA_ERRMEM: + case LUA_ERRRUN: + err_str = luaL_checkstring(L, -1); - /* max watch fd */ - fd_max = max(rfd[0], wfd[1]) + 1; + if (! err_str) + err_str = "Unknown error"; - /* find content length */ - if (req->method == UH_HTTP_MSG_POST) - { - foreach_header(i, req->headers) - { - if (! strcasecmp(req->headers[i], "Content-Length")) - { - content_length = atoi(req->headers[i+1]); - break; - } - } - } + printf("HTTP/%.1f 500 Internal Server Error\r\n" + "Connection: close\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %i\r\n\r\n" + "Lua raised a runtime error:\n %s\n", + req->version, 31 + strlen(err_str), err_str); + break; -#define ensure(x) \ - do { if (x < 0) goto out; } while(0) + default: + break; + } - data_sent = 0; + close(wfd[0]); + close(rfd[1]); + exit(0); - timeout.tv_sec = cl->server->conf->script_timeout; - timeout.tv_usec = 0; + break; - /* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */ - while (1) - { - FD_ZERO(&reader); - FD_ZERO(&writer); + /* parent; handle I/O relaying */ + default: + memset(state, 0, sizeof(*state)); - FD_SET(rfd[0], &reader); - FD_SET(wfd[1], &writer); + state->cl = cl; + state->cl->proc.pid = child; - /* wait until we can read or write or both */ - if (select_intr(fd_max, &reader, - (content_length > -1) ? &writer : NULL, - NULL, - (data_sent < 1) ? &timeout : NULL) > 0) - { - /* ready to write to Lua child */ - if (FD_ISSET(wfd[1], &writer)) - { - /* there is unread post data waiting */ - if (content_length > 0) - { - /* read it from socket ... */ - if ((buflen = uh_tcp_recv(cl, buf, min(content_length, sizeof(buf)))) > 0) - { - /* ... and write it to child's stdin */ - if (write(wfd[1], buf, buflen) < 0) - perror("write()"); - - content_length -= buflen; - } - - /* unexpected eof! */ - else - { - if (write(wfd[1], "", 0) < 0) - perror("write()"); - - content_length = 0; - } - } - - /* there is no more post data, close pipe to child's stdin */ - else if (content_length > -1) - { - close(wfd[1]); - content_length = -1; - } - } + /* close unneeded pipe ends */ + close(rfd[1]); + close(wfd[0]); - /* ready to read from Lua child */ - if (FD_ISSET(rfd[0], &reader)) - { - /* read data from child ... */ - if ((buflen = read(rfd[0], buf, sizeof(buf))) > 0) - { - /* pass through buffer to socket */ - ensure(uh_tcp_send(cl, buf, buflen)); - data_sent = 1; - } - - /* looks like eof from child */ - else - { - /* error? */ - if (!data_sent) - uh_http_sendhf(cl, 500, "Internal Server Error", - "The Lua child did not produce any response"); - - break; - } - } - } + D("Lua: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]); - /* timeout exceeded or interrupted by SIGCHLD */ - else - { - if ((errno != EINTR) && ! data_sent) - { - ensure(uh_http_sendhf(cl, 504, "Gateway Timeout", - "The Lua script took too long to produce " - "a response")); - } + state->content_length = cl->httpbuf.len; + /* find content length */ + if (req->method == UH_HTTP_MSG_POST) + { + foreach_header(i, req->headers) + { + if (!strcasecmp(req->headers[i], "Content-Length")) + { + state->content_length = atoi(req->headers[i+1]); break; } } + } - out: - close(rfd[0]); - close(wfd[1]); + state->rfd = rfd[0]; + fd_nonblock(state->rfd); - if (!kill(child, 0)) - { - kill(child, SIGTERM); - waitpid(child, NULL, 0); - } + state->wfd = wfd[1]; + fd_nonblock(state->wfd); - break; + cl->cb = uh_lua_socket_cb; + cl->priv = state; + + break; } + + return true; } void uh_lua_close(lua_State *L) -- cgit v1.2.3