Initial commit.
authorFredrik Tolf <fredrik@dolda2000.com>
Wed, 25 May 2011 08:16:10 +0000 (10:16 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Wed, 25 May 2011 08:16:10 +0000 (10:16 +0200)
.gitignore [new file with mode: 0644]
wrw/__init__.py [new file with mode: 0644]
wrw/cookie.py [new file with mode: 0644]
wrw/dispatch.py [new file with mode: 0644]
wrw/form.py [new file with mode: 0644]
wrw/req.py [new file with mode: 0644]
wrw/session.py [new file with mode: 0644]
wrw/util.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/wrw/__init__.py b/wrw/__init__.py
new file mode 100644 (file)
index 0000000..5f7aacb
--- /dev/null
@@ -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 (file)
index 0000000..2c6f982
--- /dev/null
@@ -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 (file)
index 0000000..d428a77
--- /dev/null
@@ -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 (file)
index 0000000..049da10
--- /dev/null
@@ -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 (file)
index 0000000..f3dc31f
--- /dev/null
@@ -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 (file)
index 0000000..513725d
--- /dev/null
@@ -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 (file)
index 0000000..22131ea
--- /dev/null
@@ -0,0 +1,6 @@
+import req, dispatch
+
+def wsgiwrap(callable):
+    def wrapper(env, startreq):
+        return dispatch.handle(req.request(env), startreq, callable)
+    return wrapper