import bsddb, cgi, os, rfc822, simplejson, uuid, wsgiref databases = {} VERSION = "0.0.3" def dispatch(environ, response): import basura method = environ['REQUEST_METHOD'] # debug if method == 'QUIT': response('201 Accepted', []) shutdown() os.kill(os.getpid(),15) # method not supported try: if not method.isupper(): raise method if not hasattr(basura, method): __import__("basura." + method) except: response('501 Not Implemented', []) yield "Method not supported\n" return # negotiate content type def response_negotiator(status, headers=[], *arg, **kwargs): wsgi_head = wsgiref.headers.Headers(headers) ctype,cparms = cgi.parse_header(wsgi_head['content-type'] or '') accept = [t.split(';')[0] for t in environ.get('HTTP_ACCEPT','').split(',')] if not ctype: ctype = 'application/json' if ctype not in (accept + ['text/html']): ctype = 'text/plain' if not cparms.has_key('charset'): cparms['charset'] = 'utf-8' wsgi_head.add_header('Server', 'basura/%s' % VERSION) wsgi_head.add_header('Content-Type', ctype, **cparms) wsgi_head.add_header('Damien', 'awesome') if content_encoding(environ): wsgi_head.add_header('Content-Encoding', content_encoding(environ)) response(status, headers, *arg, **kwargs) # dispatch data = getattr(basura, method).request(environ, response_negotiator) # optionally encode if content_encoding(environ) == 'deflate': import zlib data = zlib.compress(data) elif content_encoding(environ) == 'gzip': import StringIO, gzip zdata = StringIO.StringIO() zfile = gzip.GzipFile(mode='wb', fileobj=zdata) zfile.write(data) zfile.close() data = zdata.getvalue() yield data def content_encoding(environ): accept_encoding = [opt.lower().strip() for opt in environ.get('HTTP_ACCEPT_ENCODING','').split(',')] if 'deflate' in accept_encoding: return 'deflate' if 'gzip' in accept_encoding: return 'gzip' # encapsulate json conversions for the database class JsonDB(object): def __init__(self, db): self.db = db def __getitem__(self, id): return simplejson.loads(self.db[id]) def __setitem__(self, id, doc): self.db[id] = simplejson.dumps(doc,ensure_ascii=False).encode('utf-8') def __delitem__(self, id): del self.db[id] def get(self, id, default): return simplejson.loads(self.db.get(id, "null")) or default def iteritems(self): for id, doc in self.db.iteritems(): yield id, simplejson.loads(doc) def __getattr__(self, attr): return getattr(self.db, attr) # open a database, creating if necessary def dbopen(name, flag='c'): global databases try: return databases[name] except: if not os.path.exists('db_root'): os.mkdir('db_root') databases[name] = JsonDB(bsddb.btopen(os.path.join('db_root',name),flag)) return databases[name] # determine if a database exists, returning it if it does def dbexists(name): if databases.has_key(name): return databases[name] if os.path.exists(os.path.join('db_root',name)): return dbopen(name) return False # determine document status information def docstat(doc): etag = str(doc['_rev']) lmod = rfc822.formatdate(uuid.UUID(doc['_rev']).time/10000000-12219292800) return etag, lmod # check documents status def doccheck(db, id, environ, stat): if not db.has_key(id): return stat etag, lmod = docstat(db[id]) # handle if unmodified header if_unmod = environ.get('HTTP_IF_UNMODIFIED_SINCE') if if_unmod and rfc822.parsedate(if_unmod) < rfc822.parsedate(lmod): stat = '412 Precondition Failed' # handle if match header if_match = environ.get('HTTP_IF_MATCH','').split('"')[1::2] if if_match and (etag not in if_match) and ('*' not in if_match): stat = '412 Precondition Failed' # handle if modified header if_mod = environ.get('HTTP_IF_MODIFIED_SINCE') if if_mod and rfc822.parsedate(if_mod) >= rfc822.parsedate(lmod): stat = '304 Not Modified' # handle if none match header if_none = environ.get('HTTP_IF_NONE_MATCH','').split('"')[1::2] if if_none and (etag in if_none or '*' in if_none): stat = '304 Not Modified' return stat # graceful shutdown def shutdown(): global databases for db in databases.values(): db.close() databases = {}