Variable | Value |
---|---|
$k | $prettify(v) |
No data.
+ +Python | +$frames[0].filename in $frames[0].function, line $frames[0].lineno | +
---|---|
Web | +$ctx.method $ctx.home$ctx.path | +
$frame.filename
in $frame.function
+ $if frame.context_line is not None:
+
+ $ctx.output
+
+ You're seeing this error because you have web.config.debug
+ set to True
. Set that to False
if you don't to see this.
+
%s |
' + net.websafe(result) + ''] + return profile_internal + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/contrib/web/httpserver.py b/contrib/web/httpserver.py new file mode 100644 index 0000000..f2c91cc --- /dev/null +++ b/contrib/web/httpserver.py @@ -0,0 +1,250 @@ +__all__ = ["runsimple"] + +import sys, os +from SimpleHTTPServer import SimpleHTTPRequestHandler + +import webapi as web +import net +import utils + +def runbasic(func, server_address=("0.0.0.0", 8080)): + """ + Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` + is hosted statically. + + Based on [WsgiServer][ws] from [Colin Stewart][cs]. + + [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html + [cs]: http://www.owlfish.com/ + """ + # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + # Modified somewhat for simplicity + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + + import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse + import socket, errno + import traceback + + class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def run_wsgi_app(self): + protocol, host, path, parameters, query, fragment = \ + urlparse.urlparse('http://dummyhost%s' % self.path) + + # we only use path, query + env = {'wsgi.version': (1, 0) + ,'wsgi.url_scheme': 'http' + ,'wsgi.input': self.rfile + ,'wsgi.errors': sys.stderr + ,'wsgi.multithread': 1 + ,'wsgi.multiprocess': 0 + ,'wsgi.run_once': 0 + ,'REQUEST_METHOD': self.command + ,'REQUEST_URI': self.path + ,'PATH_INFO': path + ,'QUERY_STRING': query + ,'CONTENT_TYPE': self.headers.get('Content-Type', '') + ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') + ,'REMOTE_ADDR': self.client_address[0] + ,'SERVER_NAME': self.server.server_address[0] + ,'SERVER_PORT': str(self.server.server_address[1]) + ,'SERVER_PROTOCOL': self.request_version + } + + for http_header, http_value in self.headers.items(): + env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ + http_value + + # Setup the state + self.wsgi_sent_headers = 0 + self.wsgi_headers = [] + + try: + # We have there environment, now invoke the application + result = self.server.app(env, self.wsgi_start_response) + try: + try: + for data in result: + if data: + self.wsgi_write_data(data) + finally: + if hasattr(result, 'close'): + result.close() + except socket.error, socket_err: + # Catch common network errors and suppress them + if (socket_err.args[0] in \ + (errno.ECONNABORTED, errno.EPIPE)): + return + except socket.timeout, socket_timeout: + return + except: + print >> web.debug, traceback.format_exc(), + + if (not self.wsgi_sent_headers): + # We must write out something! + self.wsgi_write_data(" ") + return + + do_POST = run_wsgi_app + do_PUT = run_wsgi_app + do_DELETE = run_wsgi_app + + def do_GET(self): + if self.path.startswith('/static/'): + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + else: + self.run_wsgi_app() + + def wsgi_start_response(self, response_status, response_headers, + exc_info=None): + if (self.wsgi_sent_headers): + raise Exception \ + ("Headers already sent and start_response called again!") + # Should really take a copy to avoid changes in the application.... + self.wsgi_headers = (response_status, response_headers) + return self.wsgi_write_data + + def wsgi_write_data(self, data): + if (not self.wsgi_sent_headers): + status, headers = self.wsgi_headers + # Need to send header prior to data + status_code = status[:status.find(' ')] + status_msg = status[status.find(' ') + 1:] + self.send_response(int(status_code), status_msg) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + self.wsgi_sent_headers = 1 + # Send the data + self.wfile.write(data) + + class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + def __init__(self, func, server_address): + BaseHTTPServer.HTTPServer.__init__(self, + server_address, + WSGIHandler) + self.app = func + self.serverShuttingDown = 0 + + print "http://%s:%d/" % server_address + WSGIServer(func, server_address).serve_forever() + +def runsimple(func, server_address=("0.0.0.0", 8080)): + """ + Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. + The directory `static/` is hosted statically. + + [cp]: http://www.cherrypy.org + """ + func = StaticMiddleware(func) + func = LogMiddleware(func) + + server = WSGIServer(server_address, func) + + print "http://%s:%d/" % server_address + try: + server.start() + except KeyboardInterrupt: + server.stop() + +def WSGIServer(server_address, wsgi_app): + """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`. + This function can be overwritten to customize the webserver or use a different webserver. + """ + from wsgiserver import CherryPyWSGIServer + return CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost") + +class StaticApp(SimpleHTTPRequestHandler): + """WSGI application for serving static files.""" + def __init__(self, environ, start_response): + self.headers = [] + self.environ = environ + self.start_response = start_response + + def send_response(self, status, msg=""): + self.status = str(status) + " " + msg + + def send_header(self, name, value): + self.headers.append((name, value)) + + def end_headers(self): + pass + + def log_message(*a): pass + + def __iter__(self): + environ = self.environ + + self.path = environ.get('PATH_INFO', '') + self.client_address = environ.get('REMOTE_ADDR','-'), \ + environ.get('REMOTE_PORT','-') + self.command = environ.get('REQUEST_METHOD', '-') + + from cStringIO import StringIO + self.wfile = StringIO() # for capturing error + + f = self.send_head() + self.start_response(self.status, self.headers) + + if f: + block_size = 16 * 1024 + while True: + buf = f.read(block_size) + if not buf: + break + yield buf + f.close() + else: + value = self.wfile.getvalue() + yield value + +class StaticMiddleware: + """WSGI middleware for serving static files.""" + def __init__(self, app, prefix='/static/'): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + path = environ.get('PATH_INFO', '') + if path.startswith(self.prefix): + return StaticApp(environ, start_response) + else: + return self.app(environ, start_response) + +class LogMiddleware: + """WSGI middleware for logging the status.""" + def __init__(self, app): + self.app = app + self.format = '%s - - [%s] "%s %s %s" - %s' + + from BaseHTTPServer import BaseHTTPRequestHandler + import StringIO + f = StringIO.StringIO() + + class FakeSocket: + def makefile(self, *a): + return f + + # take log_date_time_string method from BaseHTTPRequestHandler + self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string + + def __call__(self, environ, start_response): + def xstart_response(status, response_headers, *args): + out = start_response(status, response_headers, *args) + self.log(status, environ) + return out + + return self.app(environ, xstart_response) + + def log(self, status, environ): + outfile = environ.get('wsgi.errors', web.debug) + req = environ.get('PATH_INFO', '_') + protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') + method = environ.get('REQUEST_METHOD', '-') + host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), + environ.get('REMOTE_PORT','-')) + + time = self.log_date_time_string() + + msg = self.format % (host, time, protocol, method, req, status) + print >> outfile, utils.safestr(msg) diff --git a/contrib/web/net.py b/contrib/web/net.py new file mode 100644 index 0000000..6c3ee85 --- /dev/null +++ b/contrib/web/net.py @@ -0,0 +1,190 @@ +""" +Network Utilities +(from web.py) +""" + +__all__ = [ + "validipaddr", "validipport", "validip", "validaddr", + "urlquote", + "httpdate", "parsehttpdate", + "htmlquote", "htmlunquote", "websafe", +] + +import urllib, time +try: import datetime +except ImportError: pass + +def validipaddr(address): + """ + Returns True if `address` is a valid IPv4 address. + + >>> validipaddr('192.168.1.1') + True + >>> validipaddr('192.168.1.800') + False + >>> validipaddr('192.168.1') + False + """ + try: + octets = address.split('.') + if len(octets) != 4: + return False + for x in octets: + if not (0 <= int(x) <= 255): + return False + except ValueError: + return False + return True + +def validipport(port): + """ + Returns True if `port` is a valid IPv4 port. + + >>> validipport('9000') + True + >>> validipport('foo') + False + >>> validipport('1000000') + False + """ + try: + if not (0 <= int(port) <= 65535): + return False + except ValueError: + return False + return True + +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080): + """Returns `(ip_address, port)` from string `ip_addr_port`""" + addr = defaultaddr + port = defaultport + + ip = ip.split(":", 1) + if len(ip) == 1: + if not ip[0]: + pass + elif validipaddr(ip[0]): + addr = ip[0] + elif validipport(ip[0]): + port = int(ip[0]) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + elif len(ip) == 2: + addr, port = ip + if not validipaddr(addr) and validipport(port): + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + port = int(port) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + return (addr, port) + +def validaddr(string_): + """ + Returns either (ip_address, port) or "/path/to/socket" from string_ + + >>> validaddr('/path/to/socket') + '/path/to/socket' + >>> validaddr('8000') + ('0.0.0.0', 8000) + >>> validaddr('127.0.0.1') + ('127.0.0.1', 8080) + >>> validaddr('127.0.0.1:8000') + ('127.0.0.1', 8000) + >>> validaddr('fff') + Traceback (most recent call last): + ... + ValueError: fff is not a valid IP address/port + """ + if '/' in string_: + return string_ + else: + return validip(string_) + +def urlquote(val): + """ + Quotes a string for use in a URL. + + >>> urlquote('://?f=1&j=1') + '%3A//%3Ff%3D1%26j%3D1' + >>> urlquote(None) + '' + >>> urlquote(u'\u203d') + '%E2%80%BD' + """ + if val is None: return '' + if not isinstance(val, unicode): val = str(val) + else: val = val.encode('utf-8') + return urllib.quote(val) + +def httpdate(date_obj): + """ + Formats a datetime object for use in HTTP headers. + + >>> import datetime + >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) + 'Thu, 01 Jan 1970 01:01:01 GMT' + """ + return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT") + +def parsehttpdate(string_): + """ + Parses an HTTP date into a datetime object. + + >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT') + datetime.datetime(1970, 1, 1, 1, 1, 1) + """ + try: + t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z") + except ValueError: + return None + return datetime.datetime(*t[:6]) + +def htmlquote(text): + """ + Encodes `text` for raw use in HTML. + + >>> htmlquote("<'&\\">") + '<'&">' + """ + text = text.replace("&", "&") # Must be done first! + text = text.replace("<", "<") + text = text.replace(">", ">") + text = text.replace("'", "'") + text = text.replace('"', """) + return text + +def htmlunquote(text): + """ + Decodes `text` that's HTML quoted. + + >>> htmlunquote('<'&">') + '<\\'&">' + """ + text = text.replace(""", '"') + text = text.replace("'", "'") + text = text.replace(">", ">") + text = text.replace("<", "<") + text = text.replace("&", "&") # Must be done last! + return text + +def websafe(val): + """ + Converts `val` so that it's safe for use in UTF-8 HTML. + + >>> websafe("<'&\\">") + '<'&">' + >>> websafe(None) + '' + >>> websafe(u'\u203d') + '\\xe2\\x80\\xbd' + """ + if val is None: + return '' + if isinstance(val, unicode): + val = val.encode('utf-8') + val = str(val) + return htmlquote(val) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/contrib/web/session.py b/contrib/web/session.py new file mode 100644 index 0000000..a260318 --- /dev/null +++ b/contrib/web/session.py @@ -0,0 +1,319 @@ +""" +Session Management +(from web.py) +""" + +import os, time, datetime, random, base64 +try: + import cPickle as pickle +except ImportError: + import pickle +try: + import hashlib + sha1 = hashlib.sha1 +except ImportError: + import sha + sha1 = sha.new + +import utils +import webapi as web + +__all__ = [ + 'Session', 'SessionExpired', + 'Store', 'DiskStore', 'DBStore', +] + +web.config.session_parameters = utils.storage({ + 'cookie_name': 'webpy_session_id', + 'cookie_domain': None, + 'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds + 'ignore_expiry': True, + 'ignore_change_ip': True, + 'secret_key': 'fLjUfxqXtfNoIldA0A0J', + 'expired_message': 'Session expired', +}) + +class SessionExpired(web.HTTPError): + def __init__(self, message): + web.HTTPError.__init__(self, '200 OK', {}, data=message) + +class Session(utils.ThreadedDict): + """Session management for web.py + """ + + def __init__(self, app, store, initializer=None): + self.__dict__['store'] = store + self.__dict__['_initializer'] = initializer + self.__dict__['_last_cleanup_time'] = 0 + self.__dict__['_config'] = utils.storage(web.config.session_parameters) + + if app: + app.add_processor(self._processor) + + def _processor(self, handler): + """Application processor to setup session for every request""" + self._cleanup() + self._load() + + try: + return handler() + finally: + self._save() + + def _load(self): + """Load the session from the store, by the id from cookie""" + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + self.session_id = web.cookies().get(cookie_name) + + # protection against session_id tampering + if self.session_id and not self._valid_session_id(self.session_id): + self.session_id = None + + self._check_expiry() + if self.session_id: + d = self.store[self.session_id] + self.update(d) + self._validate_ip() + + if not self.session_id: + self.session_id = self._generate_session_id() + + if self._initializer: + if isinstance(self._initializer, dict): + self.update(self._initializer) + elif hasattr(self._initializer, '__call__'): + self._initializer() + + self.ip = web.ctx.ip + + def _check_expiry(self): + # check for expiry + if self.session_id and self.session_id not in self.store: + if self._config.ignore_expiry: + self.session_id = None + else: + return self.expired() + + def _validate_ip(self): + # check for change of IP + if self.session_id and self.get('ip', None) != web.ctx.ip: + if not self._config.ignore_change_ip: + return self.expired() + + def _save(self): + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + if not self.get('_killed'): + web.setcookie(cookie_name, self.session_id, domain=cookie_domain) + self.store[self.session_id] = dict(self) + else: + web.setcookie(cookie_name, self.session_id, expires=-1, domain=cookie_domain) + + def _generate_session_id(self): + """Generate a random id for session""" + + while True: + rand = os.urandom(16) + now = time.time() + secret_key = self._config.secret_key + session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key)) + session_id = session_id.hexdigest() + if session_id not in self.store: + break + return session_id + + def _valid_session_id(self, session_id): + rx = utils.re_compile('^[0-9a-fA-F]+$') + return rx.match(session_id) + + def _cleanup(self): + """Cleanup the stored sessions""" + current_time = time.time() + timeout = self._config.timeout + if current_time - self._last_cleanup_time > timeout: + self.store.cleanup(timeout) + self.__dict__['_last_cleanup_time'] = current_time + + def expired(self): + """Called when an expired session is atime""" + self._killed = True + self._save() + raise SessionExpired(self._config.expired_message) + + def kill(self): + """Kill the session, make it no longer available""" + del self.store[self.session_id] + self._killed = True + +class Store: + """Base class for session stores""" + + def __contains__(self, key): + raise NotImplementedError + + def __getitem__(self, key): + raise NotImplementedError + + def __setitem__(self, key, value): + raise NotImplementedError + + def cleanup(self, timeout): + """removes all the expired sessions""" + raise NotImplementedError + + def encode(self, session_dict): + """encodes session dict as a string""" + pickled = pickle.dumps(session_dict) + return base64.encodestring(pickled) + + def decode(self, session_data): + """decodes the data to get back the session dict """ + pickled = base64.decodestring(session_data) + return pickle.loads(pickled) + +class DiskStore(Store): + """ + Store for saving a session on disk. + + >>> import tempfile + >>> root = tempfile.mkdtemp() + >>> s = DiskStore(root) + >>> s['a'] = 'foo' + >>> s['a'] + 'foo' + >>> time.sleep(0.01) + >>> s.cleanup(0.01) + >>> s['a'] + Traceback (most recent call last): + ... + KeyError: 'a' + """ + def __init__(self, root): + # if the storage root doesn't exists, create it. + if not os.path.exists(root): + os.mkdir(root) + self.root = root + + def _get_path(self, key): + if os.path.sep in key: + raise ValueError, "Bad key: %s" % repr(key) + return os.path.join(self.root, key) + + def __contains__(self, key): + path = self._get_path(key) + return os.path.exists(path) + + def __getitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + pickled = open(path).read() + return self.decode(pickled) + else: + raise KeyError, key + + def __setitem__(self, key, value): + path = self._get_path(key) + pickled = self.encode(value) + try: + f = open(path, 'w') + try: + f.write(pickled) + finally: + f.close() + except IOError: + pass + + def __delitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + os.remove(path) + + def cleanup(self, timeout): + now = time.time() + for f in os.listdir(self.root): + path = self._get_path(f) + atime = os.stat(path).st_atime + if now - atime > timeout : + os.remove(path) + +class DBStore(Store): + """Store for saving a session in database + Needs a table with the following columns: + + session_id CHAR(128) UNIQUE NOT NULL, + atime DATETIME NOT NULL default current_timestamp, + data TEXT + """ + def __init__(self, db, table_name): + self.db = db + self.table = table_name + + def __contains__(self, key): + data = self.db.select(self.table, where="session_id=$key", vars=locals()) + return bool(list(data)) + + def __getitem__(self, key): + now = datetime.datetime.now() + try: + s = self.db.select(self.table, where="session_id=$key", vars=locals())[0] + self.db.update(self.table, where="session_id=$key", atime=now, vars=locals()) + except IndexError: + raise KeyError + else: + return self.decode(s.data) + + def __setitem__(self, key, value): + pickled = self.encode(value) + now = datetime.datetime.now() + if key in self: + self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals()) + else: + self.db.insert(self.table, False, session_id=key, data=pickled ) + + def __delitem__(self, key): + self.db.delete(self.table, where="session_id=$key", vars=locals()) + + def cleanup(self, timeout): + timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg + last_allowed_time = datetime.datetime.now() - timeout + self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals()) + +class ShelfStore: + """Store for saving session using `shelve` module. + + import shelve + store = ShelfStore(shelve.open('session.shelf')) + + XXX: is shelve thread-safe? + """ + def __init__(self, shelf): + self.shelf = shelf + + def __contains__(self, key): + return key in self.shelf + + def __getitem__(self, key): + atime, v = self.shelf[key] + self[key] = v # update atime + return v + + def __setitem__(self, key, value): + self.shelf[key] = time.time(), value + + def __delitem__(self, key): + try: + del self.shelf[key] + except KeyError: + pass + + def cleanup(self, timeout): + now = time.time() + for k in self.shelf.keys(): + atime, v = self.shelf[k] + if now - atime > timeout : + del self[k] + +if __name__ == '__main__' : + import doctest + doctest.testmod() diff --git a/contrib/web/template.py b/contrib/web/template.py new file mode 100644 index 0000000..ef87200 --- /dev/null +++ b/contrib/web/template.py @@ -0,0 +1,1471 @@ +""" +simple, elegant templating +(part of web.py) + +Template design: + +Template string is split into tokens and the tokens are combined into nodes. +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and +for-loop, if-loop etc are block nodes, which contain multiple child nodes. + +Each node can emit some python string. python string emitted by the +root node is validated for safeeval and executed using python in the given environment. + +Enough care is taken to make sure the generated code and the template has line to line match, +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.) + +Grammar: + + template -> defwith sections + defwith -> '$def with (' arguments ')' | '' + sections -> section* + section -> block | assignment | line + + assignment -> '$ '
Enter a list of URLs to flush (one per line).
+