From: Fredrik Tolf Date: Wed, 25 May 2011 08:16:10 +0000 (+0200) Subject: Initial commit. X-Git-Url: http://dolda2000.com/gitweb/?p=wrw.git;a=commitdiff_plain;h=b409a33843abb3221edd27016558c39cf33a6510 Initial commit. --- b409a33843abb3221edd27016558c39cf33a6510 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/wrw/__init__.py b/wrw/__init__.py new file mode 100644 index 0000000..5f7aacb --- /dev/null +++ b/wrw/__init__.py @@ -0,0 +1,7 @@ +__all__ = ["request", "wsgiwrap", "restart", "cookie", "formdata"] + +from req import request +from util import wsgiwrap +from dispatch import restart +import cookie +from form import formdata diff --git a/wrw/cookie.py b/wrw/cookie.py new file mode 100644 index 0000000..2c6f982 --- /dev/null +++ b/wrw/cookie.py @@ -0,0 +1,41 @@ +import Cookie + +__all__ = ["cookies", "get", "add"] + +def addcookies(req): + ck = cookies(req) + for nm in ck.codec: + req.ohead.add("Set-Cookie", ck.codec[nm].OutputString()) + +class cookiedict(object): + def __init__(self, req): + self.bk = Cookie.SimpleCookie(req.ihead.get("Cookie")) + self.codec = Cookie.SimpleCookie() + req.oncommit(addcookies) + + def __getitem__(self, name): + return self.bk[name].value + + def __contains__(self, name): + return name in self.bk + + def get(self, name, default = None): + if name not in self.bk: + return default + return self.bk[name].value + + def add(self, name, value, path = None): + self.codec[name] = value + if path is not None: self.codec[name]["path"] = path + + def __setitem__(self, name, value): + self.add(name, value) + +def cookies(req): + return req.item(cookiedict) + +def get(req, name, default = None): + return cookies(req).get(name, default) + +def add(req, name, value, path = None): + cookies(req).add(name, value, path) diff --git a/wrw/dispatch.py b/wrw/dispatch.py new file mode 100644 index 0000000..d428a77 --- /dev/null +++ b/wrw/dispatch.py @@ -0,0 +1,28 @@ +__all__ = ["restart"] + +class restart(Exception): + def handle(self, req): + pass + +def mangle(result): + try: + iter(result) + except TypeError: + pass + else: + return result + return [str(result)] + +def handle(req, startreq, handler): + try: + resp = [""] + while True: + try: + resp = handler(req) + break + except restart, i: + handler = i + req.commit(startreq) + return resp + finally: + req.cleanup() diff --git a/wrw/form.py b/wrw/form.py new file mode 100644 index 0000000..049da10 --- /dev/null +++ b/wrw/form.py @@ -0,0 +1,40 @@ +import cgi + +__all__ = ["formdata"] + +class formwrap(object): + def __init__(self, req): + if req.ihead["Content-Type"] == "application/x-www-form-urlencoded": + self.cf = cgi.parse(environ = req.env, fp = req.env["wsgi.input"]) + else: + self.cf = cgi.parse(environ = req.env) + + def __getitem__(self, key): + return self.cf[key][0] + + def get(self, key, default = ""): + if key in self: + return self.cf[key][0] + return default + + def __contains__(self, key): + return key in self.cf and len(self.cf[key]) > 0 + + def __iter__(self): + return iter(self.cf) + + def items(self): + def iter(): + for key, list in self.cf.items(): + for val in list: + yield key, val + return list(iter()) + + def keys(self): + return self.cf.keys() + + def values(self): + return [val for key, val in self.items()] + +def formdata(req): + return req.item(formwrap) diff --git a/wrw/req.py b/wrw/req.py new file mode 100644 index 0000000..f3dc31f --- /dev/null +++ b/wrw/req.py @@ -0,0 +1,119 @@ +__all__ = ["request"] + +class headdict(object): + def __init__(self): + self.dict = {} + + def __getitem__(self, key): + return self.dict[key.lower()][1] + + def __setitem__(self, key, val): + self.dict[key.lower()] = [key, val] + + def __contains__(self, key): + return key.lower() in self.dict + + def __delitem__(self, key): + del self.dict[key.lower()] + + def __iter__(self): + return iter((list[0] for list in self.dict.itervalues())) + + def get(self, key, default = ""): + if key.lower() in self.dict: + return self.dict[key.lower()][1] + return default + + def getlist(self, key): + return self.dict.setdefault(key.lower(), [key])[1:] + + def add(self, key, val): + self.dict.setdefault(key.lower(), [key]).append(val) + + def __repr__(self): + return repr(self.dict) + + def __str__(self): + return str(self.dict) + +def fixcase(str): + str = str.lower() + i = 0 + b = True + while i < len(str): + if b: + str = str[:i] + str[i].upper() + str[i + 1:] + b = False + if str[i] == '-': + b = True + i += 1 + return str + +class request(object): + def __init__(self, env): + self.env = env + self.uriname = env["SCRIPT_NAME"] + self.filename = env.get("SCRIPT_FILENAME") + self.uri = env["REQUEST_URI"] + self.pathinfo = env["PATH_INFO"] + self.query = env["QUERY_STRING"] + self.remoteaddr = env["REMOTE_ADDR"] + self.serverport = env["SERVER_PORT"] + self.https = "HTTPS" in env + self.ihead = headdict() + self.ohead = headdict() + for k, v in env.items(): + if k[:5] == "HTTP_": + self.ihead.add(fixcase(k[5:].replace("_", "-")), v) + self.items = {} + self.statuscode = (200, "OK") + self.ohead["Content-Type"] = "text/html" + self.resources = set() + self.clean = set() + self.commitfuns = [] + + def status(self, code, msg): + self.statuscode = code, msg + + def item(self, id): + if id in self.items: + return self.items[id] + self.items[id] = new = id(self) + if hasattr(new, "__enter__") and hasattr(new, "__exit__"): + self.withres(new) + return new + + def withres(self, res): + if res not in self.resources: + done = False + res.__enter__() + try: + self.resources.add(res) + self.clean.add(res.__exit__) + done = True + finally: + if not done: + res.__exit__(None, None, None) + self.resources.discard(res) + + def cleanup(self): + def clean1(list): + if len(list) > 0: + try: + list[0]() + finally: + clean1(list[1:]) + clean1(list(self.clean)) + + def oncommit(self, fn): + if fn not in self.commitfuns: + self.commitfuns.append(fn) + + def commit(self, startreq): + for fun in reversed(self.commitfuns): + fun(self) + hdrs = [] + for nm in self.ohead: + for val in self.ohead.getlist(nm): + hdrs.append((nm, val)) + startreq("%s %s" % self.statuscode, hdrs) diff --git a/wrw/session.py b/wrw/session.py new file mode 100644 index 0000000..513725d --- /dev/null +++ b/wrw/session.py @@ -0,0 +1,192 @@ +import threading, time, pickle, random, os +import cookie + +__all__ = ["db", "get"] + +def hexencode(str): + ret = "" + for byte in str: + ret += "%02X" % (ord(byte),) + return ret + +def gennonce(length): + nonce = "" + for i in xrange(length): + nonce += chr(random.randint(0, 255)) + return nonce + +class session(object): + def __init__(self, expire = 86400 * 7): + self.id = hexencode(gennonce(16)) + self.dict = {} + self.lock = threading.Lock() + self.ctime = self.atime = self.mtime = int(time.time()) + self.expire = expire + self.dctl = set() + self.dirtyp = False + + def dirty(self): + for d in self.dctl: + if d.sessdirty(): + return True + return self.dirtyp + + def frozen(self): + for d in self.dctl: + d.sessfrozen() + self.dirtyp = False + + def __getitem__(self, key): + return self.dict[key] + + def get(self, key, default = None): + return self.dict.get(key, default) + + def __setitem__(self, key, value): + self.dict[key] = value + if hasattr(value, "sessdirty"): + self.dctl.add(value) + else: + self.dirtyp = True + + def __delitem__(self, key): + old = self.dict.pop(key) + if old in self.dctl: + self.dctl.remove(old) + self.dirtyp = True + + def __contains__(self, key): + return key in self.dict + + def __getstate__(self): + ret = [] + for k, v in self.__dict__.items(): + if k == "lock": continue + ret.append((k, v)) + return ret + + def __setstate__(self, st): + for k, v in st: + self.__dict__[k] = v + self.lock = threading.Lock() + +class db(object): + def __init__(self, cookiename = "wrwsess", path = "/"): + self.live = {} + self.cookiename = cookiename + self.path = path + self.lock = threading.Lock() + self.lastuse = 0 + self.cthread = None + self.freezetime = 3600 + + def clean(self): + now = int(time.time()) + with self.lock: + dlist = [] + for sess in self.live.itervalues(): + if sess.atime + self.freezetime < now: + try: + if sess.dirty(): + self.freeze(sess) + except: + if sess.atime + sess.expire < now: + dlist.append(sess) + else: + dlist.append(sess) + for sess in dlist: + del self.live[sess.id] + + def cleanloop(self): + try: + lastuse = self.lastuse + while self.lastuse >= lastuse: + lastuse = self.lastuse + time.sleep(300) + self.clean() + finally: + with self.lock: + self.cthread = None + + def fetch(self, req): + now = int(time.time()) + self.lastuse = now + sessid = cookie.get(req, self.cookiename) + with self.lock: + if self.cthread is None: + self.cthread = threading.Thread(target = self.cleanloop) + self.cthread.setDaemon(True) + self.cthread.start() + try: + if sessid is None: + raise KeyError() + elif sessid in self.live: + sess = self.live[sessid] + else: + sess = self.thaw(sessid) + self.live[sessid] = sess + if sess.atime + sess.expire < now: + raise KeyError() + sess.atime = now + except KeyError: + sess = session() + self.live[sess.id] = sess + req.oncommit(self.addcookie) + req.oncommit(self.ckfreeze) + return sess + + def addcookie(self, req): + sess = req.item(self.fetch) + cookie.add(req, self.cookiename, sess.id, self.path) + + def ckfreeze(self, req): + sess = req.item(self.fetch) + if sess.dirty(): + try: + self.freeze(sess) + except: + pass + + def thaw(self, sessid): + raise KeyError() + + def freeze(self, sess): + raise TypeError() + +class backeddb(db): + def __init__(self, backdb, *args, **kw): + super(backeddb, self).__init__(*args, **kw) + self.backdb = backdb + + def thaw(self, sessid): + data = self.backdb[sessid] + try: + return pickle.loads(data) + except Exception, e: + raise KeyError() + + def freeze(self, sess): + self.backdb[sess.id] = pickle.dumps(sess) + sess.frozen() + +class dirback(object): + def __init__(self, path): + self.path = path + + def __getitem__(self, key): + try: + with open(os.path.join(self.path, key)) as inf: + return inf.read() + except IOError: + raise KeyError(key) + + def __setitem__(self, key, value): + if not os.path.exists(self.path): + os.makedirs(self.path) + with open(os.path.join(self.path, key), "w") as out: + out.write(value) + +default = backeddb(dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))) + +def get(req): + return req.item(default.fetch) diff --git a/wrw/util.py b/wrw/util.py new file mode 100644 index 0000000..22131ea --- /dev/null +++ b/wrw/util.py @@ -0,0 +1,6 @@ +import req, dispatch + +def wsgiwrap(callable): + def wrapper(env, startreq): + return dispatch.handle(req.request(env), startreq, callable) + return wrapper