Hi plugins/cgi/ABOUT | 1 + plugins/cgi/Makefile.in | 22 +++ plugins/cgi/README | 8 + plugins/cgi/cgi.c | 333 +++++++++++++++++++++++++++++++++++++++++++++ plugins/cgi/conf/cgi.conf | 8 + 5 files changed, 372 insertions(+), 0 deletions(-)
Testing with a "hello" shell script, busybox httpd does about 3400 trans/sec. Monkey with default config (5 workers) does 2200 with the same file. That is, CGI speed for this particular file is about 7% of the same file being served as a static file (29k trans/sec). For shits and giggles, a page sending phpinfo() got 500 trans/sec with Monkey. - Lauri PS: This patch depends on the previous epoll_del one, without it things are quite unstable, with monkey closing random file descriptors.
>From a915f1bb08ee9cedfc5f3942b8bac5de25c092c1 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen <[email protected]> Date: Wed, 13 Jun 2012 18:07:50 +0300 Subject: [PATCH] plugins: New plugin: CGI Signed-off-by: Lauri Kasanen <[email protected]> --- plugins/cgi/ABOUT | 1 + plugins/cgi/Makefile.in | 22 +++ plugins/cgi/README | 8 + plugins/cgi/cgi.c | 333 +++++++++++++++++++++++++++++++++++++++++++++ plugins/cgi/conf/cgi.conf | 8 + 5 files changed, 372 insertions(+), 0 deletions(-) create mode 100644 plugins/cgi/ABOUT create mode 100644 plugins/cgi/Makefile.in create mode 100644 plugins/cgi/README create mode 100644 plugins/cgi/cgi.c create mode 100644 plugins/cgi/conf/cgi.conf diff --git a/plugins/cgi/ABOUT b/plugins/cgi/ABOUT new file mode 100644 index 0000000..100b938 --- /dev/null +++ b/plugins/cgi/ABOUT @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/plugins/cgi/Makefile.in b/plugins/cgi/Makefile.in new file mode 100644 index 0000000..97a9cd8 --- /dev/null +++ b/plugins/cgi/Makefile.in @@ -0,0 +1,22 @@ +_PATH = $(patsubst $(monkey_root)/%, %, $(CURDIR)) +CC = @echo " CC $(_PATH)/$@"; $CC +CC_QUIET= @echo -n; $CC +CFLAGS = $CFLAGS +LDFLAGS = $LDFLAGS +DEFS = $DEFS +INCDIR = ../../src/include +OBJECTS = cgi.o + +all: monkey-cgi.so + +-include $(OBJECTS:.o=.d) + +monkey-cgi.so: $(OBJECTS) + $(CC) $(CFLAGS) $(LDFLAGS) $(DEFS) -shared -o $@ $^ -lc + +.c.o: + $(CC) $(CFLAGS) $(DEFS) -I$(INCDIR) -fPIC -c $< + $(CC_QUIET) -MM -MP $(CFLAGS) $(DEFS) -I$(INCDIR) $*.c -o $*.d > /dev/null &2>&1 + +clean: + rm -rf *.[od] *~ *.*so* diff --git a/plugins/cgi/README b/plugins/cgi/README new file mode 100644 index 0000000..183b5a3 --- /dev/null +++ b/plugins/cgi/README @@ -0,0 +1,8 @@ +CGI plugin +========== + +This plugin provides plain old CGI, ie sh/perl/C/C++/etc +one-process-per-request architecture. + +It's slow and using FastCGI instead is recommended, but building CGI +apps is rather easy and fast. diff --git a/plugins/cgi/cgi.c b/plugins/cgi/cgi.c new file mode 100644 index 0000000..e9871a3 --- /dev/null +++ b/plugins/cgi/cgi.c @@ -0,0 +1,333 @@ +/* Monkey HTTP Daemon + * ------------------ + * Copyright (C) 2012, Lauri Kasanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <regex.h> +#include <unistd.h> +#include <signal.h> +#include <libgen.h> + +#include "MKPlugin.h" + +MONKEY_PLUGIN("cgi", /* shortname */ + "CGI handler", /* name */ + VERSION, /* version */ + MK_PLUGIN_STAGE_30); /* hooks */ + +static regex_t match_regex; + +enum { + PATHLEN = 1024, + SHORTLEN = 30 +}; + +struct post_t { + int fd; + void *buf; + unsigned long len; +}; + +static int swrite(const int fd, const void *buf, const size_t count) +{ + ssize_t pos = count, ret = 0; + + while (pos > 0 && ret >= 0) { + ret = write(fd, buf, pos); + pos -= ret; + buf += ret; + } + + if (ret < 0) + return 0; + + return 1; +} + +static void *cgi_write_post(void *p) +{ + const struct post_t * const in = p; + + swrite(in->fd, in->buf, in->len); + close(in->fd); + + return NULL; +} + +static int do_cgi(const char * const __restrict__ file, const char * const __restrict__ url, + int socket, const struct session_request * const sr) +{ + char *env[30]; + + /* Unchanging env vars */ + env[0] = "PATH_INFO="; + env[1] = "SERVER_SOFTWARE=Monkey/CGI"; + env[2] = "SERVER_PROTOCOL=HTTP/1.0"; + env[3] = "GATEWAY_INTERFACE=CGI/1.1"; + env[4] = "HTTP_HOST="; + env[5] = "REDIRECT_STATUS=200"; + const int env_start = 6; + + /* Dynamic env vars */ + + unsigned short envpos = env_start; + + char method[SHORTLEN]; + char *query = NULL; + char request_uri[PATHLEN]; + char script_filename[PATHLEN]; + char script_name[PATHLEN]; + char query_string[PATHLEN]; + char remote_addr[INET6_ADDRSTRLEN+SHORTLEN]; + char tmpaddr[INET6_ADDRSTRLEN], *ptr = tmpaddr; + char remote_port[SHORTLEN]; + char content_length[SHORTLEN]; + char content_type[SHORTLEN]; + + snprintf(method, SHORTLEN, "REQUEST_METHOD=%s", sr->method_p.data); + env[envpos++] = method; + + if (sr->query_string.len) { + query = mk_api->mem_alloc_z(sr->query_string.len + 1); + memcpy(query, sr->query_string.data, sr->query_string.len); + snprintf(request_uri, PATHLEN, "REQUEST_URI=%s?%s", url, query); + } + else { + snprintf(request_uri, PATHLEN, "REQUEST_URI=%s", url); + } + env[envpos++] = request_uri; + + snprintf(script_filename, PATHLEN, "SCRIPT_FILENAME=%s", file); + env[envpos++] = script_filename; + + snprintf(script_name, PATHLEN, "SCRIPT_NAME=%s", url); + env[envpos++] = script_name; + + if (query) { + snprintf(query_string, PATHLEN, "QUERY_STRING=%s", query); + env[envpos++] = query_string; + } + + unsigned long len; + if (mk_api->socket_ip_str(socket, &ptr, INET6_ADDRSTRLEN, &len) < 0) + tmpaddr[0] = '\0'; + snprintf(remote_addr, INET6_ADDRSTRLEN+SHORTLEN, "REMOTE_ADDR=%s", tmpaddr); + env[envpos++] = remote_addr; + + snprintf(remote_port, SHORTLEN, "REMOTE_PORT=%ld", sr->port); + env[envpos++] = remote_port; + + if (sr->data.len) { + snprintf(content_length, SHORTLEN, "CONTENT_LENGTH=%lu", sr->data.len); + env[envpos++] = content_length; + } + + if (sr->content_type.len) { + snprintf(content_type, SHORTLEN, "CONTENT_TYPE=%s", sr->content_type.data); + env[envpos++] = content_type; + } + + + /* Must be NULL-terminated */ + env[envpos] = NULL; + + /* pipes, from monkey's POV */ + int writepipe[2], readpipe[2]; + if (pipe(writepipe) || pipe(readpipe)) { + mk_err("Failed to create pipe"); + return 403; + } + + pid_t pid = vfork(); + if (pid < 0) { + mk_err("Failed to fork"); + return 403; + } + + /* Child */ + if (pid == 0) { + close(writepipe[1]); + close(readpipe[0]); + + /* Our stdin is the read end of monkey's writing */ + if (dup2(writepipe[0], 0) < 0) { + mk_err("dup2 failed"); + _exit(1); + } + close(writepipe[0]); + + /* Our stdout is the write end of monkey's reading */ + if (dup2(readpipe[1], 1) < 0) { + mk_err("dup2 failed"); + _exit(1); + } + close(readpipe[1]); + + char *argv[2] = { NULL }; + + char *tmp = strdup(file); + chdir(dirname(tmp)); + + char *tmp2 = strdup(file); + argv[0] = basename(tmp2); + + /* Restore signals for the child */ + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + execve(file, argv, env); + + /* Exec failed, return */ + _exit(1); + } + + /* Yay me */ + close(writepipe[0]); + close(readpipe[1]); + + mk_api->socket_cork_flag(socket, TCP_CORK_ON); + + /* If we have POST data to write, spawn a thread to do that */ + if (sr->data.len) { + struct post_t p; + p.fd = writepipe[1]; + p.buf = sr->data.data; + p.len = sr->data.len; + + pthread_t tid; + + pthread_create(&tid, NULL, cgi_write_post, &p); + } + + char buf[PATHLEN], *outptr; + long count; + char headers_done = 0; + + while ((count = read(readpipe[0], buf, PATHLEN)) > 0) { + outptr = buf; + + /* We have to check the headers are OK */ + if (!headers_done) { + if (count >= 8 && memcmp(buf, "Status: ", 8) == 0) { + swrite(socket, "HTTP/1.0 ", 9); + outptr += 8; + count -= 8; + headers_done = 1; + } + else if (count >= 4) { + if (memcmp(buf, "HTTP", 4) != 0) { + swrite(socket, "HTTP/1.0 200 OK\r\n", sizeof("HTTP/1.0 200 OK\r\n") - 1); + } + headers_done = 1; + } + } + + swrite(socket, outptr, count); + } + + close(writepipe[1]); + close(readpipe[0]); + + mk_api->socket_cork_flag(socket, TCP_CORK_OFF); + + return 200; +} + +static void cgi_read_config(const char * const path) +{ + char *file = NULL; + unsigned long len; + struct mk_config *conf; + struct mk_config_section *section; + + mk_api->str_build(&file, &len, "%scgi.conf", path); + conf = mk_api->config_create(file); + section = mk_api->config_section_get(conf, "CGI"); + + if (section) { + char *match = mk_api->config_section_getval(section, "Match", MK_CONFIG_VAL_STR); + if (match) { +// printf("Got match %s\n", match); + + char *p = match; + while (*p) { + if (*p == ' ') *p = '|'; + p++; + } + +// printf("Got match %s\n", match); + int ret = regcomp(&match_regex, match, REG_EXTENDED|REG_ICASE|REG_NOSUB); + if (ret) { + char tmp[80]; + regerror(ret, &match_regex, tmp, 80); + mk_err("CGI: Failed to compile regex: %s", tmp); + } + } + } + + free(file); + mk_api->config_free(conf); +} + +int _mkp_init(struct plugin_api **api, char *confdir) +{ + mk_api = *api; + cgi_read_config(confdir); + + /* Make sure we act good if the child dies */ + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + return 0; +} + +void _mkp_exit() +{ + regfree(&match_regex); +} + +int _mkp_stage_30(struct plugin *plugin, struct client_session *cs, + struct session_request *sr) +{ + char *url = mk_api->mem_alloc_z(sr->uri.len + 1); + memcpy(url, sr->uri.data, sr->uri.len); + +// printf("Got URL %s\n", url); +// printf("Realpath: %s\n", sr->real_path.data); + + const char * const file = sr->real_path.data; + + if (access(file, X_OK)) + return MK_PLUGIN_RET_NOT_ME; + + if (regexec(&match_regex, url, 0, NULL, 0)) + return MK_PLUGIN_RET_NOT_ME; + + int status = do_cgi(file, url, cs->socket, sr); + + /* These are just for the other plugins, such as logger; bogus data */ + mk_api->header_set_http_status(sr, status); + + sr->close_now = MK_TRUE; + + return MK_PLUGIN_RET_END; +} diff --git a/plugins/cgi/conf/cgi.conf b/plugins/cgi/conf/cgi.conf new file mode 100644 index 0000000..9213e43 --- /dev/null +++ b/plugins/cgi/conf/cgi.conf @@ -0,0 +1,8 @@ +# CGI configuration +# ================== + +# Give an extended regex for matching the URL of allowed executables. +# You can list as many as needed, space-separated. + +[CGI] + Match /cgi-bin/.*.cgi .*.sh -- 1.7.2.1
_______________________________________________ Monkey mailing list [email protected] http://lists.monkey-project.com/listinfo/monkey
