From 47da1b3d5982ece5d8fd1f777b8471332f0d341a Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 16 Jul 2012 06:59:45 +0200 Subject: [PATCH 01/16] python: Added XBitHack-style caching to SSI handler. --- python3/ashd/ssi.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/python3/ashd/ssi.py b/python3/ashd/ssi.py index ff59e6e..6a3d977 100644 --- a/python3/ashd/ssi.py +++ b/python3/ashd/ssi.py @@ -59,7 +59,9 @@ class context(object): class ssifile(object): def __init__(self, path): self.path = path - self.mtime = os.stat(self.path).st_mtime + sb = os.stat(self.path) + self.cache = (sb.st_mode & 0o010) != 0 + self.mtime = int(sb.st_mtime) with open(path) as fp: self.parts = self.parse(fp.read()) @@ -128,7 +130,7 @@ def getfile(path): cf = filecache.get(path) if not cf: cf = filecache[path] = ssifile(path) - elif os.stat(path).st_mtime != cf.mtime: + elif int(os.stat(path).st_mtime) != cf.mtime: cf = filecache[path] = ssifile(path) return cf @@ -137,10 +139,23 @@ def wsgi(env, startreq): if env["PATH_INFO"] != "": return wsgiutil.simpleerror(env, startreq, 404, "Not Found", "The resource specified by the URL does not exist.") root = getfile(env["SCRIPT_FILENAME"]) + + if root.cache and "HTTP_IF_MODIFIED_SINCE" in env: + try: + lmt = wsgiutil.phttpdate(env["HTTP_IF_MODIFIED_SINCE"]) + if root.mtime <= lmt: + startreq("304 Not Modified", [("Content-Length", "0")]) + return [] + except: + pass + buf = io.StringIO() root.process(context(buf, root)) except Exception: - return wsgituil.simpleerror(env, startreq, 500, "Internal Error", "The server encountered an unpexpected error while handling SSI.") + return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server encountered an unpexpected error while handling SSI.") ret = buf.getvalue().encode("utf8") - startreq("200 OK", [("Content-Type", "text/html; charset=UTF-8"), ("Content-Length", str(len(ret)))]) + head = [("Content-Type", "text/html; charset=UTF-8"), ("Content-Length", str(len(ret)))] + if root.cache: + head.append(("Last-Modified", wsgiutil.httpdate(root.mtime))) + startreq("200 OK", head) return [ret] -- 2.11.0 From 58ee5c4ab4ce9be692277b520de72735494bd9f7 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 16 Jul 2012 08:11:20 +0200 Subject: [PATCH 02/16] python: Handle errors when loading chained modules more properly. --- python/ashd/wsgidir.py | 10 ++++++++-- python3/ashd/wsgidir.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/python/ashd/wsgidir.py b/python/ashd/wsgidir.py index 2da3a94..6bf00b6 100644 --- a/python/ashd/wsgidir.py +++ b/python/ashd/wsgidir.py @@ -33,11 +33,13 @@ argument `.fpy=my.module.foohandler' can be given to pass requests for functions, you may want to use the getmod() function in this module. """ -import os, threading, types, getopt +import sys, os, threading, types, logging, getopt import wsgiutil __all__ = ["application", "wmain", "getmod", "cachedmod", "chain"] +log = logging.getLogger("wsgidir") + class cachedmod(object): """Cache entry for modules loaded by getmod() @@ -192,7 +194,11 @@ def chain(env, startreq): object. """ path = env["SCRIPT_FILENAME"] - mod = getmod(path) + try: + mod = getmod(path) + except Exception: + log.error("Exception occurred when loading %s" % path, exc_info=sys.exc_info()) + return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Could not load WSGI handler.") entry = None if mod is not None: mod.lock.acquire() diff --git a/python3/ashd/wsgidir.py b/python3/ashd/wsgidir.py index 31afd3b..c2efcca 100644 --- a/python3/ashd/wsgidir.py +++ b/python3/ashd/wsgidir.py @@ -33,11 +33,13 @@ argument `.fpy=my.module.foohandler' can be given to pass requests for functions, you may want to use the getmod() function in this module. """ -import os, threading, types, importlib, getopt +import sys, os, threading, types, logging, importlib, getopt from . import wsgiutil __all__ = ["application", "wmain", "getmod", "cachedmod", "chain"] +log = logging.getLogger("wsgidir") + class cachedmod(object): """Cache entry for modules loaded by getmod() @@ -180,7 +182,11 @@ def chain(env, startreq): object. """ path = env["SCRIPT_FILENAME"] - mod = getmod(path) + try: + mod = getmod(path) + except Exception: + log.error("Exception occurred when loading %s" % path, exc_info=sys.exc_info()) + return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Could not load WSGI handler.") entry = None if mod is not None: with mod.lock: -- 2.11.0 From 5f0c1cd631b9abcca90afe15cf129babba86f7f1 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 16 Jul 2012 08:11:42 +0200 Subject: [PATCH 03/16] python: Added more useful logging to wsgidir. --- python/ashd/wsgidir.py | 10 +++++++++- python3/ashd/wsgidir.py | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/python/ashd/wsgidir.py b/python/ashd/wsgidir.py index 6bf00b6..d406c35 100644 --- a/python/ashd/wsgidir.py +++ b/python/ashd/wsgidir.py @@ -140,19 +140,27 @@ class handler(object): def handle(self, env, startreq): if not "SCRIPT_FILENAME" in env: + log.error("wsgidir called without SCRIPT_FILENAME set") return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") path = env["SCRIPT_FILENAME"] if not os.access(path, os.R_OK): + log.error("%s: not readable" % path) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") if "HTTP_X_ASH_PYTHON_HANDLER" in env: - handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"]) + try: + handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"]) + except Exception: + log.error("could not load handler %s" % env["HTTP_X_ASH_PYTHON_HANDLER"], exc_info=sys.exc_info()) + return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") else: base = os.path.basename(path) p = base.rfind('.') if p < 0: + log.error("wsgidir called with neither X-Ash-Python-Handler nor a file extension: %s" % path) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") ext = base[p + 1:] if not ext in self.exts: + log.error("unregistered file extension: %s" % ext) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") handler = self.exts[ext] return handler(env, startreq) diff --git a/python3/ashd/wsgidir.py b/python3/ashd/wsgidir.py index c2efcca..fc7e9ab 100644 --- a/python3/ashd/wsgidir.py +++ b/python3/ashd/wsgidir.py @@ -128,19 +128,27 @@ class handler(object): def handle(self, env, startreq): if not "SCRIPT_FILENAME" in env: + log.error("wsgidir called without SCRIPT_FILENAME set") return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") path = env["SCRIPT_FILENAME"] if not os.access(path, os.R_OK): + log.error("%s: not readable" % path) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") if "HTTP_X_ASH_PYTHON_HANDLER" in env: - handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"]) + try: + handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"]) + except Exception: + log.error("could not load handler %s" % env["HTTP_X_ASH_PYTHON_HANDLER"], exc_info=sys.exc_info()) + return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") else: base = os.path.basename(path) p = base.rfind('.') if p < 0: + log.error("wsgidir called with neither X-Ash-Python-Handler nor a file extension: %s" % path) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") ext = base[p + 1:] if not ext in self.exts: + log.error("unregistered file extension: %s" % ext) return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.") handler = self.exts[ext] return handler(env, startreq) -- 2.11.0 From c329061e49e3e62619200dd4ef61f7c096337d72 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 8 Aug 2012 08:37:51 +0200 Subject: [PATCH 04/16] python: Added some SCGI fixes apparently necessary for Jython. --- python/ashd/scgi.py | 4 +++- python3/ashd/scgi.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/ashd/scgi.py b/python/ashd/scgi.py index 95325f2..f7ba3a8 100644 --- a/python/ashd/scgi.py +++ b/python/ashd/scgi.py @@ -37,7 +37,8 @@ def readhead(sk): class reqthread(threading.Thread): def __init__(self, sk, handler): super(reqthread, self).__init__(name = "SCGI request handler") - self.sk = sk.dup().makefile("r+") + self.bsk = sk.dup() + self.sk = self.bsk.makefile("r+") self.handler = handler def run(self): @@ -46,6 +47,7 @@ class reqthread(threading.Thread): self.handler(head, self.sk) finally: self.sk.close() + self.bsk.close() def handlescgi(sk, handler): t = reqthread(sk, handler) diff --git a/python3/ashd/scgi.py b/python3/ashd/scgi.py index a06267f..8fa5767 100644 --- a/python3/ashd/scgi.py +++ b/python3/ashd/scgi.py @@ -37,7 +37,8 @@ def readhead(sk): class reqthread(threading.Thread): def __init__(self, sk, handler): super(reqthread, self).__init__(name = "SCGI request handler") - self.sk = sk.dup().makefile("rwb") + self.bsk = sk.dup() + self.sk = self.bsk.makefile("rwb") self.handler = handler def run(self): @@ -46,6 +47,7 @@ class reqthread(threading.Thread): self.handler(head, self.sk) finally: self.sk.close() + self.bsk.close() def handlescgi(sk, handler): t = reqthread(sk, handler) -- 2.11.0 From 78c8462c86c6043c7326bee7ebdf754e01d641c9 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 8 Aug 2012 08:40:34 +0200 Subject: [PATCH 05/16] python: Added logging initialization to scgi-wsgi. --- python/scgi-wsgi | 12 +++++++++--- python3/scgi-wsgi3 | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/python/scgi-wsgi b/python/scgi-wsgi index 5ffcf6e..58f5e91 100755 --- a/python/scgi-wsgi +++ b/python/scgi-wsgi @@ -1,23 +1,27 @@ #!/usr/bin/python -import sys, os, getopt +import sys, os, getopt, logging import socket import ashd.scgi def usage(out): - out.write("usage: scgi-wsgi [-hA] [-p MODPATH] [-T [HOST:]PORT] HANDLER-MODULE [ARGS...]\n") + out.write("usage: scgi-wsgi [-hAL] [-p MODPATH] [-T [HOST:]PORT] HANDLER-MODULE [ARGS...]\n") sk = None modwsgi_compat = False -opts, args = getopt.getopt(sys.argv[1:], "+hAp:T:") +setlog = True +opts, args = getopt.getopt(sys.argv[1:], "+hALp:T:") for o, a in opts: if o == "-h": usage(sys.stdout) sys.exit(0) elif o == "-p": sys.path.insert(0, a) + elif o == "-L": + setlog = False elif o == "-T": sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) p = a.rfind(":") if p < 0: bindhost = "localhost" @@ -32,6 +36,8 @@ for o, a in opts: if len(args) < 1: usage(sys.stderr) sys.exit(1) +if setlog: + logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s") if sk is None: # This is suboptimal, since the socket on stdin is not necessarily diff --git a/python3/scgi-wsgi3 b/python3/scgi-wsgi3 index 4f5714e..4242d10 100755 --- a/python3/scgi-wsgi3 +++ b/python3/scgi-wsgi3 @@ -1,23 +1,27 @@ #!/usr/bin/python3 -import sys, os, getopt +import sys, os, getopt, logging import socket import ashd.scgi def usage(out): - out.write("usage: scgi-wsgi3 [-hA] [-p MODPATH] [-T [HOST:]PORT] HANDLER-MODULE [ARGS...]\n") + out.write("usage: scgi-wsgi3 [-hAL] [-p MODPATH] [-T [HOST:]PORT] HANDLER-MODULE [ARGS...]\n") sk = None modwsgi_compat = False -opts, args = getopt.getopt(sys.argv[1:], "+hAp:T:") +setlog = True +opts, args = getopt.getopt(sys.argv[1:], "+hALp:T:") for o, a in opts: if o == "-h": usage(sys.stdout) sys.exit(0) elif o == "-p": sys.path.insert(0, a) + elif o == "-L": + setlog = False elif o == "-T": sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) p = a.rfind(":") if p < 0: bindhost = "localhost" @@ -32,6 +36,8 @@ for o, a in opts: if len(args) < 1: usage(sys.stderr) sys.exit(1) +if setlog: + logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s") if sk is None: # This is suboptimal, since the socket on stdin is not necessarily -- 2.11.0 From b327e4c1aff5a329e2e61cb1903fd1d4bd216a07 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 8 Aug 2012 08:48:27 +0200 Subject: [PATCH 06/16] python: Fixed log header for scgi-wsgi. --- python/scgi-wsgi | 2 +- python3/scgi-wsgi3 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/scgi-wsgi b/python/scgi-wsgi index 58f5e91..e2689d4 100755 --- a/python/scgi-wsgi +++ b/python/scgi-wsgi @@ -37,7 +37,7 @@ if len(args) < 1: usage(sys.stderr) sys.exit(1) if setlog: - logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s") + logging.basicConfig(format="scgi-wsgi(%(name)s): %(levelname)s: %(message)s") if sk is None: # This is suboptimal, since the socket on stdin is not necessarily diff --git a/python3/scgi-wsgi3 b/python3/scgi-wsgi3 index 4242d10..c66f6e3 100755 --- a/python3/scgi-wsgi3 +++ b/python3/scgi-wsgi3 @@ -37,7 +37,7 @@ if len(args) < 1: usage(sys.stderr) sys.exit(1) if setlog: - logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s") + logging.basicConfig(format="scgi-wsgi3(%(name)s): %(levelname)s: %(message)s") if sk is None: # This is suboptimal, since the socket on stdin is not necessarily -- 2.11.0 From c1fc98261011ee0758a49da491cf4bd4e47eb8eb Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 8 Aug 2012 19:19:24 +0200 Subject: [PATCH 07/16] Added the `httimed' program. --- doc/Makefile.am | 2 +- doc/httimed.doc | 49 ++++++++++++++++++++++++++ src/.gitignore | 1 + src/Makefile.am | 2 +- src/httimed.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 doc/httimed.doc create mode 100644 src/httimed.c diff --git a/doc/Makefile.am b/doc/Makefile.am index e018ed8..dc88692 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,6 +1,6 @@ dist_man1_MANS = callcgi.1 dirplex.1 htparser.1 patplex.1 sendfile.1 \ userplex.1 htls.1 callscgi.1 accesslog.1 htextauth.1 \ - callfcgi.1 multifscgi.1 errlogger.1 + callfcgi.1 multifscgi.1 errlogger.1 httimed.1 dist_man7_MANS = ashd.7 diff --git a/doc/httimed.doc b/doc/httimed.doc new file mode 100644 index 0000000..d34f474 --- /dev/null +++ b/doc/httimed.doc @@ -0,0 +1,49 @@ +httimed(1) +========== + +NAME +---- +httimed - Ashd handler expiration pipe + +SYNOPSIS +-------- +*httimed* [*-h*] [*-t* 'TIMEOUT'] 'CHILD' ['ARGS'...] + +DESCRIPTION +----------- + +The *httimed* handler starts a single child handler which it passes +all request it receives unmodified, and in addition keeps track of +time and simply exits if no requests are received in a certain +interval, which might be useful for handlers which are only rarely +used and waste unproportionally much system resources if kept beyond +their usefulness. + +*httimed* is a persistent handler, as defined in *ashd*(7), and the +specified child handler must also be a persistent handler. + +By default, *httimed* exits if it receives no requests in five +minutes, but the time interval can be specified using the *-t* option. + +If the child handler exits, *httimed* exits as well. + +OPTIONS +------- + +*-h*:: + + Print a brief help message to standard output and exit. + +*-t* 'TIMEOUT':: + + Exit if no requests are received after 'TIMEOUT' seconds since + the last one. If *-t* is not specified, 'TIMEOUT' is five + minutes by default. + +AUTHOR +------ +Fredrik Tolf + +SEE ALSO +-------- +*ashd*(7) diff --git a/src/.gitignore b/src/.gitignore index 4a9153f..582f3e7 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -11,3 +11,4 @@ /multifscgi /errlogger /psendfile +/httimed diff --git a/src/Makefile.am b/src/Makefile.am index 394e353..8a08744 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = dirplex bin_PROGRAMS = htparser sendfile callcgi patplex userplex htls \ callscgi accesslog htextauth callfcgi multifscgi \ - errlogger + errlogger httimed noinst_PROGRAMS=psendfile diff --git a/src/httimed.c b/src/httimed.c new file mode 100644 index 0000000..2c939c5 --- /dev/null +++ b/src/httimed.c @@ -0,0 +1,106 @@ +/* + ashd - A Sane HTTP Daemon + Copyright (C) 2008 Fredrik Tolf + + 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 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include + +static void usage(FILE *out) +{ + fprintf(out, "usage: httimed [-h] [-t TIMEOUT] CHILD [ARGS...]\n"); +} + +int main(int argc, char **argv) +{ + int c, t, ret; + int ch, fd; + struct hthead *req; + struct pollfd pfd[2]; + time_t lreq, now; + + t = 300; + while((c = getopt(argc, argv, "+ht:")) >= 0) { + switch(c) { + case 'h': + usage(stdout); + exit(0); + case 't': + t = atoi(optarg); + if(t < 1) { + fprintf(stderr, "httimed: timeout must be positive\n"); + exit(1); + } + break; + } + } + if(argc - optind < 1) { + usage(stderr); + exit(1); + } + if((ch = stdmkchild(argv + optind, NULL, NULL)) < 0) { + flog(LOG_ERR, "httimed: could not fork child: %s", strerror(errno)); + exit(1); + } + lreq = time(NULL); + while(1) { + memset(pfd, 0, sizeof(pfd)); + pfd[0].fd = 0; + pfd[0].events = POLLIN; + pfd[1].fd = ch; + pfd[1].events = POLLHUP; + if((ret = poll(pfd, 2, (t + 1 - (time(NULL) - lreq)) * 1000)) < 0) { + if(errno != EINTR) { + flog(LOG_ERR, "httimed: error in poll: %s", strerror(errno)); + exit(1); + } + } + now = time(NULL); + if(pfd[0].revents) { + if((fd = recvreq(0, &req)) < 0) { + if(errno == 0) + break; + flog(LOG_ERR, "httimed: error in recvreq: %s", strerror(errno)); + exit(1); + } + if(sendreq(ch, req, fd)) { + flog(LOG_ERR, "httimed: could not pass request to child: %s", strerror(errno)); + exit(1); + } + freehthead(req); + close(fd); + } + if(pfd[1].revents & POLLHUP) + break; + if(now - lreq > t) + break; + lreq = now; + } + return(0); +} -- 2.11.0 From 54490135194e0474e753ce7d4cb60f935dad1dd4 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 16 Aug 2012 01:51:11 +0200 Subject: [PATCH 08/16] dirplex: Added a capture option to ignore captures of the root directory. --- doc/dirplex.doc | 6 +++++- src/dirplex/conf.c | 3 +++ src/dirplex/dirplex.c | 2 +- src/dirplex/dirplex.h | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/dirplex.doc b/doc/dirplex.doc index 9d2240d..a2dd61c 100644 --- a/doc/dirplex.doc +++ b/doc/dirplex.doc @@ -182,7 +182,7 @@ The following configuration directives are recognized: pattern-matching procedure and the follow-up lines accepted by this stanza are described below, under MATCHING. -*capture* 'HANDLER':: +*capture* 'HANDLER' ['FLAGS']:: Only meaningful in `.htrc` files. If a *capture* directive is specified, then the URL-to-file mapping procedure as described @@ -194,6 +194,10 @@ The following configuration directives are recognized: follow-up lines. Note that the `X-Ash-File` header is not added to requests passed via *capture* directives. + If 'FLAGS' contain the character `R`, this *capture* directive + will be ignored if it is in the root directory that *dirplex* + serves. + MATCHING -------- diff --git a/src/dirplex/conf.c b/src/dirplex/conf.c index 84c035d..dec84b5 100644 --- a/src/dirplex/conf.c +++ b/src/dirplex/conf.c @@ -262,6 +262,9 @@ struct config *readconfig(char *file) if(cf->capture != NULL) free(cf->capture); cf->capture = sstrdup(s->argv[1]); + cf->caproot = 1; + if((s->argc > 2) && strchr(s->argv[2], 'R')) + cf->caproot = 0; } else if(!strcmp(s->argv[0], "eof")) { break; } else { diff --git a/src/dirplex/dirplex.c b/src/dirplex/dirplex.c index 55b4648..d687957 100644 --- a/src/dirplex/dirplex.c +++ b/src/dirplex/dirplex.c @@ -244,7 +244,7 @@ static int checkdir(struct hthead *req, int fd, char *path, char *rest) struct child *ch; cf = getconfig(path); - if(cf->capture != NULL) { + if((cf->capture != NULL) && (cf->caproot || !cf->path || strcmp(cf->path, "."))) { cpath = sprintf2("%s/", path); if((ch = findchild(cpath, cf->capture, &ccf)) == NULL) { free(cpath); diff --git a/src/dirplex/dirplex.h b/src/dirplex/dirplex.h index d3011f5..14b5454 100644 --- a/src/dirplex/dirplex.h +++ b/src/dirplex/dirplex.h @@ -18,6 +18,7 @@ struct config { struct pattern *patterns; char **index; char *capture; + int caproot; }; struct rule { -- 2.11.0 From fa88545551d709cdff26b5421bd69ac6b1af7e3b Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 16 Aug 2012 01:55:12 +0200 Subject: [PATCH 09/16] doc: Fixed formatting error. --- doc/dirplex.doc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/dirplex.doc b/doc/dirplex.doc index a2dd61c..10b7609 100644 --- a/doc/dirplex.doc +++ b/doc/dirplex.doc @@ -192,11 +192,9 @@ The following configuration directives are recognized: be a named request handler specified either in the same `.htrc` file or elsewhere. The *capture* directive accepts no follow-up lines. Note that the `X-Ash-File` header is not - added to requests passed via *capture* directives. - - If 'FLAGS' contain the character `R`, this *capture* directive - will be ignored if it is in the root directory that *dirplex* - serves. + added to requests passed via *capture* directives. If 'FLAGS' + contain the character `R`, this *capture* directive will be + ignored if it is in the root directory that *dirplex* serves. MATCHING -------- -- 2.11.0 From c6800c091d203a10388ddc165b79065c5e9168dc Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 16 Aug 2012 05:54:23 +0200 Subject: [PATCH 10/16] Updated ChangeLog. --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index f022552..69a2025 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Version 0.11: + + * Some useful configuration options for dirplex. + * Better default configuration. + * Various bug fixes, tunings and other minor improvements. + Version 0.10: * Added options to dirplex and patplex for setting request headers -- 2.11.0 From 0281ef697c76ab09635075cf7162eea66bdb1372 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 16 Aug 2012 06:07:20 +0200 Subject: [PATCH 11/16] Bumped version number. --- configure.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.in b/configure.in index ece99db..74a1a99 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ AC_INIT(src/htparser.c) -AM_INIT_AUTOMAKE([ashd], [0.11]) +AM_INIT_AUTOMAKE([ashd], [0.12]) AM_CONFIG_HEADER(config.h) AC_PROG_CC -- 2.11.0 From 64a8cd9feaab29ed99cadd9eed943a6fc7709fa4 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 16 Aug 2012 06:30:25 +0200 Subject: [PATCH 12/16] python: Improved error handling and logging in ashd-wsgi. --- python/ashd-wsgi | 15 +++++++++------ python3/ashd-wsgi3 | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/python/ashd-wsgi b/python/ashd-wsgi index 9eb3d4f..5e1db09 100755 --- a/python/ashd-wsgi +++ b/python/ashd-wsgi @@ -34,6 +34,7 @@ if len(args) < 1: sys.exit(1) if setlog: logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s") +log = logging.getLogger("ashd-wsgi") try: handlermod = __import__(args[0], fromlist = ["dummy"]) @@ -182,18 +183,18 @@ def dowsgi(req): reqevent = ashd.perf.request(env) exc = (None, None, None) try: - respiter = handler(env, startreq) try: + respiter = handler(env, startreq) try: for data in respiter: write(data) if resp: flushreq() - except closed: - pass - finally: - if hasattr(respiter, "close"): - respiter.close() + finally: + if hasattr(respiter, "close"): + respiter.close() + except closed: + pass if resp: reqevent.response(resp) except: @@ -233,6 +234,8 @@ class reqthread(threading.Thread): flightlock.notify() finally: flightlock.release() + except: + log.error("exception occurred in handler thread", exc_info=True) finally: self.req.close() diff --git a/python3/ashd-wsgi3 b/python3/ashd-wsgi3 index e6c39a4..cdabec3 100755 --- a/python3/ashd-wsgi3 +++ b/python3/ashd-wsgi3 @@ -34,6 +34,7 @@ if len(args) < 1: sys.exit(1) if setlog: logging.basicConfig(format="ashd-wsgi3(%(name)s): %(levelname)s: %(message)s") +log = logging.getLogger("ashd-wsgi3") try: handlermod = __import__(args[0], fromlist = ["dummy"]) @@ -191,18 +192,18 @@ def dowsgi(req): return write with ashd.perf.request(env) as reqevent: - respiter = handler(env, startreq) try: + respiter = handler(env, startreq) try: for data in respiter: write(data) if resp: flushreq() - except closed: - pass - finally: - if hasattr(respiter, "close"): - respiter.close() + finally: + if hasattr(respiter, "close"): + respiter.close() + except closed: + pass if resp: reqevent.response(resp) @@ -231,6 +232,8 @@ class reqthread(threading.Thread): with flightlock: inflight -= 1 flightlock.notify() + except: + log.error("exception occurred in handler thread", exc_info=True) finally: self.req.close() sys.stderr.flush() -- 2.11.0 From cefb0f7aedb9e3f0a2c04e7258ab07a243638e75 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sun, 19 Aug 2012 08:11:16 +0200 Subject: [PATCH 13/16] htparser: Made HTTP version recognition case-independent. --- src/htparser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/htparser.c b/src/htparser.c index 4086f79..c9cc97c 100644 --- a/src/htparser.c +++ b/src/htparser.c @@ -274,7 +274,7 @@ void serve(FILE *in, struct conn *conn) if(!getheader(resp, "server")) headappheader(resp, "Server", sprintf3("ashd/%s", VERSION)); - if(!strcmp(req->ver, "HTTP/1.0")) { + if(!strcasecmp(req->ver, "HTTP/1.0")) { if(!strcasecmp(req->method, "head")) { keep = http10keep(req, resp); writeresp(in, resp); @@ -295,7 +295,7 @@ void serve(FILE *in, struct conn *conn) } if(!keep) break; - } else if(!strcmp(req->ver, "HTTP/1.1")) { + } else if(!strcasecmp(req->ver, "HTTP/1.1")) { if(!strcasecmp(req->method, "head")) { writeresp(in, resp); fprintf(in, "\r\n"); -- 2.11.0 From 0bf0720d9d585a56904081d0c8a71507d8889adf Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Tue, 28 Aug 2012 02:59:57 +0200 Subject: [PATCH 14/16] python: Remove HTTP_CONTENT_{TYPE,LENGTH} from WSGI environment. Although not strictly required by the CGI specification, many programs and libraries seem to require it. --- python/ashd-wsgi | 10 ++++++++-- python3/ashd-wsgi3 | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/python/ashd-wsgi b/python/ashd-wsgi index 5e1db09..f20d6ca 100755 --- a/python/ashd-wsgi +++ b/python/ashd-wsgi @@ -130,8 +130,14 @@ def dowsgi(req): if "X-Ash-Server-Port" in req: env["SERVER_PORT"] = req["X-Ash-Server-Port"] if "X-Ash-Protocol" in req and req["X-Ash-Protocol"] == "https": env["HTTPS"] = "on" if "X-Ash-Address" in req: env["REMOTE_ADDR"] = req["X-Ash-Address"] - if "Content-Type" in req: env["CONTENT_TYPE"] = req["Content-Type"] - if "Content-Length" in req: env["CONTENT_LENGTH"] = req["Content-Length"] + if "Content-Type" in req: + env["CONTENT_TYPE"] = req["Content-Type"] + # The CGI specification does not strictly require this, but + # many actualy programs and libraries seem to. + del env["HTTP_CONTENT_TYPE"] + if "Content-Length" in req: + env["CONTENT_LENGTH"] = req["Content-Length"] + del env["HTTP_CONTENT_TYPE"] if "X-Ash-File" in req: env["SCRIPT_FILENAME"] = absolutify(req["X-Ash-File"]) if "X-Ash-Protocol" in req: env["wsgi.url_scheme"] = req["X-Ash-Protocol"] env["wsgi.input"] = req.sk diff --git a/python3/ashd-wsgi3 b/python3/ashd-wsgi3 index cdabec3..dcf1613 100755 --- a/python3/ashd-wsgi3 +++ b/python3/ashd-wsgi3 @@ -135,6 +135,10 @@ def dowsgi(req): ("HTTP_X_ASH_ADDRESS", "REMOTE_ADDR"), ("HTTP_CONTENT_TYPE", "CONTENT_TYPE"), ("HTTP_CONTENT_LENGTH", "CONTENT_LENGTH"), ("HTTP_X_ASH_PROTOCOL", "wsgi.url_scheme")]: if src in env: env[tgt] = env[src] + for key in ["HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"]: + # The CGI specification does not strictly require this, but + # many actualy programs and libraries seem to. + if key in env: del env[key] if "X-Ash-Protocol" in req and req["X-Ash-Protocol"] == b"https": env["HTTPS"] = "on" if "X-Ash-File" in req: env["SCRIPT_FILENAME"] = absolutify(req["X-Ash-File"].decode(locale.getpreferredencoding())) env["wsgi.input"] = req.sk -- 2.11.0 From 75c134b696c197ab94c0a3064455dd41b8061d57 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Thu, 30 Aug 2012 05:13:15 +0200 Subject: [PATCH 15/16] python: Fixed typo. --- python/ashd-wsgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ashd-wsgi b/python/ashd-wsgi index f20d6ca..46cf85c 100755 --- a/python/ashd-wsgi +++ b/python/ashd-wsgi @@ -137,7 +137,7 @@ def dowsgi(req): del env["HTTP_CONTENT_TYPE"] if "Content-Length" in req: env["CONTENT_LENGTH"] = req["Content-Length"] - del env["HTTP_CONTENT_TYPE"] + del env["HTTP_CONTENT_LENGTH"] if "X-Ash-File" in req: env["SCRIPT_FILENAME"] = absolutify(req["X-Ash-File"]) if "X-Ash-Protocol" in req: env["wsgi.url_scheme"] = req["X-Ash-Protocol"] env["wsgi.input"] = req.sk -- 2.11.0 From 56e8f0f59acce9ccd1d161b2846f636b3e332b76 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sat, 1 Sep 2012 06:36:20 +0200 Subject: [PATCH 16/16] python: Added a simple function for doing directory-local "imports". --- python/ashd/wsgidir.py | 21 +++++++++++++++++++++ python3/ashd/wsgidir.py | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/python/ashd/wsgidir.py b/python/ashd/wsgidir.py index d406c35..1fc66f9 100644 --- a/python/ashd/wsgidir.py +++ b/python/ashd/wsgidir.py @@ -110,6 +110,27 @@ def getmod(path): finally: entry[0].release() +def importlocal(filename): + import inspect + cf = inspect.currentframe() + if cf is None: raise ImportError("could not get current frame") + if cf.f_back is None: raise ImportError("could not get caller frame") + cfile = cf.f_back.f_code.co_filename + if not os.path.exists(cfile): + raise ImportError("caller is not in a proper file") + path = os.path.realpath(os.path.join(os.path.dirname(cfile), filename)) + if '.' not in os.path.basename(path): + for ext in [".pyl", ".py"]: + if os.path.exists(path + ext): + path += ext + break + else: + raise ImportError("could not resolve file: " + filename) + else: + if not os.path.exists(cfile): + raise ImportError("no such file: " + filename) + return getmod(path).mod + class handler(object): def __init__(self): self.lock = threading.Lock() diff --git a/python3/ashd/wsgidir.py b/python3/ashd/wsgidir.py index fc7e9ab..c6fa1ad 100644 --- a/python3/ashd/wsgidir.py +++ b/python3/ashd/wsgidir.py @@ -101,6 +101,27 @@ def getmod(path): entry[1] = cachedmod(mod, sb.st_mtime) return entry[1] +def importlocal(filename): + import inspect + cf = inspect.currentframe() + if cf is None: raise ImportError("could not get current frame") + if cf.f_back is None: raise ImportError("could not get caller frame") + cfile = cf.f_back.f_code.co_filename + if not os.path.exists(cfile): + raise ImportError("caller is not in a proper file") + path = os.path.realpath(os.path.join(os.path.dirname(cfile), filename)) + if '.' not in os.path.basename(path): + for ext in [".pyl", ".py"]: + if os.path.exists(path + ext): + path += ext + break + else: + raise ImportError("could not resolve file: " + filename) + else: + if not os.path.exists(cfile): + raise ImportError("no such file: " + filename) + return getmod(path).mod + class handler(object): def __init__(self): self.lock = threading.Lock() -- 2.11.0