bottle.py (122190B)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 """ 4 Bottle is a fast and simple micro-framework for small web applications. It 5 offers request dispatching (Routes) with url parameter support, templates, 6 a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and 7 template engines - all in a single file and with no dependencies other than the 8 Python Standard Library. 9 10 Homepage and documentation: http://bottlepy.org/ 11 12 Copyright (c) 2011, Marcel Hellkamp. 13 License: MIT (see LICENSE for details) 14 """ 15 16 from __future__ import with_statement 17 18 __author__ = 'Marcel Hellkamp' 19 __version__ = '0.11.dev' 20 __license__ = 'MIT' 21 22 # The gevent server adapter needs to patch some modules before they are imported 23 # This is why we parse the commandline parameters here but handle them later 24 if __name__ == '__main__': 25 from optparse import OptionParser 26 _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app") 27 _opt = _cmd_parser.add_option 28 _opt("--version", action="store_true", help="show version number.") 29 _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") 30 _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.") 31 _opt("-p", "--plugin", action="append", help="install additional plugin/s.") 32 _opt("--debug", action="store_true", help="start server in debug mode.") 33 _opt("--reload", action="store_true", help="auto-reload on file changes.") 34 _cmd_options, _cmd_args = _cmd_parser.parse_args() 35 if _cmd_options.server and _cmd_options.server.startswith('gevent'): 36 import gevent.monkey; gevent.monkey.patch_all() 37 38 import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\ 39 os, re, subprocess, sys, tempfile, threading, time, urllib, warnings 40 41 from datetime import date as datedate, datetime, timedelta 42 from tempfile import TemporaryFile 43 from traceback import format_exc, print_exc 44 45 try: from json import dumps as json_dumps, loads as json_lds 46 except ImportError: # pragma: no cover 47 try: from simplejson import dumps as json_dumps, loads as json_lds 48 except ImportError: 49 try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds 50 except ImportError: 51 def json_dumps(data): 52 raise ImportError("JSON support requires Python 2.6 or simplejson.") 53 json_lds = json_dumps 54 55 56 57 # We now try to fix 2.5/2.6/3.1/3.2 incompatibilities. 58 # It ain't pretty but it works... Sorry for the mess. 59 60 py = sys.version_info 61 py3k = py >= (3,0,0) 62 py25 = py < (2,6,0) 63 64 # Workaround for the missing "as" keyword in py3k. 65 def _e(): return sys.exc_info()[1] 66 67 # Workaround for the "print is a keyword/function" dilemma. 68 _stdout, _stderr = sys.stdout.write, sys.stderr.write 69 70 # Lots of stdlib and builtin differences. 71 if py3k: 72 import http.client as httplib 73 import _thread as thread 74 from urllib.parse import urljoin, parse_qsl, SplitResult as UrlSplitResult 75 from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote 76 from http.cookies import SimpleCookie 77 from collections import MutableMapping as DictMixin 78 import pickle 79 from io import BytesIO 80 basestring = str 81 unicode = str 82 json_loads = lambda s: json_lds(touni(s)) 83 callable = lambda x: hasattr(x, '__call__') 84 imap = map 85 else: # 2.x 86 import httplib 87 import thread 88 from urlparse import urljoin, SplitResult as UrlSplitResult 89 from urllib import urlencode, quote as urlquote, unquote as urlunquote 90 from Cookie import SimpleCookie 91 from itertools import imap 92 import cPickle as pickle 93 from StringIO import StringIO as BytesIO 94 if py25: 95 msg = "Python 2.5 support may be dropped in future versions of Bottle." 96 warnings.warn(msg, DeprecationWarning) 97 from cgi import parse_qsl 98 from UserDict import DictMixin 99 def next(it): return it.next() 100 bytes = str 101 else: # 2.6, 2.7 102 from urlparse import parse_qsl 103 from collections import MutableMapping as DictMixin 104 json_loads = json_lds 105 106 # Some helpers for string/byte handling 107 def tob(s, enc='utf8'): 108 return s.encode(enc) if isinstance(s, unicode) else bytes(s) 109 def touni(s, enc='utf8', err='strict'): 110 return s.decode(enc, err) if isinstance(s, bytes) else unicode(s) 111 tonat = touni if py3k else tob 112 113 # 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense). 114 # 3.1 needs a workaround. 115 NCTextIOWrapper = None 116 if (3,0,0) < py < (3,2,0): 117 from io import TextIOWrapper 118 class NCTextIOWrapper(TextIOWrapper): 119 def close(self): pass # Keep wrapped buffer open. 120 121 # A bug in functools causes it to break if the wrapper is an instance method 122 def update_wrapper(wrapper, wrapped, *a, **ka): 123 try: functools.update_wrapper(wrapper, wrapped, *a, **ka) 124 except AttributeError: pass 125 126 127 128 # These helpers are used at module level and need to be defined first. 129 # And yes, I know PEP-8, but sometimes a lower-case classname makes more sense. 130 131 def depr(message): 132 warnings.warn(message, DeprecationWarning, stacklevel=3) 133 134 def makelist(data): # This is just to handy 135 if isinstance(data, (tuple, list, set, dict)): return list(data) 136 elif data: return [data] 137 else: return [] 138 139 140 class DictProperty(object): 141 ''' Property that maps to a key in a local dict-like attribute. ''' 142 def __init__(self, attr, key=None, read_only=False): 143 self.attr, self.key, self.read_only = attr, key, read_only 144 145 def __call__(self, func): 146 functools.update_wrapper(self, func, updated=[]) 147 self.getter, self.key = func, self.key or func.__name__ 148 return self 149 150 def __get__(self, obj, cls): 151 if obj is None: return self 152 key, storage = self.key, getattr(obj, self.attr) 153 if key not in storage: storage[key] = self.getter(obj) 154 return storage[key] 155 156 def __set__(self, obj, value): 157 if self.read_only: raise AttributeError("Read-Only property.") 158 getattr(obj, self.attr)[self.key] = value 159 160 def __delete__(self, obj): 161 if self.read_only: raise AttributeError("Read-Only property.") 162 del getattr(obj, self.attr)[self.key] 163 164 165 class cached_property(object): 166 ''' A property that is only computed once per instance and then replaces 167 itself with an ordinary attribute. Deleting the attribute resets the 168 property. ''' 169 170 def __init__(self, func): 171 self.func = func 172 173 def __get__(self, obj, cls): 174 if obj is None: return self 175 value = obj.__dict__[self.func.__name__] = self.func(obj) 176 return value 177 178 179 class lazy_attribute(object): 180 ''' A property that caches itself to the class object. ''' 181 def __init__(self, func): 182 functools.update_wrapper(self, func, updated=[]) 183 self.getter = func 184 185 def __get__(self, obj, cls): 186 value = self.getter(cls) 187 setattr(cls, self.__name__, value) 188 return value 189 190 191 192 193 194 195 ############################################################################### 196 # Exceptions and Events ######################################################## 197 ############################################################################### 198 199 200 class BottleException(Exception): 201 """ A base class for exceptions used by bottle. """ 202 pass 203 204 205 #TODO: This should subclass BaseRequest 206 class HTTPResponse(BottleException): 207 """ Used to break execution and immediately finish the response """ 208 def __init__(self, output='', status=200, header=None): 209 super(BottleException, self).__init__("HTTP Response %d" % status) 210 self.status = int(status) 211 self.output = output 212 self.headers = HeaderDict(header) if header else None 213 214 def apply(self, response): 215 if self.headers: 216 for key, value in self.headers.allitems(): 217 response.headers[key] = value 218 response.status = self.status 219 220 221 class HTTPError(HTTPResponse): 222 """ Used to generate an error page """ 223 def __init__(self, code=500, output='Unknown Error', exception=None, 224 traceback=None, header=None): 225 super(HTTPError, self).__init__(output, code, header) 226 self.exception = exception 227 self.traceback = traceback 228 229 def __repr__(self): 230 return tonat(template(ERROR_PAGE_TEMPLATE, e=self)) 231 232 233 234 235 236 237 ############################################################################### 238 # Routing ###################################################################### 239 ############################################################################### 240 241 242 class RouteError(BottleException): 243 """ This is a base class for all routing related exceptions """ 244 245 246 class RouteReset(BottleException): 247 """ If raised by a plugin or request handler, the route is reset and all 248 plugins are re-applied. """ 249 250 class RouterUnknownModeError(RouteError): pass 251 252 253 class RouteSyntaxError(RouteError): 254 """ The route parser found something not supported by this router """ 255 256 257 class RouteBuildError(RouteError): 258 """ The route could not been built """ 259 260 261 class Router(object): 262 ''' A Router is an ordered collection of route->target pairs. It is used to 263 efficiently match WSGI requests against a number of routes and return 264 the first target that satisfies the request. The target may be anything, 265 usually a string, ID or callable object. A route consists of a path-rule 266 and a HTTP method. 267 268 The path-rule is either a static path (e.g. `/contact`) or a dynamic 269 path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax 270 and details on the matching order are described in docs:`routing`. 271 ''' 272 273 default_pattern = '[^/]+' 274 default_filter = 're' 275 #: Sorry for the mess. It works. Trust me. 276 rule_syntax = re.compile('(\\\\*)'\ 277 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ 278 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ 279 '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))') 280 281 def __init__(self, strict=False): 282 self.rules = {} # A {rule: Rule} mapping 283 self.builder = {} # A rule/name->build_info mapping 284 self.static = {} # Cache for static routes: {path: {method: target}} 285 self.dynamic = [] # Cache for dynamic routes. See _compile() 286 #: If true, static routes are no longer checked first. 287 self.strict_order = strict 288 self.filters = {'re': self.re_filter, 'int': self.int_filter, 289 'float': self.float_filter, 'path': self.path_filter} 290 291 def re_filter(self, conf): 292 return conf or self.default_pattern, None, None 293 294 def int_filter(self, conf): 295 return r'-?\d+', int, lambda x: str(int(x)) 296 297 def float_filter(self, conf): 298 return r'-?[\d.]+', float, lambda x: str(float(x)) 299 300 def path_filter(self, conf): 301 return r'.+?', None, None 302 303 def add_filter(self, name, func): 304 ''' Add a filter. The provided function is called with the configuration 305 string as parameter and must return a (regexp, to_python, to_url) tuple. 306 The first element is a string, the last two are callables or None. ''' 307 self.filters[name] = func 308 309 def parse_rule(self, rule): 310 ''' Parses a rule into a (name, filter, conf) token stream. If mode is 311 None, name contains a static rule part. ''' 312 offset, prefix = 0, '' 313 for match in self.rule_syntax.finditer(rule): 314 prefix += rule[offset:match.start()] 315 g = match.groups() 316 if len(g[0])%2: # Escaped wildcard 317 prefix += match.group(0)[len(g[0]):] 318 offset = match.end() 319 continue 320 if prefix: yield prefix, None, None 321 name, filtr, conf = g[1:4] if not g[2] is None else g[4:7] 322 if not filtr: filtr = self.default_filter 323 yield name, filtr, conf or None 324 offset, prefix = match.end(), '' 325 if offset <= len(rule) or prefix: 326 yield prefix+rule[offset:], None, None 327 328 def add(self, rule, method, target, name=None): 329 ''' Add a new route or replace the target for an existing route. ''' 330 if rule in self.rules: 331 self.rules[rule][method] = target 332 if name: self.builder[name] = self.builder[rule] 333 return 334 335 target = self.rules[rule] = {method: target} 336 337 # Build pattern and other structures for dynamic routes 338 anons = 0 # Number of anonymous wildcards 339 pattern = '' # Regular expression pattern 340 filters = [] # Lists of wildcard input filters 341 builder = [] # Data structure for the URL builder 342 is_static = True 343 for key, mode, conf in self.parse_rule(rule): 344 if mode: 345 is_static = False 346 mask, in_filter, out_filter = self.filters[mode](conf) 347 if key: 348 pattern += '(?P<%s>%s)' % (key, mask) 349 else: 350 pattern += '(?:%s)' % mask 351 key = 'anon%d' % anons; anons += 1 352 if in_filter: filters.append((key, in_filter)) 353 builder.append((key, out_filter or str)) 354 elif key: 355 pattern += re.escape(key) 356 builder.append((None, key)) 357 self.builder[rule] = builder 358 if name: self.builder[name] = builder 359 360 if is_static and not self.strict_order: 361 self.static[self.build(rule)] = target 362 return 363 364 def fpat_sub(m): 365 return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:' 366 flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern) 367 368 try: 369 re_match = re.compile('^(%s)$' % pattern).match 370 except re.error: 371 raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e())) 372 373 def match(path): 374 """ Return an url-argument dictionary. """ 375 url_args = re_match(path).groupdict() 376 for name, wildcard_filter in filters: 377 try: 378 url_args[name] = wildcard_filter(url_args[name]) 379 except ValueError: 380 raise HTTPError(400, 'Path has wrong format.') 381 return url_args 382 383 try: 384 combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern) 385 self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1]) 386 self.dynamic[-1][1].append((match, target)) 387 except (AssertionError, IndexError): # AssertionError: Too many groups 388 self.dynamic.append((re.compile('(^%s$)' % flat_pattern), 389 [(match, target)])) 390 return match 391 392 def build(self, _name, *anons, **query): 393 ''' Build an URL by filling the wildcards in a rule. ''' 394 builder = self.builder.get(_name) 395 if not builder: raise RouteBuildError("No route with that name.", _name) 396 try: 397 for i, value in enumerate(anons): query['anon%d'%i] = value 398 url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder]) 399 return url if not query else url+'?'+urlencode(query) 400 except KeyError: 401 raise RouteBuildError('Missing URL argument: %r' % _e().args[0]) 402 403 def match(self, environ): 404 ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). ''' 405 path, targets, urlargs = environ['PATH_INFO'] or '/', None, {} 406 if path in self.static: 407 targets = self.static[path] 408 else: 409 for combined, rules in self.dynamic: 410 match = combined.match(path) 411 if not match: continue 412 getargs, targets = rules[match.lastindex - 1] 413 urlargs = getargs(path) if getargs else {} 414 break 415 416 if not targets: 417 raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO'])) 418 method = environ['REQUEST_METHOD'].upper() 419 if method in targets: 420 return targets[method], urlargs 421 if method == 'HEAD' and 'GET' in targets: 422 return targets['GET'], urlargs 423 if 'ANY' in targets: 424 return targets['ANY'], urlargs 425 allowed = [verb for verb in targets if verb != 'ANY'] 426 if 'GET' in allowed and 'HEAD' not in allowed: 427 allowed.append('HEAD') 428 raise HTTPError(405, "Method not allowed.", 429 header=[('Allow',",".join(allowed))]) 430 431 432 class Route(object): 433 ''' This class wraps a route callback along with route specific metadata and 434 configuration and applies Plugins on demand. It is also responsible for 435 turing an URL path rule into a regular expression usable by the Router. 436 ''' 437 438 def __init__(self, app, rule, method, callback, name=None, 439 plugins=None, skiplist=None, **config): 440 #: The application this route is installed to. 441 self.app = app 442 #: The path-rule string (e.g. ``/wiki/:page``). 443 self.rule = rule 444 #: The HTTP method as a string (e.g. ``GET``). 445 self.method = method 446 #: The original callback with no plugins applied. Useful for introspection. 447 self.callback = callback 448 #: The name of the route (if specified) or ``None``. 449 self.name = name or None 450 #: A list of route-specific plugins (see :meth:`Bottle.route`). 451 self.plugins = plugins or [] 452 #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). 453 self.skiplist = skiplist or [] 454 #: Additional keyword arguments passed to the :meth:`Bottle.route` 455 #: decorator are stored in this dictionary. Used for route-specific 456 #: plugin configuration and meta-data. 457 self.config = ConfigDict(config) 458 459 def __call__(self, *a, **ka): 460 depr("Some APIs changed to return Route() instances instead of"\ 461 " callables. Make sure to use the Route.call method and not to"\ 462 " call Route instances directly.") 463 return self.call(*a, **ka) 464 465 @cached_property 466 def call(self): 467 ''' The route callback with all plugins applied. This property is 468 created on demand and then cached to speed up subsequent requests.''' 469 return self._make_callback() 470 471 def reset(self): 472 ''' Forget any cached values. The next time :attr:`call` is accessed, 473 all plugins are re-applied. ''' 474 self.__dict__.pop('call', None) 475 476 def prepare(self): 477 ''' Do all on-demand work immediately (useful for debugging).''' 478 self.call 479 480 @property 481 def _context(self): 482 depr('Switch to Plugin API v2 and access the Route object directly.') 483 return dict(rule=self.rule, method=self.method, callback=self.callback, 484 name=self.name, app=self.app, config=self.config, 485 apply=self.plugins, skip=self.skiplist) 486 487 def all_plugins(self): 488 ''' Yield all Plugins affecting this route. ''' 489 unique = set() 490 for p in reversed(self.app.plugins + self.plugins): 491 if True in self.skiplist: break 492 name = getattr(p, 'name', False) 493 if name and (name in self.skiplist or name in unique): continue 494 if p in self.skiplist or type(p) in self.skiplist: continue 495 if name: unique.add(name) 496 yield p 497 498 def _make_callback(self): 499 callback = self.callback 500 for plugin in self.all_plugins(): 501 try: 502 if hasattr(plugin, 'apply'): 503 api = getattr(plugin, 'api', 1) 504 context = self if api > 1 else self._context 505 callback = plugin.apply(callback, context) 506 else: 507 callback = plugin(callback) 508 except RouteReset: # Try again with changed configuration. 509 return self._make_callback() 510 if not callback is self.callback: 511 update_wrapper(callback, self.callback) 512 return callback 513 514 def __repr__(self): 515 return '<%s %r %r>' % (self.method, self.rule, self.callback) 516 517 518 519 520 521 522 ############################################################################### 523 # Application Object ########################################################### 524 ############################################################################### 525 526 527 class Bottle(object): 528 """ Each Bottle object represents a single, distinct web application and 529 consists of routes, callbacks, plugins and configuration. Instances are 530 callable WSGI applications. """ 531 532 def __init__(self, catchall=True, autojson=True, config=None): 533 self.routes = [] # List of installed :class:`Route` instances. 534 self.router = Router() # Maps requests to :class:`Route` instances. 535 self.plugins = [] # List of installed plugins. 536 537 self.error_handler = {} 538 self.config = ConfigDict(config or {}) 539 #: If true, most exceptions are catched and returned as :exc:`HTTPError` 540 self.catchall = catchall 541 #: An instance of :class:`HooksPlugin`. Empty by default. 542 self.hooks = HooksPlugin() 543 self.install(self.hooks) 544 if autojson: 545 self.install(JSONPlugin()) 546 self.install(TemplatePlugin()) 547 548 def mount(self, prefix, app, **options): 549 ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific 550 URL prefix. Example:: 551 552 root_app.mount('/admin/', admin_app) 553 554 :param prefix: path prefix or `mount-point`. If it ends in a slash, 555 that slash is mandatory. 556 :param app: an instance of :class:`Bottle` or a WSGI application. 557 558 All other parameters are passed to the underlying :meth:`route` call. 559 ''' 560 if isinstance(app, basestring): 561 prefix, app = app, prefix 562 depr('Parameter order of Bottle.mount() changed.') # 0.10 563 564 parts = [p for p in prefix.split('/') if p] 565 if not parts: raise ValueError('Empty path prefix.') 566 path_depth = len(parts) 567 options.setdefault('skip', True) 568 options.setdefault('method', 'ANY') 569 570 @self.route('/%s/:#.*#' % '/'.join(parts), **options) 571 def mountpoint(): 572 try: 573 request.path_shift(path_depth) 574 rs = BaseResponse([], 200) 575 def start_response(status, header): 576 rs.status = status 577 for name, value in header: rs.add_header(name, value) 578 return rs.body.append 579 rs.body = itertools.chain(rs.body, app(request.environ, start_response)) 580 return HTTPResponse(rs.body, rs.status_code, rs.headers) 581 finally: 582 request.path_shift(-path_depth) 583 584 if not prefix.endswith('/'): 585 self.route('/' + '/'.join(parts), callback=mountpoint, **options) 586 587 def merge(self, routes): 588 ''' Merge the routes of another :cls:`Bottle` application or a list of 589 :class:`Route` objects into this application. The routes keep their 590 'owner', meaning that the :data:`Route.app` attribute is not 591 changed. ''' 592 if isinstance(routes, Bottle): 593 routes = routes.routes 594 for route in routes: 595 self.add_route(route) 596 597 def install(self, plugin): 598 ''' Add a plugin to the list of plugins and prepare it for being 599 applied to all routes of this application. A plugin may be a simple 600 decorator or an object that implements the :class:`Plugin` API. 601 ''' 602 if hasattr(plugin, 'setup'): plugin.setup(self) 603 if not callable(plugin) and not hasattr(plugin, 'apply'): 604 raise TypeError("Plugins must be callable or implement .apply()") 605 self.plugins.append(plugin) 606 self.reset() 607 return plugin 608 609 def uninstall(self, plugin): 610 ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type 611 object to remove all plugins that match that type, a string to remove 612 all plugins with a matching ``name`` attribute or ``True`` to remove all 613 plugins. Return the list of removed plugins. ''' 614 removed, remove = [], plugin 615 for i, plugin in list(enumerate(self.plugins))[::-1]: 616 if remove is True or remove is plugin or remove is type(plugin) \ 617 or getattr(plugin, 'name', True) == remove: 618 removed.append(plugin) 619 del self.plugins[i] 620 if hasattr(plugin, 'close'): plugin.close() 621 if removed: self.reset() 622 return removed 623 624 def run(self, **kwargs): 625 ''' Calls :func:`run` with the same parameters. ''' 626 run(self, **kwargs) 627 628 def reset(self, route=None): 629 ''' Reset all routes (force plugins to be re-applied) and clear all 630 caches. If an ID or route object is given, only that specific route 631 is affected. ''' 632 if route is None: routes = self.routes 633 elif isinstance(route, Route): routes = [route] 634 else: routes = [self.routes[route]] 635 for route in routes: route.reset() 636 if DEBUG: 637 for route in routes: route.prepare() 638 self.hooks.trigger('app_reset') 639 640 def close(self): 641 ''' Close the application and all installed plugins. ''' 642 for plugin in self.plugins: 643 if hasattr(plugin, 'close'): plugin.close() 644 self.stopped = True 645 646 def match(self, environ): 647 """ Search for a matching route and return a (:class:`Route` , urlargs) 648 tuple. The second value is a dictionary with parameters extracted 649 from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" 650 return self.router.match(environ) 651 652 def get_url(self, routename, **kargs): 653 """ Return a string that matches a named route """ 654 scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' 655 location = self.router.build(routename, **kargs).lstrip('/') 656 return urljoin(urljoin('/', scriptname), location) 657 658 def add_route(self, route): 659 ''' Add a route object, but do not change the :data:`Route.app` 660 attribute.''' 661 self.routes.append(route) 662 self.router.add(route.rule, route.method, route, name=route.name) 663 if DEBUG: route.prepare() 664 665 def route(self, path=None, method='GET', callback=None, name=None, 666 apply=None, skip=None, **config): 667 """ A decorator to bind a function to a request URL. Example:: 668 669 @app.route('/hello/:name') 670 def hello(name): 671 return 'Hello %s' % name 672 673 The ``:name`` part is a wildcard. See :class:`Router` for syntax 674 details. 675 676 :param path: Request path or a list of paths to listen to. If no 677 path is specified, it is automatically generated from the 678 signature of the function. 679 :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of 680 methods to listen to. (default: `GET`) 681 :param callback: An optional shortcut to avoid the decorator 682 syntax. ``route(..., callback=func)`` equals ``route(...)(func)`` 683 :param name: The name for this route. (default: None) 684 :param apply: A decorator or plugin or a list of plugins. These are 685 applied to the route callback in addition to installed plugins. 686 :param skip: A list of plugins, plugin classes or names. Matching 687 plugins are not installed to this route. ``True`` skips all. 688 689 Any additional keyword arguments are stored as route-specific 690 configuration and passed to plugins (see :meth:`Plugin.apply`). 691 """ 692 if callable(path): path, callback = None, path 693 plugins = makelist(apply) 694 skiplist = makelist(skip) 695 def decorator(callback): 696 # TODO: Documentation and test_data 697 if isinstance(callback, basestring): callback = load(callback) 698 for rule in makelist(path) or yieldroutes(callback): 699 for verb in makelist(method): 700 verb = verb.upper() 701 route = Route(self, rule, verb, callback, name=name, 702 plugins=plugins, skiplist=skiplist, **config) 703 self.add_route(route) 704 return callback 705 return decorator(callback) if callback else decorator 706 707 def get(self, path=None, method='GET', **options): 708 """ Equals :meth:`route`. """ 709 return self.route(path, method, **options) 710 711 def post(self, path=None, method='POST', **options): 712 """ Equals :meth:`route` with a ``POST`` method parameter. """ 713 return self.route(path, method, **options) 714 715 def put(self, path=None, method='PUT', **options): 716 """ Equals :meth:`route` with a ``PUT`` method parameter. """ 717 return self.route(path, method, **options) 718 719 def delete(self, path=None, method='DELETE', **options): 720 """ Equals :meth:`route` with a ``DELETE`` method parameter. """ 721 return self.route(path, method, **options) 722 723 def error(self, code=500): 724 """ Decorator: Register an output handler for a HTTP error code""" 725 def wrapper(handler): 726 self.error_handler[int(code)] = handler 727 return handler 728 return wrapper 729 730 def hook(self, name): 731 """ Return a decorator that attaches a callback to a hook. Three hooks 732 are currently implemented: 733 734 - before_request: Executed once before each request 735 - after_request: Executed once after each request 736 - app_reset: Called whenever :meth:`reset` is called. 737 """ 738 def wrapper(func): 739 self.hooks.add(name, func) 740 return func 741 return wrapper 742 743 def handle(self, path, method='GET'): 744 """ (deprecated) Execute the first matching route callback and return 745 the result. :exc:`HTTPResponse` exceptions are catched and returned. 746 If :attr:`Bottle.catchall` is true, other exceptions are catched as 747 well and returned as :exc:`HTTPError` instances (500). 748 """ 749 depr("This method will change semantics in 0.10. Try to avoid it.") 750 if isinstance(path, dict): 751 return self._handle(path) 752 return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()}) 753 754 def _handle(self, environ): 755 try: 756 environ['bottle.app'] = self 757 request.bind(environ) 758 response.bind() 759 route, args = self.router.match(environ) 760 environ['route.handle'] = environ['bottle.route'] = route 761 environ['route.url_args'] = args 762 return route.call(**args) 763 except HTTPResponse: 764 return _e() 765 except RouteReset: 766 route.reset() 767 return self._handle(environ) 768 except (KeyboardInterrupt, SystemExit, MemoryError): 769 raise 770 except Exception: 771 if not self.catchall: raise 772 stacktrace = format_exc(10) 773 environ['wsgi.errors'].write(stacktrace) 774 return HTTPError(500, "Internal Server Error", _e(), stacktrace) 775 776 def _cast(self, out, peek=None): 777 """ Try to convert the parameter into something WSGI compatible and set 778 correct HTTP headers when possible. 779 Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like, 780 iterable of strings and iterable of unicodes 781 """ 782 783 # Empty output is done here 784 if not out: 785 response['Content-Length'] = 0 786 return [] 787 # Join lists of byte or unicode strings. Mixed lists are NOT supported 788 if isinstance(out, (tuple, list))\ 789 and isinstance(out[0], (bytes, unicode)): 790 out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' 791 # Encode unicode strings 792 if isinstance(out, unicode): 793 out = out.encode(response.charset) 794 # Byte Strings are just returned 795 if isinstance(out, bytes): 796 response['Content-Length'] = len(out) 797 return [out] 798 # HTTPError or HTTPException (recursive, because they may wrap anything) 799 # TODO: Handle these explicitly in handle() or make them iterable. 800 if isinstance(out, HTTPError): 801 out.apply(response) 802 out = self.error_handler.get(out.status, repr)(out) 803 if isinstance(out, HTTPResponse): 804 depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9 805 return self._cast(out) 806 if isinstance(out, HTTPResponse): 807 out.apply(response) 808 return self._cast(out.output) 809 810 # File-like objects. 811 if hasattr(out, 'read'): 812 if 'wsgi.file_wrapper' in request.environ: 813 return request.environ['wsgi.file_wrapper'](out) 814 elif hasattr(out, 'close') or not hasattr(out, '__iter__'): 815 return WSGIFileWrapper(out) 816 817 # Handle Iterables. We peek into them to detect their inner type. 818 try: 819 out = iter(out) 820 first = next(out) 821 while not first: 822 first = next(out) 823 except StopIteration: 824 return self._cast('') 825 except HTTPResponse: 826 first = _e() 827 except (KeyboardInterrupt, SystemExit, MemoryError): 828 raise 829 except Exception: 830 if not self.catchall: raise 831 first = HTTPError(500, 'Unhandled exception', _e(), format_exc(10)) 832 833 # These are the inner types allowed in iterator or generator objects. 834 if isinstance(first, HTTPResponse): 835 return self._cast(first) 836 if isinstance(first, bytes): 837 return itertools.chain([first], out) 838 if isinstance(first, unicode): 839 return imap(lambda x: x.encode(response.charset), 840 itertools.chain([first], out)) 841 return self._cast(HTTPError(500, 'Unsupported response type: %s'\ 842 % type(first))) 843 844 def wsgi(self, environ, start_response): 845 """ The bottle WSGI-interface. """ 846 try: 847 out = self._cast(self._handle(environ)) 848 # rfc2616 section 4.3 849 if response._status_code in (100, 101, 204, 304)\ 850 or request.method == 'HEAD': 851 if hasattr(out, 'close'): out.close() 852 out = [] 853 if isinstance(response._status_line, unicode): 854 response._status_line = str(response._status_line) 855 start_response(response._status_line, list(response.iter_headers())) 856 return out 857 except (KeyboardInterrupt, SystemExit, MemoryError): 858 raise 859 except Exception: 860 if not self.catchall: raise 861 err = '<h1>Critical error while processing request: %s</h1>' \ 862 % html_escape(environ.get('PATH_INFO', '/')) 863 if DEBUG: 864 err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \ 865 '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \ 866 % (html_escape(repr(_e())), html_escape(format_exc(10))) 867 environ['wsgi.errors'].write(err) 868 headers = [('Content-Type', 'text/html; charset=UTF-8')] 869 start_response('500 INTERNAL SERVER ERROR', headers) 870 return [tob(err)] 871 872 def __call__(self, environ, start_response): 873 ''' Each instance of :class:'Bottle' is a WSGI application. ''' 874 return self.wsgi(environ, start_response) 875 876 877 878 879 880 881 ############################################################################### 882 # HTTP and WSGI Tools ########################################################## 883 ############################################################################### 884 885 886 class BaseRequest(object): 887 """ A wrapper for WSGI environment dictionaries that adds a lot of 888 convenient access methods and properties. Most of them are read-only.""" 889 890 #: Maximum size of memory buffer for :attr:`body` in bytes. 891 MEMFILE_MAX = 102400 892 #: Maximum number pr GET or POST parameters per request 893 MAX_PARAMS = 100 894 895 def __init__(self, environ): 896 """ Wrap a WSGI environ dictionary. """ 897 #: The wrapped WSGI environ dictionary. This is the only real attribute. 898 #: All other attributes actually are read-only properties. 899 self.environ = environ 900 environ['bottle.request'] = self 901 902 @DictProperty('environ', 'bottle.app', read_only=True) 903 def app(self): 904 ''' Bottle application handling this request. ''' 905 raise AttributeError('This request is not connected to an application.') 906 907 @property 908 def path(self): 909 ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix 910 broken clients and avoid the "empty path" edge case). ''' 911 return '/' + self.environ.get('PATH_INFO','').lstrip('/') 912 913 @property 914 def method(self): 915 ''' The ``REQUEST_METHOD`` value as an uppercase string. ''' 916 return self.environ.get('REQUEST_METHOD', 'GET').upper() 917 918 @DictProperty('environ', 'bottle.request.headers', read_only=True) 919 def headers(self): 920 ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to 921 HTTP request headers. ''' 922 return WSGIHeaderDict(self.environ) 923 924 def get_header(self, name, default=None): 925 ''' Return the value of a request header, or a given default value. ''' 926 return self.headers.get(name, default) 927 928 @DictProperty('environ', 'bottle.request.cookies', read_only=True) 929 def cookies(self): 930 """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT 931 decoded. Use :meth:`get_cookie` if you expect signed cookies. """ 932 cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')) 933 cookies = list(cookies.values())[:self.MAX_PARAMS] 934 return FormsDict((c.key, c.value) for c in cookies) 935 936 def get_cookie(self, key, default=None, secret=None): 937 """ Return the content of a cookie. To read a `Signed Cookie`, the 938 `secret` must match the one used to create the cookie (see 939 :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing 940 cookie or wrong signature), return a default value. """ 941 value = self.cookies.get(key) 942 if secret and value: 943 dec = cookie_decode(value, secret) # (key, value) tuple or None 944 return dec[1] if dec and dec[0] == key else default 945 return value or default 946 947 @DictProperty('environ', 'bottle.request.query', read_only=True) 948 def query(self): 949 ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These 950 values are sometimes called "URL arguments" or "GET parameters", but 951 not to be confused with "URL wildcards" as they are provided by the 952 :class:`Router`. ''' 953 pairs = parse_qsl(self.query_string, keep_blank_values=True) 954 get = self.environ['bottle.get'] = FormsDict() 955 for key, value in pairs[:self.MAX_PARAMS]: 956 get[key] = value 957 return get 958 959 @DictProperty('environ', 'bottle.request.forms', read_only=True) 960 def forms(self): 961 """ Form values parsed from an `url-encoded` or `multipart/form-data` 962 encoded POST or PUT request body. The result is retuned as a 963 :class:`FormsDict`. All keys and values are strings. File uploads 964 are stored separately in :attr:`files`. """ 965 forms = FormsDict() 966 for name, item in self.POST.allitems(): 967 if not hasattr(item, 'filename'): 968 forms[name] = item 969 return forms 970 971 @DictProperty('environ', 'bottle.request.params', read_only=True) 972 def params(self): 973 """ A :class:`FormsDict` with the combined values of :attr:`query` and 974 :attr:`forms`. File uploads are stored in :attr:`files`. """ 975 params = FormsDict() 976 for key, value in self.query.allitems(): 977 params[key] = value 978 for key, value in self.forms.allitems(): 979 params[key] = value 980 return params 981 982 @DictProperty('environ', 'bottle.request.files', read_only=True) 983 def files(self): 984 """ File uploads parsed from an `url-encoded` or `multipart/form-data` 985 encoded POST or PUT request body. The values are instances of 986 :class:`cgi.FieldStorage`. The most important attributes are: 987 988 filename 989 The filename, if specified; otherwise None; this is the client 990 side filename, *not* the file name on which it is stored (that's 991 a temporary file you don't deal with) 992 file 993 The file(-like) object from which you can read the data. 994 value 995 The value as a *string*; for file uploads, this transparently 996 reads the file every time you request the value. Do not do this 997 on big files. 998 """ 999 files = FormsDict() 1000 for name, item in self.POST.allitems(): 1001 if hasattr(item, 'filename'): 1002 files[name] = item 1003 return files 1004 1005 @DictProperty('environ', 'bottle.request.json', read_only=True) 1006 def json(self): 1007 ''' If the ``Content-Type`` header is ``application/json``, this 1008 property holds the parsed content of the request body. Only requests 1009 smaller than :attr:`MEMFILE_MAX` are processed to avoid memory 1010 exhaustion. ''' 1011 if 'application/json' in self.environ.get('CONTENT_TYPE', '') \ 1012 and 0 < self.content_length < self.MEMFILE_MAX: 1013 return json_loads(self.body.read(self.MEMFILE_MAX)) 1014 return None 1015 1016 @DictProperty('environ', 'bottle.request.body', read_only=True) 1017 def _body(self): 1018 maxread = max(0, self.content_length) 1019 stream = self.environ['wsgi.input'] 1020 body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b') 1021 while maxread > 0: 1022 part = stream.read(min(maxread, self.MEMFILE_MAX)) 1023 if not part: break 1024 body.write(part) 1025 maxread -= len(part) 1026 self.environ['wsgi.input'] = body 1027 body.seek(0) 1028 return body 1029 1030 @property 1031 def body(self): 1032 """ The HTTP request body as a seek-able file-like object. Depending on 1033 :attr:`MEMFILE_MAX`, this is either a temporary file or a 1034 :class:`io.BytesIO` instance. Accessing this property for the first 1035 time reads and replaces the ``wsgi.input`` environ variable. 1036 Subsequent accesses just do a `seek(0)` on the file object. """ 1037 self._body.seek(0) 1038 return self._body 1039 1040 #: An alias for :attr:`query`. 1041 GET = query 1042 1043 @DictProperty('environ', 'bottle.request.post', read_only=True) 1044 def POST(self): 1045 """ The values of :attr:`forms` and :attr:`files` combined into a single 1046 :class:`FormsDict`. Values are either strings (form values) or 1047 instances of :class:`cgi.FieldStorage` (file uploads). 1048 """ 1049 post = FormsDict() 1050 safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi 1051 for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): 1052 if key in self.environ: safe_env[key] = self.environ[key] 1053 if NCTextIOWrapper: 1054 fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n') 1055 else: 1056 fb = self.body 1057 data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True) 1058 for item in (data.list or [])[:self.MAX_PARAMS]: 1059 post[item.name] = item if item.filename else item.value 1060 return post 1061 1062 @property 1063 def COOKIES(self): 1064 ''' Alias for :attr:`cookies` (deprecated). ''' 1065 depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).') 1066 return self.cookies 1067 1068 @property 1069 def url(self): 1070 """ The full request URI including hostname and scheme. If your app 1071 lives behind a reverse proxy or load balancer and you get confusing 1072 results, make sure that the ``X-Forwarded-Host`` header is set 1073 correctly. """ 1074 return self.urlparts.geturl() 1075 1076 @DictProperty('environ', 'bottle.request.urlparts', read_only=True) 1077 def urlparts(self): 1078 ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. 1079 The tuple contains (scheme, host, path, query_string and fragment), 1080 but the fragment is always empty because it is not visible to the 1081 server. ''' 1082 env = self.environ 1083 http = env.get('wsgi.url_scheme', 'http') 1084 host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') 1085 if not host: 1086 # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. 1087 host = env.get('SERVER_NAME', '127.0.0.1') 1088 port = env.get('SERVER_PORT') 1089 if port and port != ('80' if http == 'http' else '443'): 1090 host += ':' + port 1091 path = urlquote(self.fullpath) 1092 return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '') 1093 1094 @property 1095 def fullpath(self): 1096 """ Request path including :attr:`script_name` (if present). """ 1097 return urljoin(self.script_name, self.path.lstrip('/')) 1098 1099 @property 1100 def query_string(self): 1101 """ The raw :attr:`query` part of the URL (everything in between ``?`` 1102 and ``#``) as a string. """ 1103 return self.environ.get('QUERY_STRING', '') 1104 1105 @property 1106 def script_name(self): 1107 ''' The initial portion of the URL's `path` that was removed by a higher 1108 level (server or routing middleware) before the application was 1109 called. This script path is returned with leading and tailing 1110 slashes. ''' 1111 script_name = self.environ.get('SCRIPT_NAME', '').strip('/') 1112 return '/' + script_name + '/' if script_name else '/' 1113 1114 def path_shift(self, shift=1): 1115 ''' Shift path segments from :attr:`path` to :attr:`script_name` and 1116 vice versa. 1117 1118 :param shift: The number of path segments to shift. May be negative 1119 to change the shift direction. (default: 1) 1120 ''' 1121 script = self.environ.get('SCRIPT_NAME','/') 1122 self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift) 1123 1124 @property 1125 def content_length(self): 1126 ''' The request body length as an integer. The client is responsible to 1127 set this header. Otherwise, the real length of the body is unknown 1128 and -1 is returned. In this case, :attr:`body` will be empty. ''' 1129 return int(self.environ.get('CONTENT_LENGTH') or -1) 1130 1131 @property 1132 def is_xhr(self): 1133 ''' True if the request was triggered by a XMLHttpRequest. This only 1134 works with JavaScript libraries that support the `X-Requested-With` 1135 header (most of the popular libraries do). ''' 1136 requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','') 1137 return requested_with.lower() == 'xmlhttprequest' 1138 1139 @property 1140 def is_ajax(self): 1141 ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. ''' 1142 return self.is_xhr 1143 1144 @property 1145 def auth(self): 1146 """ HTTP authentication data as a (user, password) tuple. This 1147 implementation currently supports basic (not digest) authentication 1148 only. If the authentication happened at a higher level (e.g. in the 1149 front web-server or a middleware), the password field is None, but 1150 the user field is looked up from the ``REMOTE_USER`` environ 1151 variable. On any errors, None is returned. """ 1152 basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION','')) 1153 if basic: return basic 1154 ruser = self.environ.get('REMOTE_USER') 1155 if ruser: return (ruser, None) 1156 return None 1157 1158 @property 1159 def remote_route(self): 1160 """ A list of all IPs that were involved in this request, starting with 1161 the client IP and followed by zero or more proxies. This does only 1162 work if all proxies support the ```X-Forwarded-For`` header. Note 1163 that this information can be forged by malicious clients. """ 1164 proxy = self.environ.get('HTTP_X_FORWARDED_FOR') 1165 if proxy: return [ip.strip() for ip in proxy.split(',')] 1166 remote = self.environ.get('REMOTE_ADDR') 1167 return [remote] if remote else [] 1168 1169 @property 1170 def remote_addr(self): 1171 """ The client IP as a string. Note that this information can be forged 1172 by malicious clients. """ 1173 route = self.remote_route 1174 return route[0] if route else None 1175 1176 def copy(self): 1177 """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """ 1178 return Request(self.environ.copy()) 1179 1180 def get(self, value, default=None): return self.environ.get(value, default) 1181 def __getitem__(self, key): return self.environ[key] 1182 def __delitem__(self, key): self[key] = ""; del(self.environ[key]) 1183 def __iter__(self): return iter(self.environ) 1184 def __len__(self): return len(self.environ) 1185 def keys(self): return self.environ.keys() 1186 def __setitem__(self, key, value): 1187 """ Change an environ value and clear all caches that depend on it. """ 1188 1189 if self.environ.get('bottle.request.readonly'): 1190 raise KeyError('The environ dictionary is read-only.') 1191 1192 self.environ[key] = value 1193 todelete = () 1194 1195 if key == 'wsgi.input': 1196 todelete = ('body', 'forms', 'files', 'params', 'post', 'json') 1197 elif key == 'QUERY_STRING': 1198 todelete = ('query', 'params') 1199 elif key.startswith('HTTP_'): 1200 todelete = ('headers', 'cookies') 1201 1202 for key in todelete: 1203 self.environ.pop('bottle.request.'+key, None) 1204 1205 def __repr__(self): 1206 return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url) 1207 1208 def _hkey(s): 1209 return s.title().replace('_','-') 1210 1211 1212 class HeaderProperty(object): 1213 def __init__(self, name, reader=None, writer=str, default=''): 1214 self.name, self.reader, self.writer, self.default = name, reader, writer, default 1215 self.__doc__ = 'Current value of the %r header.' % name.title() 1216 1217 def __get__(self, obj, cls): 1218 if obj is None: return self 1219 value = obj.headers.get(self.name) 1220 return self.reader(value) if (value and self.reader) else (value or self.default) 1221 1222 def __set__(self, obj, value): 1223 if self.writer: value = self.writer(value) 1224 obj.headers[self.name] = value 1225 1226 def __delete__(self, obj): 1227 if self.name in obj.headers: 1228 del obj.headers[self.name] 1229 1230 1231 class BaseResponse(object): 1232 """ Storage class for a response body as well as headers and cookies. 1233 1234 This class does support dict-like case-insensitive item-access to 1235 headers, but is NOT a dict. Most notably, iterating over a response 1236 yields parts of the body and not the headers. 1237 """ 1238 1239 default_status = 200 1240 default_content_type = 'text/html; charset=UTF-8' 1241 1242 # Header blacklist for specific response codes 1243 # (rfc2616 section 10.2.3 and 10.3.5) 1244 bad_headers = { 1245 204: set(('Content-Type',)), 1246 304: set(('Allow', 'Content-Encoding', 'Content-Language', 1247 'Content-Length', 'Content-Range', 'Content-Type', 1248 'Content-Md5', 'Last-Modified'))} 1249 1250 def __init__(self, body='', status=None, **headers): 1251 self._status_line = None 1252 self._status_code = None 1253 self._cookies = None 1254 self._headers = {'Content-Type': [self.default_content_type]} 1255 self.body = body 1256 self.status = status or self.default_status 1257 if headers: 1258 for name, value in headers.items(): 1259 self[name] = value 1260 1261 def copy(self): 1262 ''' Returns a copy of self. ''' 1263 copy = Response() 1264 copy.status = self.status 1265 copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) 1266 return copy 1267 1268 def __iter__(self): 1269 return iter(self.body) 1270 1271 def close(self): 1272 if hasattr(self.body, 'close'): 1273 self.body.close() 1274 1275 @property 1276 def status_line(self): 1277 ''' The HTTP status line as a string (e.g. ``404 Not Found``).''' 1278 return self._status_line 1279 1280 @property 1281 def status_code(self): 1282 ''' The HTTP status code as an integer (e.g. 404).''' 1283 return self._status_code 1284 1285 def _set_status(self, status): 1286 if isinstance(status, int): 1287 code, status = status, _HTTP_STATUS_LINES.get(status) 1288 elif ' ' in status: 1289 status = status.strip() 1290 code = int(status.split()[0]) 1291 else: 1292 raise ValueError('String status line without a reason phrase.') 1293 if not 100 <= code <= 999: raise ValueError('Status code out of range.') 1294 self._status_code = code 1295 self._status_line = status or ('%d Unknown' % code) 1296 1297 def _get_status(self): 1298 return self._status_line 1299 1300 status = property(_get_status, _set_status, None, 1301 ''' A writeable property to change the HTTP response status. It accepts 1302 either a numeric code (100-999) or a string with a custom reason 1303 phrase (e.g. "404 Brain not found"). Both :data:`status_line` and 1304 :data:`status_code` are updated accordingly. The return value is 1305 always a status string. ''') 1306 del _get_status, _set_status 1307 1308 @property 1309 def headers(self): 1310 ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like 1311 view on the response headers. ''' 1312 self.__dict__['headers'] = hdict = HeaderDict() 1313 hdict.dict = self._headers 1314 return hdict 1315 1316 def __contains__(self, name): return _hkey(name) in self._headers 1317 def __delitem__(self, name): del self._headers[_hkey(name)] 1318 def __getitem__(self, name): return self._headers[_hkey(name)][-1] 1319 def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)] 1320 1321 def get_header(self, name, default=None): 1322 ''' Return the value of a previously defined header. If there is no 1323 header with that name, return a default value. ''' 1324 return self._headers.get(_hkey(name), [default])[-1] 1325 1326 def set_header(self, name, value, append=False): 1327 ''' Create a new response header, replacing any previously defined 1328 headers with the same name. ''' 1329 if append: 1330 self.add_header(name, value) 1331 else: 1332 self._headers[_hkey(name)] = [str(value)] 1333 1334 def add_header(self, name, value): 1335 ''' Add an additional response header, not removing duplicates. ''' 1336 self._headers.setdefault(_hkey(name), []).append(str(value)) 1337 1338 def iter_headers(self): 1339 ''' Yield (header, value) tuples, skipping headers that are not 1340 allowed with the current response status code. ''' 1341 headers = self._headers.items() 1342 bad_headers = self.bad_headers.get(self._status_code) 1343 if bad_headers: 1344 headers = [h for h in headers if h[0] not in bad_headers] 1345 for name, values in headers: 1346 for value in values: 1347 yield name, value 1348 if self._cookies: 1349 for c in self._cookies.values(): 1350 yield 'Set-Cookie', c.OutputString() 1351 1352 def wsgiheader(self): 1353 depr('The wsgiheader method is deprecated. See headerlist.') #0.10 1354 return self.headerlist 1355 1356 @property 1357 def headerlist(self): 1358 ''' WSGI conform list of (header, value) tuples. ''' 1359 return list(self.iter_headers()) 1360 1361 content_type = HeaderProperty('Content-Type') 1362 content_length = HeaderProperty('Content-Length', reader=int) 1363 1364 @property 1365 def charset(self): 1366 """ Return the charset specified in the content-type header (default: utf8). """ 1367 if 'charset=' in self.content_type: 1368 return self.content_type.split('charset=')[-1].split(';')[0].strip() 1369 return 'UTF-8' 1370 1371 @property 1372 def COOKIES(self): 1373 """ A dict-like SimpleCookie instance. This should not be used directly. 1374 See :meth:`set_cookie`. """ 1375 depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10 1376 if not self._cookies: 1377 self._cookies = SimpleCookie() 1378 return self._cookies 1379 1380 def set_cookie(self, name, value, secret=None, **options): 1381 ''' Create a new cookie or replace an old one. If the `secret` parameter is 1382 set, create a `Signed Cookie` (described below). 1383 1384 :param name: the name of the cookie. 1385 :param value: the value of the cookie. 1386 :param secret: a signature key required for signed cookies. 1387 1388 Additionally, this method accepts all RFC 2109 attributes that are 1389 supported by :class:`cookie.Morsel`, including: 1390 1391 :param max_age: maximum age in seconds. (default: None) 1392 :param expires: a datetime object or UNIX timestamp. (default: None) 1393 :param domain: the domain that is allowed to read the cookie. 1394 (default: current domain) 1395 :param path: limits the cookie to a given path (default: current path) 1396 :param secure: limit the cookie to HTTPS connections (default: off). 1397 :param httponly: prevents client-side javascript to read this cookie 1398 (default: off, requires Python 2.6 or newer). 1399 1400 If neither `expires` nor `max_age` is set (default), the cookie will 1401 expire at the end of the browser session (as soon as the browser 1402 window is closed). 1403 1404 Signed cookies may store any pickle-able object and are 1405 cryptographically signed to prevent manipulation. Keep in mind that 1406 cookies are limited to 4kb in most browsers. 1407 1408 Warning: Signed cookies are not encrypted (the client can still see 1409 the content) and not copy-protected (the client can restore an old 1410 cookie). The main intention is to make pickling and unpickling 1411 save, not to store secret information at client side. 1412 ''' 1413 if not self._cookies: 1414 self._cookies = SimpleCookie() 1415 1416 if secret: 1417 value = touni(cookie_encode((name, value), secret)) 1418 elif not isinstance(value, basestring): 1419 raise TypeError('Secret key missing for non-string Cookie.') 1420 1421 if len(value) > 4096: raise ValueError('Cookie value to long.') 1422 self._cookies[name] = value 1423 1424 for key, value in options.items(): 1425 if key == 'max_age': 1426 if isinstance(value, timedelta): 1427 value = value.seconds + value.days * 24 * 3600 1428 if key == 'expires': 1429 if isinstance(value, (datedate, datetime)): 1430 value = value.timetuple() 1431 elif isinstance(value, (int, float)): 1432 value = time.gmtime(value) 1433 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value) 1434 self._cookies[name][key.replace('_', '-')] = value 1435 1436 def delete_cookie(self, key, **kwargs): 1437 ''' Delete a cookie. Be sure to use the same `domain` and `path` 1438 settings as used to create the cookie. ''' 1439 kwargs['max_age'] = -1 1440 kwargs['expires'] = 0 1441 self.set_cookie(key, '', **kwargs) 1442 1443 def __repr__(self): 1444 out = '' 1445 for name, value in self.headerlist: 1446 out += '%s: %s\n' % (name.title(), value.strip()) 1447 return out 1448 1449 #: Thread-local storage for :class:`LocalRequest` and :class:`LocalResponse` 1450 #: attributes. 1451 _lctx = threading.local() 1452 1453 def local_property(name, doc=None): 1454 1455 return property( 1456 lambda self: getattr(_lctx, name), 1457 lambda self, value: setattr(_lctx, name, value), 1458 lambda self: delattr(_lctx, name), 1459 doc or ('Thread-local property stored in :data:`_lctx.%s` ' % name) 1460 ) 1461 1462 class LocalRequest(BaseRequest): 1463 ''' A thread-local subclass of :class:`BaseRequest` with a different 1464 set of attribues for each thread. There is usually only one global 1465 instance of this class (:data:`request`). If accessed during a 1466 request/response cycle, this instance always refers to the *current* 1467 request (even on a multithreaded server). ''' 1468 def __init__(self): pass 1469 bind = BaseRequest.__init__ 1470 environ = local_property('request_environ') 1471 1472 1473 class LocalResponse(BaseResponse): 1474 ''' A thread-local subclass of :class:`BaseResponse` with a different 1475 set of attribues for each thread. There is usually only one global 1476 instance of this class (:data:`response`). Its attributes are used 1477 to build the HTTP response at the end of the request/response cycle. 1478 ''' 1479 def __init__(self): pass 1480 bind = BaseResponse.__init__ 1481 _status_line = local_property('response_status_line') 1482 _status_code = local_property('response_status_code') 1483 _cookies = local_property('response_cookies') 1484 _headers = local_property('response_headers') 1485 body = local_property('response_body') 1486 1487 Response = LocalResponse # BC 0.9 1488 Request = LocalRequest # BC 0.9 1489 1490 1491 1492 1493 1494 1495 ############################################################################### 1496 # Plugins ###################################################################### 1497 ############################################################################### 1498 1499 class PluginError(BottleException): pass 1500 1501 class JSONPlugin(object): 1502 name = 'json' 1503 api = 2 1504 1505 def __init__(self, json_dumps=json_dumps): 1506 self.json_dumps = json_dumps 1507 1508 def apply(self, callback, context): 1509 dumps = self.json_dumps 1510 if not dumps: return callback 1511 def wrapper(*a, **ka): 1512 rv = callback(*a, **ka) 1513 if isinstance(rv, dict): 1514 #Attempt to serialize, raises exception on failure 1515 json_response = dumps(rv) 1516 #Set content type only if serialization succesful 1517 response.content_type = 'application/json' 1518 return json_response 1519 return rv 1520 return wrapper 1521 1522 1523 class HooksPlugin(object): 1524 name = 'hooks' 1525 api = 2 1526 1527 _names = 'before_request', 'after_request', 'app_reset' 1528 1529 def __init__(self): 1530 self.hooks = dict((name, []) for name in self._names) 1531 self.app = None 1532 1533 def _empty(self): 1534 return not (self.hooks['before_request'] or self.hooks['after_request']) 1535 1536 def setup(self, app): 1537 self.app = app 1538 1539 def add(self, name, func): 1540 ''' Attach a callback to a hook. ''' 1541 was_empty = self._empty() 1542 self.hooks.setdefault(name, []).append(func) 1543 if self.app and was_empty and not self._empty(): self.app.reset() 1544 1545 def remove(self, name, func): 1546 ''' Remove a callback from a hook. ''' 1547 was_empty = self._empty() 1548 if name in self.hooks and func in self.hooks[name]: 1549 self.hooks[name].remove(func) 1550 if self.app and not was_empty and self._empty(): self.app.reset() 1551 1552 def trigger(self, name, *a, **ka): 1553 ''' Trigger a hook and return a list of results. ''' 1554 hooks = self.hooks[name] 1555 if ka.pop('reversed', False): hooks = hooks[::-1] 1556 return [hook(*a, **ka) for hook in hooks] 1557 1558 def apply(self, callback, context): 1559 if self._empty(): return callback 1560 def wrapper(*a, **ka): 1561 self.trigger('before_request') 1562 rv = callback(*a, **ka) 1563 self.trigger('after_request', reversed=True) 1564 return rv 1565 return wrapper 1566 1567 1568 class TemplatePlugin(object): 1569 ''' This plugin applies the :func:`view` decorator to all routes with a 1570 `template` config parameter. If the parameter is a tuple, the second 1571 element must be a dict with additional options (e.g. `template_engine`) 1572 or default variables for the template. ''' 1573 name = 'template' 1574 api = 2 1575 1576 def apply(self, callback, route): 1577 conf = route.config.get('template') 1578 if isinstance(conf, (tuple, list)) and len(conf) == 2: 1579 return view(conf[0], **conf[1])(callback) 1580 elif isinstance(conf, str) and 'template_opts' in route.config: 1581 depr('The `template_opts` parameter is deprecated.') #0.9 1582 return view(conf, **route.config['template_opts'])(callback) 1583 elif isinstance(conf, str): 1584 return view(conf)(callback) 1585 else: 1586 return callback 1587 1588 1589 #: Not a plugin, but part of the plugin API. TODO: Find a better place. 1590 class _ImportRedirect(object): 1591 def __init__(self, name, impmask): 1592 ''' Create a virtual package that redirects imports (see PEP 302). ''' 1593 self.name = name 1594 self.impmask = impmask 1595 self.module = sys.modules.setdefault(name, imp.new_module(name)) 1596 self.module.__dict__.update({'__file__': __file__, '__path__': [], 1597 '__all__': [], '__loader__': self}) 1598 sys.meta_path.append(self) 1599 1600 def find_module(self, fullname, path=None): 1601 if '.' not in fullname: return 1602 packname, modname = fullname.rsplit('.', 1) 1603 if packname != self.name: return 1604 return self 1605 1606 def load_module(self, fullname): 1607 if fullname in sys.modules: return sys.modules[fullname] 1608 packname, modname = fullname.rsplit('.', 1) 1609 realname = self.impmask % modname 1610 __import__(realname) 1611 module = sys.modules[fullname] = sys.modules[realname] 1612 setattr(self.module, modname, module) 1613 module.__loader__ = self 1614 return module 1615 1616 1617 1618 1619 1620 1621 ############################################################################### 1622 # Common Utilities ############################################################# 1623 ############################################################################### 1624 1625 1626 class MultiDict(DictMixin): 1627 """ This dict stores multiple values per key, but behaves exactly like a 1628 normal dict in that it returns only the newest value for any given key. 1629 There are special methods available to access the full list of values. 1630 """ 1631 1632 def __init__(self, *a, **k): 1633 self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items()) 1634 1635 def __len__(self): return len(self.dict) 1636 def __iter__(self): return iter(self.dict) 1637 def __contains__(self, key): return key in self.dict 1638 def __delitem__(self, key): del self.dict[key] 1639 def __getitem__(self, key): return self.dict[key][-1] 1640 def __setitem__(self, key, value): self.append(key, value) 1641 def keys(self): return self.dict.keys() 1642 1643 if py3k: 1644 def values(self): return (v[-1] for v in self.dict.values()) 1645 def items(self): return ((k, v[-1]) for k, v in self.dict.items()) 1646 def allitems(self): 1647 return ((k, v) for k, vl in self.dict.items() for v in vl) 1648 iterkeys = keys 1649 itervalues = values 1650 iteritems = items 1651 iterallitems = allitems 1652 1653 else: 1654 def values(self): return [v[-1] for v in self.dict.values()] 1655 def items(self): return [(k, v[-1]) for k, v in self.dict.items()] 1656 def iterkeys(self): return self.dict.iterkeys() 1657 def itervalues(self): return (v[-1] for v in self.dict.itervalues()) 1658 def iteritems(self): 1659 return ((k, v[-1]) for k, v in self.dict.iteritems()) 1660 def iterallitems(self): 1661 return ((k, v) for k, vl in self.dict.iteritems() for v in vl) 1662 def allitems(self): 1663 return [(k, v) for k, vl in self.dict.iteritems() for v in vl] 1664 1665 def get(self, key, default=None, index=-1, type=None): 1666 ''' Return the most recent value for a key. 1667 1668 :param default: The default value to be returned if the key is not 1669 present or the type conversion fails. 1670 :param index: An index for the list of available values. 1671 :param type: If defined, this callable is used to cast the value 1672 into a specific type. Exception are suppressed and result in 1673 the default value to be returned. 1674 ''' 1675 try: 1676 val = self.dict[key][index] 1677 return type(val) if type else val 1678 except Exception: 1679 pass 1680 return default 1681 1682 def append(self, key, value): 1683 ''' Add a new value to the list of values for this key. ''' 1684 self.dict.setdefault(key, []).append(value) 1685 1686 def replace(self, key, value): 1687 ''' Replace the list of values with a single value. ''' 1688 self.dict[key] = [value] 1689 1690 def getall(self, key): 1691 ''' Return a (possibly empty) list of values for a key. ''' 1692 return self.dict.get(key) or [] 1693 1694 #: Aliases for WTForms to mimic other multi-dict APIs (Django) 1695 getone = get 1696 getlist = getall 1697 1698 1699 1700 class FormsDict(MultiDict): 1701 ''' This :class:`MultiDict` subclass is used to store request form data. 1702 Additionally to the normal dict-like item access methods (which return 1703 unmodified data as native strings), this container also supports 1704 attribute-like access to its values. Attributes are automatically de- 1705 or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing 1706 attributes default to an empty string. ''' 1707 1708 #: Encoding used for attribute values. 1709 input_encoding = 'utf8' 1710 #: If true (default), unicode strings are first encoded with `latin1` 1711 #: and then decoded to match :attr:`input_encoding`. 1712 recode_unicode = True 1713 1714 def _fix(self, s, encoding=None): 1715 if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI 1716 s = s.encode('latin1') 1717 if isinstance(s, bytes): # Python 2 WSGI 1718 return s.decode(encoding or self.input_encoding) 1719 return s 1720 1721 def decode(self, encoding=None): 1722 ''' Returns a copy with all keys and values de- or recoded to match 1723 :attr:`input_encoding`. Some libraries (e.g. WTForms) want a 1724 unicode dictionary. ''' 1725 copy = FormsDict() 1726 enc = copy.input_encoding = encoding or self.input_encoding 1727 copy.recode_unicode = False 1728 for key, value in self.allitems(): 1729 copy.append(self._fix(key, enc), self._fix(value, enc)) 1730 return copy 1731 1732 def getunicode(self, name, default=None, encoding=None): 1733 try: 1734 return self._fix(self[name], encoding) 1735 except (UnicodeError, KeyError): 1736 return default 1737 1738 def __getattr__(self, name, default=unicode()): 1739 return self.getunicode(name, default=default) 1740 1741 1742 class HeaderDict(MultiDict): 1743 """ A case-insensitive version of :class:`MultiDict` that defaults to 1744 replace the old value instead of appending it. """ 1745 1746 def __init__(self, *a, **ka): 1747 self.dict = {} 1748 if a or ka: self.update(*a, **ka) 1749 1750 def __contains__(self, key): return _hkey(key) in self.dict 1751 def __delitem__(self, key): del self.dict[_hkey(key)] 1752 def __getitem__(self, key): return self.dict[_hkey(key)][-1] 1753 def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)] 1754 def append(self, key, value): 1755 self.dict.setdefault(_hkey(key), []).append(str(value)) 1756 def replace(self, key, value): self.dict[_hkey(key)] = [str(value)] 1757 def getall(self, key): return self.dict.get(_hkey(key)) or [] 1758 def get(self, key, default=None, index=-1): 1759 return MultiDict.get(self, _hkey(key), default, index) 1760 def filter(self, names): 1761 for name in [_hkey(n) for n in names]: 1762 if name in self.dict: 1763 del self.dict[name] 1764 1765 1766 class WSGIHeaderDict(DictMixin): 1767 ''' This dict-like class wraps a WSGI environ dict and provides convenient 1768 access to HTTP_* fields. Keys and values are native strings 1769 (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI 1770 environment contains non-native string values, these are de- or encoded 1771 using a lossless 'latin1' character set. 1772 1773 The API will remain stable even on changes to the relevant PEPs. 1774 Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one 1775 that uses non-native strings.) 1776 ''' 1777 #: List of keys that do not have a 'HTTP_' prefix. 1778 cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') 1779 1780 def __init__(self, environ): 1781 self.environ = environ 1782 1783 def _ekey(self, key): 1784 ''' Translate header field name to CGI/WSGI environ key. ''' 1785 key = key.replace('-','_').upper() 1786 if key in self.cgikeys: 1787 return key 1788 return 'HTTP_' + key 1789 1790 def raw(self, key, default=None): 1791 ''' Return the header value as is (may be bytes or unicode). ''' 1792 return self.environ.get(self._ekey(key), default) 1793 1794 def __getitem__(self, key): 1795 return tonat(self.environ[self._ekey(key)], 'latin1') 1796 1797 def __setitem__(self, key, value): 1798 raise TypeError("%s is read-only." % self.__class__) 1799 1800 def __delitem__(self, key): 1801 raise TypeError("%s is read-only." % self.__class__) 1802 1803 def __iter__(self): 1804 for key in self.environ: 1805 if key[:5] == 'HTTP_': 1806 yield key[5:].replace('_', '-').title() 1807 elif key in self.cgikeys: 1808 yield key.replace('_', '-').title() 1809 1810 def keys(self): return [x for x in self] 1811 def __len__(self): return len(self.keys()) 1812 def __contains__(self, key): return self._ekey(key) in self.environ 1813 1814 1815 class ConfigDict(dict): 1816 ''' A dict-subclass with some extras: You can access keys like attributes. 1817 Uppercase attributes create new ConfigDicts and act as name-spaces. 1818 Other missing attributes return None. Calling a ConfigDict updates its 1819 values and returns itself. 1820 1821 >>> cfg = ConfigDict() 1822 >>> cfg.Namespace.value = 5 1823 >>> cfg.OtherNamespace(a=1, b=2) 1824 >>> cfg 1825 {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}} 1826 ''' 1827 1828 def __getattr__(self, key): 1829 if key not in self and key[0].isupper(): 1830 self[key] = ConfigDict() 1831 return self.get(key) 1832 1833 def __setattr__(self, key, value): 1834 if hasattr(dict, key): 1835 raise AttributeError('Read-only attribute.') 1836 if key in self and self[key] and isinstance(self[key], ConfigDict): 1837 raise AttributeError('Non-empty namespace attribute.') 1838 self[key] = value 1839 1840 def __delattr__(self, key): 1841 if key in self: del self[key] 1842 1843 def __call__(self, *a, **ka): 1844 for key, value in dict(*a, **ka).items(): setattr(self, key, value) 1845 return self 1846 1847 1848 class AppStack(list): 1849 """ A stack-like list. Calling it returns the head of the stack. """ 1850 1851 def __call__(self): 1852 """ Return the current default application. """ 1853 return self[-1] 1854 1855 def push(self, value=None): 1856 """ Add a new :class:`Bottle` instance to the stack """ 1857 if not isinstance(value, Bottle): 1858 value = Bottle() 1859 self.append(value) 1860 return value 1861 1862 1863 class WSGIFileWrapper(object): 1864 1865 def __init__(self, fp, buffer_size=1024*64): 1866 self.fp, self.buffer_size = fp, buffer_size 1867 for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'): 1868 if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr)) 1869 1870 def __iter__(self): 1871 buff, read = self.buffer_size, self.read 1872 while True: 1873 part = read(buff) 1874 if not part: return 1875 yield part 1876 1877 1878 1879 1880 1881 1882 ############################################################################### 1883 # Application Helper ########################################################### 1884 ############################################################################### 1885 1886 1887 def abort(code=500, text='Unknown Error: Application stopped.'): 1888 """ Aborts execution and causes a HTTP error. """ 1889 raise HTTPError(code, text) 1890 1891 1892 def redirect(url, code=None): 1893 """ Aborts execution and causes a 303 or 302 redirect, depending on 1894 the HTTP protocol version. """ 1895 if code is None: 1896 code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 1897 location = urljoin(request.url, url) 1898 raise HTTPResponse("", status=code, header=dict(Location=location)) 1899 1900 1901 def _file_iter_range(fp, offset, bytes, maxread=1024*1024): 1902 ''' Yield chunks from a range in a file. No chunk is bigger than maxread.''' 1903 fp.seek(offset) 1904 while bytes > 0: 1905 part = fp.read(min(bytes, maxread)) 1906 if not part: break 1907 bytes -= len(part) 1908 yield part 1909 1910 1911 def static_file(filename, root, mimetype='auto', download=False): 1912 """ Open a file in a safe way and return :exc:`HTTPResponse` with status 1913 code 200, 305, 401 or 404. Set Content-Type, Content-Encoding, 1914 Content-Length and Last-Modified header. Obey If-Modified-Since header 1915 and HEAD requests. 1916 """ 1917 root = os.path.abspath(root) + os.sep 1918 filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) 1919 header = dict() 1920 1921 if not filename.startswith(root): 1922 return HTTPError(403, "Access denied.") 1923 if not os.path.exists(filename) or not os.path.isfile(filename): 1924 return HTTPError(404, "File does not exist.") 1925 if not os.access(filename, os.R_OK): 1926 return HTTPError(403, "You do not have permission to access this file.") 1927 1928 if mimetype == 'auto': 1929 mimetype, encoding = mimetypes.guess_type(filename) 1930 if mimetype: header['Content-Type'] = mimetype 1931 if encoding: header['Content-Encoding'] = encoding 1932 elif mimetype: 1933 header['Content-Type'] = mimetype 1934 1935 if download: 1936 download = os.path.basename(filename if download == True else download) 1937 header['Content-Disposition'] = 'attachment; filename="%s"' % download 1938 1939 stats = os.stat(filename) 1940 header['Content-Length'] = clen = stats.st_size 1941 lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) 1942 header['Last-Modified'] = lm 1943 1944 ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') 1945 if ims: 1946 ims = parse_date(ims.split(";")[0].strip()) 1947 if ims is not None and ims >= int(stats.st_mtime): 1948 header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) 1949 return HTTPResponse(status=304, header=header) 1950 1951 body = '' if request.method == 'HEAD' else open(filename, 'rb') 1952 1953 header["Accept-Ranges"] = "bytes" 1954 ranges = request.environ.get('HTTP_RANGE') 1955 if 'HTTP_RANGE' in request.environ: 1956 ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen)) 1957 if not ranges: 1958 return HTTPError(416, "Requested Range Not Satisfiable") 1959 offset, end = ranges[0] 1960 header["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen) 1961 header["Content-Length"] = str(end-offset) 1962 if body: body = _file_iter_range(body, offset, end-offset) 1963 return HTTPResponse(body, header=header, status=206) 1964 return HTTPResponse(body, header=header) 1965 1966 1967 1968 1969 1970 1971 ############################################################################### 1972 # HTTP Utilities and MISC (TODO) ############################################### 1973 ############################################################################### 1974 1975 1976 def debug(mode=True): 1977 """ Change the debug level. 1978 There is only one debug level supported at the moment.""" 1979 global DEBUG 1980 DEBUG = bool(mode) 1981 1982 1983 def parse_date(ims): 1984 """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ 1985 try: 1986 ts = email.utils.parsedate_tz(ims) 1987 return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone 1988 except (TypeError, ValueError, IndexError, OverflowError): 1989 return None 1990 1991 1992 def parse_auth(header): 1993 """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" 1994 try: 1995 method, data = header.split(None, 1) 1996 if method.lower() == 'basic': 1997 user, pwd = touni(base64.b64decode(tob(data))).split(':',1) 1998 return user, pwd 1999 except (KeyError, ValueError): 2000 return None 2001 2002 def parse_range_header(header, maxlen=0): 2003 ''' Yield (start, end) ranges parsed from a HTTP Range header. Skip 2004 unsatisfiable ranges. The end index is non-inclusive.''' 2005 if not header or header[:6] != 'bytes=': return 2006 ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r] 2007 for start, end in ranges: 2008 try: 2009 if not start: # bytes=-100 -> last 100 bytes 2010 start, end = max(0, maxlen-int(end)), maxlen 2011 elif not end: # bytes=100- -> all but the first 99 bytes 2012 start, end = int(start), maxlen 2013 else: # bytes=100-200 -> bytes 100-200 (inclusive) 2014 start, end = int(start), min(int(end)+1, maxlen) 2015 if 0 <= start < end <= maxlen: 2016 yield start, end 2017 except ValueError: 2018 pass 2019 2020 def _lscmp(a, b): 2021 ''' Compares two strings in a cryptographically save way: 2022 Runtime is not affected by length of common prefix. ''' 2023 return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) 2024 2025 2026 def cookie_encode(data, key): 2027 ''' Encode and sign a pickle-able object. Return a (byte) string ''' 2028 msg = base64.b64encode(pickle.dumps(data, -1)) 2029 sig = base64.b64encode(hmac.new(tob(key), msg).digest()) 2030 return tob('!') + sig + tob('?') + msg 2031 2032 2033 def cookie_decode(data, key): 2034 ''' Verify and decode an encoded string. Return an object or None.''' 2035 data = tob(data) 2036 if cookie_is_encoded(data): 2037 sig, msg = data.split(tob('?'), 1) 2038 if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())): 2039 return pickle.loads(base64.b64decode(msg)) 2040 return None 2041 2042 2043 def cookie_is_encoded(data): 2044 ''' Return True if the argument looks like a encoded cookie.''' 2045 return bool(data.startswith(tob('!')) and tob('?') in data) 2046 2047 2048 def html_escape(string): 2049 ''' Escape HTML special characters ``&<>`` and quotes ``'"``. ''' 2050 return string.replace('&','&').replace('<','<').replace('>','>')\ 2051 .replace('"','"').replace("'",''') 2052 2053 2054 def html_quote(string): 2055 ''' Escape and quote a string to be used as an HTTP attribute.''' 2056 return '"%s"' % html_escape(string).replace('\n','%#10;')\ 2057 .replace('\r',' ').replace('\t','	') 2058 2059 2060 def yieldroutes(func): 2061 """ Return a generator for routes that match the signature (name, args) 2062 of the func parameter. This may yield more than one route if the function 2063 takes optional keyword arguments. The output is best described by example:: 2064 2065 a() -> '/a' 2066 b(x, y) -> '/b/:x/:y' 2067 c(x, y=5) -> '/c/:x' and '/c/:x/:y' 2068 d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y' 2069 """ 2070 import inspect # Expensive module. Only import if necessary. 2071 path = '/' + func.__name__.replace('__','/').lstrip('/') 2072 spec = inspect.getargspec(func) 2073 argc = len(spec[0]) - len(spec[3] or []) 2074 path += ('/:%s' * argc) % tuple(spec[0][:argc]) 2075 yield path 2076 for arg in spec[0][argc:]: 2077 path += '/:%s' % arg 2078 yield path 2079 2080 2081 def path_shift(script_name, path_info, shift=1): 2082 ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. 2083 2084 :return: The modified paths. 2085 :param script_name: The SCRIPT_NAME path. 2086 :param script_name: The PATH_INFO path. 2087 :param shift: The number of path fragments to shift. May be negative to 2088 change the shift direction. (default: 1) 2089 ''' 2090 if shift == 0: return script_name, path_info 2091 pathlist = path_info.strip('/').split('/') 2092 scriptlist = script_name.strip('/').split('/') 2093 if pathlist and pathlist[0] == '': pathlist = [] 2094 if scriptlist and scriptlist[0] == '': scriptlist = [] 2095 if shift > 0 and shift <= len(pathlist): 2096 moved = pathlist[:shift] 2097 scriptlist = scriptlist + moved 2098 pathlist = pathlist[shift:] 2099 elif shift < 0 and shift >= -len(scriptlist): 2100 moved = scriptlist[shift:] 2101 pathlist = moved + pathlist 2102 scriptlist = scriptlist[:shift] 2103 else: 2104 empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO' 2105 raise AssertionError("Cannot shift. Nothing left from %s" % empty) 2106 new_script_name = '/' + '/'.join(scriptlist) 2107 new_path_info = '/' + '/'.join(pathlist) 2108 if path_info.endswith('/') and pathlist: new_path_info += '/' 2109 return new_script_name, new_path_info 2110 2111 2112 def validate(**vkargs): 2113 """ 2114 Validates and manipulates keyword arguments by user defined callables. 2115 Handles ValueError and missing arguments by raising HTTPError(403). 2116 """ 2117 depr('Use route wildcard filters instead.') 2118 def decorator(func): 2119 @functools.wraps(func) 2120 def wrapper(*args, **kargs): 2121 for key, value in vkargs.items(): 2122 if key not in kargs: 2123 abort(403, 'Missing parameter: %s' % key) 2124 try: 2125 kargs[key] = value(kargs[key]) 2126 except ValueError: 2127 abort(403, 'Wrong parameter format for: %s' % key) 2128 return func(*args, **kargs) 2129 return wrapper 2130 return decorator 2131 2132 2133 def auth_basic(check, realm="private", text="Access denied"): 2134 ''' Callback decorator to require HTTP auth (basic). 2135 TODO: Add route(check_auth=...) parameter. ''' 2136 def decorator(func): 2137 def wrapper(*a, **ka): 2138 user, password = request.auth or (None, None) 2139 if user is None or not check(user, password): 2140 response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm 2141 return HTTPError(401, text) 2142 return func(*a, **ka) 2143 return wrapper 2144 return decorator 2145 2146 2147 # Shortcuts for common Bottle methods. 2148 # They all refer to the current default application. 2149 2150 def make_default_app_wrapper(name): 2151 ''' Return a callable that relays calls to the current default app. ''' 2152 @functools.wraps(getattr(Bottle, name)) 2153 def wrapper(*a, **ka): 2154 return getattr(app(), name)(*a, **ka) 2155 return wrapper 2156 2157 route = make_default_app_wrapper('route') 2158 get = make_default_app_wrapper('get') 2159 post = make_default_app_wrapper('post') 2160 put = make_default_app_wrapper('put') 2161 delete = make_default_app_wrapper('delete') 2162 error = make_default_app_wrapper('error') 2163 mount = make_default_app_wrapper('mount') 2164 hook = make_default_app_wrapper('hook') 2165 install = make_default_app_wrapper('install') 2166 uninstall = make_default_app_wrapper('uninstall') 2167 url = make_default_app_wrapper('get_url') 2168 2169 2170 2171 2172 2173 2174 2175 ############################################################################### 2176 # Server Adapter ############################################################### 2177 ############################################################################### 2178 2179 2180 class ServerAdapter(object): 2181 quiet = False 2182 def __init__(self, host='127.0.0.1', port=8080, **config): 2183 self.options = config 2184 self.host = host 2185 self.port = int(port) 2186 2187 def run(self, handler): # pragma: no cover 2188 pass 2189 2190 def __repr__(self): 2191 args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) 2192 return "%s(%s)" % (self.__class__.__name__, args) 2193 2194 2195 class CGIServer(ServerAdapter): 2196 quiet = True 2197 def run(self, handler): # pragma: no cover 2198 from wsgiref.handlers import CGIHandler 2199 def fixed_environ(environ, start_response): 2200 environ.setdefault('PATH_INFO', '') 2201 return handler(environ, start_response) 2202 CGIHandler().run(fixed_environ) 2203 2204 2205 class FlupFCGIServer(ServerAdapter): 2206 def run(self, handler): # pragma: no cover 2207 import flup.server.fcgi 2208 self.options.setdefault('bindAddress', (self.host, self.port)) 2209 flup.server.fcgi.WSGIServer(handler, **self.options).run() 2210 2211 2212 class WSGIRefServer(ServerAdapter): 2213 def run(self, handler): # pragma: no cover 2214 from wsgiref.simple_server import make_server, WSGIRequestHandler 2215 if self.quiet: 2216 class QuietHandler(WSGIRequestHandler): 2217 def log_request(*args, **kw): pass 2218 self.options['handler_class'] = QuietHandler 2219 srv = make_server(self.host, self.port, handler, **self.options) 2220 srv.serve_forever() 2221 2222 2223 class CherryPyServer(ServerAdapter): 2224 def run(self, handler): # pragma: no cover 2225 from cherrypy import wsgiserver 2226 server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) 2227 try: 2228 server.start() 2229 finally: 2230 server.stop() 2231 2232 2233 class WaitressServer(ServerAdapter): 2234 def run(self, handler): 2235 from waitress import serve 2236 serve(handler, host=self.host, port=self.port) 2237 2238 2239 class PasteServer(ServerAdapter): 2240 def run(self, handler): # pragma: no cover 2241 from paste import httpserver 2242 if not self.quiet: 2243 from paste.translogger import TransLogger 2244 handler = TransLogger(handler) 2245 httpserver.serve(handler, host=self.host, port=str(self.port), 2246 **self.options) 2247 2248 2249 class MeinheldServer(ServerAdapter): 2250 def run(self, handler): 2251 from meinheld import server 2252 server.listen((self.host, self.port)) 2253 server.run(handler) 2254 2255 2256 class FapwsServer(ServerAdapter): 2257 """ Extremely fast webserver using libev. See http://www.fapws.org/ """ 2258 def run(self, handler): # pragma: no cover 2259 import fapws._evwsgi as evwsgi 2260 from fapws import base, config 2261 port = self.port 2262 if float(config.SERVER_IDENT[-2:]) > 0.4: 2263 # fapws3 silently changed its API in 0.5 2264 port = str(port) 2265 evwsgi.start(self.host, port) 2266 # fapws3 never releases the GIL. Complain upstream. I tried. No luck. 2267 if 'BOTTLE_CHILD' in os.environ and not self.quiet: 2268 _stderr("WARNING: Auto-reloading does not work with Fapws3.\n") 2269 _stderr(" (Fapws3 breaks python thread support)\n") 2270 evwsgi.set_base_module(base) 2271 def app(environ, start_response): 2272 environ['wsgi.multiprocess'] = False 2273 return handler(environ, start_response) 2274 evwsgi.wsgi_cb(('', app)) 2275 evwsgi.run() 2276 2277 2278 class TornadoServer(ServerAdapter): 2279 """ The super hyped asynchronous server by facebook. Untested. """ 2280 def run(self, handler): # pragma: no cover 2281 import tornado.wsgi, tornado.httpserver, tornado.ioloop 2282 container = tornado.wsgi.WSGIContainer(handler) 2283 server = tornado.httpserver.HTTPServer(container) 2284 server.listen(port=self.port) 2285 tornado.ioloop.IOLoop.instance().start() 2286 2287 2288 class AppEngineServer(ServerAdapter): 2289 """ Adapter for Google App Engine. """ 2290 quiet = True 2291 def run(self, handler): 2292 from google.appengine.ext.webapp import util 2293 # A main() function in the handler script enables 'App Caching'. 2294 # Lets makes sure it is there. This _really_ improves performance. 2295 module = sys.modules.get('__main__') 2296 if module and not hasattr(module, 'main'): 2297 module.main = lambda: util.run_wsgi_app(handler) 2298 util.run_wsgi_app(handler) 2299 2300 2301 class TwistedServer(ServerAdapter): 2302 """ Untested. """ 2303 def run(self, handler): 2304 from twisted.web import server, wsgi 2305 from twisted.python.threadpool import ThreadPool 2306 from twisted.internet import reactor 2307 thread_pool = ThreadPool() 2308 thread_pool.start() 2309 reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) 2310 factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) 2311 reactor.listenTCP(self.port, factory, interface=self.host) 2312 reactor.run() 2313 2314 2315 class DieselServer(ServerAdapter): 2316 """ Untested. """ 2317 def run(self, handler): 2318 from diesel.protocols.wsgi import WSGIApplication 2319 app = WSGIApplication(handler, port=self.port) 2320 app.run() 2321 2322 2323 class GeventServer(ServerAdapter): 2324 """ Untested. Options: 2325 2326 * `monkey` (default: True) fixes the stdlib to use greenthreads. 2327 * `fast` (default: False) uses libevent's http server, but has some 2328 issues: No streaming, no pipelining, no SSL. 2329 """ 2330 def run(self, handler): 2331 from gevent import wsgi as wsgi_fast, pywsgi, monkey, local 2332 if self.options.get('monkey', True): 2333 if not threading.local is local.local: monkey.patch_all() 2334 wsgi = wsgi_fast if self.options.get('fast') else pywsgi 2335 wsgi.WSGIServer((self.host, self.port), handler).serve_forever() 2336 2337 2338 class GunicornServer(ServerAdapter): 2339 """ Untested. See http://gunicorn.org/configure.html for options. """ 2340 def run(self, handler): 2341 from gunicorn.app.base import Application 2342 2343 config = {'bind': "%s:%d" % (self.host, int(self.port))} 2344 config.update(self.options) 2345 2346 class GunicornApplication(Application): 2347 def init(self, parser, opts, args): 2348 return config 2349 2350 def load(self): 2351 return handler 2352 2353 GunicornApplication().run() 2354 2355 2356 class EventletServer(ServerAdapter): 2357 """ Untested """ 2358 def run(self, handler): 2359 from eventlet import wsgi, listen 2360 try: 2361 wsgi.server(listen((self.host, self.port)), handler, 2362 log_output=(not self.quiet)) 2363 except TypeError: 2364 # Fallback, if we have old version of eventlet 2365 wsgi.server(listen((self.host, self.port)), handler) 2366 2367 2368 class RocketServer(ServerAdapter): 2369 """ Untested. """ 2370 def run(self, handler): 2371 from rocket import Rocket 2372 server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler }) 2373 server.start() 2374 2375 2376 class BjoernServer(ServerAdapter): 2377 """ Fast server written in C: https://github.com/jonashaag/bjoern """ 2378 def run(self, handler): 2379 from bjoern import run 2380 run(handler, self.host, self.port) 2381 2382 2383 class AutoServer(ServerAdapter): 2384 """ Untested. """ 2385 adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer] 2386 def run(self, handler): 2387 for sa in self.adapters: 2388 try: 2389 return sa(self.host, self.port, **self.options).run(handler) 2390 except ImportError: 2391 pass 2392 2393 server_names = { 2394 'cgi': CGIServer, 2395 'flup': FlupFCGIServer, 2396 'wsgiref': WSGIRefServer, 2397 'waitress': WaitressServer, 2398 'cherrypy': CherryPyServer, 2399 'paste': PasteServer, 2400 'fapws3': FapwsServer, 2401 'tornado': TornadoServer, 2402 'gae': AppEngineServer, 2403 'twisted': TwistedServer, 2404 'diesel': DieselServer, 2405 'meinheld': MeinheldServer, 2406 'gunicorn': GunicornServer, 2407 'eventlet': EventletServer, 2408 'gevent': GeventServer, 2409 'rocket': RocketServer, 2410 'bjoern' : BjoernServer, 2411 'auto': AutoServer, 2412 } 2413 2414 2415 2416 2417 2418 2419 ############################################################################### 2420 # Application Control ########################################################## 2421 ############################################################################### 2422 2423 2424 def load(target, **namespace): 2425 """ Import a module or fetch an object from a module. 2426 2427 * ``package.module`` returns `module` as a module object. 2428 * ``pack.mod:name`` returns the module variable `name` from `pack.mod`. 2429 * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result. 2430 2431 The last form accepts not only function calls, but any type of 2432 expression. Keyword arguments passed to this function are available as 2433 local variables. Example: ``import_string('re:compile(x)', x='[a-z]')`` 2434 """ 2435 module, target = target.split(":", 1) if ':' in target else (target, None) 2436 if module not in sys.modules: __import__(module) 2437 if not target: return sys.modules[module] 2438 if target.isalnum(): return getattr(sys.modules[module], target) 2439 package_name = module.split('.')[0] 2440 namespace[package_name] = sys.modules[package_name] 2441 return eval('%s.%s' % (module, target), namespace) 2442 2443 2444 def load_app(target): 2445 """ Load a bottle application from a module and make sure that the import 2446 does not affect the current default application, but returns a separate 2447 application object. See :func:`load` for the target parameter. """ 2448 global NORUN; NORUN, nr_old = True, NORUN 2449 try: 2450 tmp = default_app.push() # Create a new "default application" 2451 rv = load(target) # Import the target module 2452 return rv if callable(rv) else tmp 2453 finally: 2454 default_app.remove(tmp) # Remove the temporary added default application 2455 NORUN = nr_old 2456 2457 _debug = debug 2458 def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, 2459 interval=1, reloader=False, quiet=False, plugins=None, 2460 debug=False, **kargs): 2461 """ Start a server instance. This method blocks until the server terminates. 2462 2463 :param app: WSGI application or target string supported by 2464 :func:`load_app`. (default: :func:`default_app`) 2465 :param server: Server adapter to use. See :data:`server_names` keys 2466 for valid names or pass a :class:`ServerAdapter` subclass. 2467 (default: `wsgiref`) 2468 :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on 2469 all interfaces including the external one. (default: 127.0.0.1) 2470 :param port: Server port to bind to. Values below 1024 require root 2471 privileges. (default: 8080) 2472 :param reloader: Start auto-reloading server? (default: False) 2473 :param interval: Auto-reloader interval in seconds (default: 1) 2474 :param quiet: Suppress output to stdout and stderr? (default: False) 2475 :param options: Options passed to the server adapter. 2476 """ 2477 if NORUN: return 2478 if reloader and not os.environ.get('BOTTLE_CHILD'): 2479 try: 2480 lockfile = None 2481 fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') 2482 os.close(fd) # We only need this file to exist. We never write to it 2483 while os.path.exists(lockfile): 2484 args = [sys.executable] + sys.argv 2485 environ = os.environ.copy() 2486 environ['BOTTLE_CHILD'] = 'true' 2487 environ['BOTTLE_LOCKFILE'] = lockfile 2488 p = subprocess.Popen(args, env=environ) 2489 while p.poll() is None: # Busy wait... 2490 os.utime(lockfile, None) # I am alive! 2491 time.sleep(interval) 2492 if p.poll() != 3: 2493 if os.path.exists(lockfile): os.unlink(lockfile) 2494 sys.exit(p.poll()) 2495 except KeyboardInterrupt: 2496 pass 2497 finally: 2498 if os.path.exists(lockfile): 2499 os.unlink(lockfile) 2500 return 2501 2502 try: 2503 _debug(debug) 2504 app = app or default_app() 2505 if isinstance(app, basestring): 2506 app = load_app(app) 2507 if not callable(app): 2508 raise ValueError("Application is not callable: %r" % app) 2509 2510 for plugin in plugins or []: 2511 app.install(plugin) 2512 2513 if server in server_names: 2514 server = server_names.get(server) 2515 if isinstance(server, basestring): 2516 server = load(server) 2517 if isinstance(server, type): 2518 server = server(host=host, port=port, **kargs) 2519 if not isinstance(server, ServerAdapter): 2520 raise ValueError("Unknown or unsupported server: %r" % server) 2521 2522 server.quiet = server.quiet or quiet 2523 if not server.quiet: 2524 _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server))) 2525 _stderr("Listening on http://%s:%d/\n" % (server.host, server.port)) 2526 _stderr("Hit Ctrl-C to quit.\n\n") 2527 2528 if reloader: 2529 lockfile = os.environ.get('BOTTLE_LOCKFILE') 2530 bgcheck = FileCheckerThread(lockfile, interval) 2531 with bgcheck: 2532 server.run(app) 2533 if bgcheck.status == 'reload': 2534 sys.exit(3) 2535 else: 2536 server.run(app) 2537 except KeyboardInterrupt: 2538 pass 2539 except (SystemExit, MemoryError): 2540 raise 2541 except: 2542 if not reloader: raise 2543 if not getattr(server, 'quiet', quiet): 2544 print_exc() 2545 time.sleep(interval) 2546 sys.exit(3) 2547 2548 2549 2550 class FileCheckerThread(threading.Thread): 2551 ''' Interrupt main-thread as soon as a changed module file is detected, 2552 the lockfile gets deleted or gets to old. ''' 2553 2554 def __init__(self, lockfile, interval): 2555 threading.Thread.__init__(self) 2556 self.lockfile, self.interval = lockfile, interval 2557 #: Is one of 'reload', 'error' or 'exit' 2558 self.status = None 2559 2560 def run(self): 2561 exists = os.path.exists 2562 mtime = lambda path: os.stat(path).st_mtime 2563 files = dict() 2564 2565 for module in list(sys.modules.values()): 2566 path = getattr(module, '__file__', '') 2567 if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] 2568 if path and exists(path): files[path] = mtime(path) 2569 2570 while not self.status: 2571 if not exists(self.lockfile)\ 2572 or mtime(self.lockfile) < time.time() - self.interval - 5: 2573 self.status = 'error' 2574 thread.interrupt_main() 2575 for path, lmtime in list(files.items()): 2576 if not exists(path) or mtime(path) > lmtime: 2577 self.status = 'reload' 2578 thread.interrupt_main() 2579 break 2580 time.sleep(self.interval) 2581 2582 def __enter__(self): 2583 self.start() 2584 2585 def __exit__(self, exc_type, exc_val, exc_tb): 2586 if not self.status: self.status = 'exit' # silent exit 2587 self.join() 2588 return exc_type is not None and issubclass(exc_type, KeyboardInterrupt) 2589 2590 2591 2592 2593 2594 ############################################################################### 2595 # Template Adapters ############################################################ 2596 ############################################################################### 2597 2598 2599 class TemplateError(HTTPError): 2600 def __init__(self, message): 2601 HTTPError.__init__(self, 500, message) 2602 2603 2604 class BaseTemplate(object): 2605 """ Base class and minimal API for template adapters """ 2606 extensions = ['tpl','html','thtml','stpl'] 2607 settings = {} #used in prepare() 2608 defaults = {} #used in render() 2609 2610 def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings): 2611 """ Create a new template. 2612 If the source parameter (str or buffer) is missing, the name argument 2613 is used to guess a template filename. Subclasses can assume that 2614 self.source and/or self.filename are set. Both are strings. 2615 The lookup, encoding and settings parameters are stored as instance 2616 variables. 2617 The lookup parameter stores a list containing directory paths. 2618 The encoding parameter should be used to decode byte strings or files. 2619 The settings parameter contains a dict for engine-specific settings. 2620 """ 2621 self.name = name 2622 self.source = source.read() if hasattr(source, 'read') else source 2623 self.filename = source.filename if hasattr(source, 'filename') else None 2624 self.lookup = [os.path.abspath(x) for x in lookup] 2625 self.encoding = encoding 2626 self.settings = self.settings.copy() # Copy from class variable 2627 self.settings.update(settings) # Apply 2628 if not self.source and self.name: 2629 self.filename = self.search(self.name, self.lookup) 2630 if not self.filename: 2631 raise TemplateError('Template %s not found.' % repr(name)) 2632 if not self.source and not self.filename: 2633 raise TemplateError('No template specified.') 2634 self.prepare(**self.settings) 2635 2636 @classmethod 2637 def search(cls, name, lookup=[]): 2638 """ Search name in all directories specified in lookup. 2639 First without, then with common extensions. Return first hit. """ 2640 if os.path.isfile(name): return name 2641 for spath in lookup: 2642 fname = os.path.join(spath, name) 2643 if os.path.isfile(fname): 2644 return fname 2645 for ext in cls.extensions: 2646 if os.path.isfile('%s.%s' % (fname, ext)): 2647 return '%s.%s' % (fname, ext) 2648 2649 @classmethod 2650 def global_config(cls, key, *args): 2651 ''' This reads or sets the global settings stored in class.settings. ''' 2652 if args: 2653 cls.settings = cls.settings.copy() # Make settings local to class 2654 cls.settings[key] = args[0] 2655 else: 2656 return cls.settings[key] 2657 2658 def prepare(self, **options): 2659 """ Run preparations (parsing, caching, ...). 2660 It should be possible to call this again to refresh a template or to 2661 update settings. 2662 """ 2663 raise NotImplementedError 2664 2665 def render(self, *args, **kwargs): 2666 """ Render the template with the specified local variables and return 2667 a single byte or unicode string. If it is a byte string, the encoding 2668 must match self.encoding. This method must be thread-safe! 2669 Local variables may be provided in dictionaries (*args) 2670 or directly, as keywords (**kwargs). 2671 """ 2672 raise NotImplementedError 2673 2674 2675 class MakoTemplate(BaseTemplate): 2676 def prepare(self, **options): 2677 from mako.template import Template 2678 from mako.lookup import TemplateLookup 2679 options.update({'input_encoding':self.encoding}) 2680 options.setdefault('format_exceptions', bool(DEBUG)) 2681 lookup = TemplateLookup(directories=self.lookup, **options) 2682 if self.source: 2683 self.tpl = Template(self.source, lookup=lookup, **options) 2684 else: 2685 self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options) 2686 2687 def render(self, *args, **kwargs): 2688 for dictarg in args: kwargs.update(dictarg) 2689 _defaults = self.defaults.copy() 2690 _defaults.update(kwargs) 2691 return self.tpl.render(**_defaults) 2692 2693 2694 class CheetahTemplate(BaseTemplate): 2695 def prepare(self, **options): 2696 from Cheetah.Template import Template 2697 self.context = threading.local() 2698 self.context.vars = {} 2699 options['searchList'] = [self.context.vars] 2700 if self.source: 2701 self.tpl = Template(source=self.source, **options) 2702 else: 2703 self.tpl = Template(file=self.filename, **options) 2704 2705 def render(self, *args, **kwargs): 2706 for dictarg in args: kwargs.update(dictarg) 2707 self.context.vars.update(self.defaults) 2708 self.context.vars.update(kwargs) 2709 out = str(self.tpl) 2710 self.context.vars.clear() 2711 return out 2712 2713 2714 class Jinja2Template(BaseTemplate): 2715 def prepare(self, filters=None, tests=None, **kwargs): 2716 from jinja2 import Environment, FunctionLoader 2717 if 'prefix' in kwargs: # TODO: to be removed after a while 2718 raise RuntimeError('The keyword argument `prefix` has been removed. ' 2719 'Use the full jinja2 environment name line_statement_prefix instead.') 2720 self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) 2721 if filters: self.env.filters.update(filters) 2722 if tests: self.env.tests.update(tests) 2723 if self.source: 2724 self.tpl = self.env.from_string(self.source) 2725 else: 2726 self.tpl = self.env.get_template(self.filename) 2727 2728 def render(self, *args, **kwargs): 2729 for dictarg in args: kwargs.update(dictarg) 2730 _defaults = self.defaults.copy() 2731 _defaults.update(kwargs) 2732 return self.tpl.render(**_defaults) 2733 2734 def loader(self, name): 2735 fname = self.search(name, self.lookup) 2736 if not fname: return 2737 with open(fname, "rb") as f: 2738 return f.read().decode(self.encoding) 2739 2740 2741 class SimpleTALTemplate(BaseTemplate): 2742 ''' Deprecated, do not use. ''' 2743 def prepare(self, **options): 2744 depr('The SimpleTAL template handler is deprecated'\ 2745 ' and will be removed in 0.12') 2746 from simpletal import simpleTAL 2747 if self.source: 2748 self.tpl = simpleTAL.compileHTMLTemplate(self.source) 2749 else: 2750 with open(self.filename, 'rb') as fp: 2751 self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read())) 2752 2753 def render(self, *args, **kwargs): 2754 from simpletal import simpleTALES 2755 for dictarg in args: kwargs.update(dictarg) 2756 context = simpleTALES.Context() 2757 for k,v in self.defaults.items(): 2758 context.addGlobal(k, v) 2759 for k,v in kwargs.items(): 2760 context.addGlobal(k, v) 2761 output = StringIO() 2762 self.tpl.expand(context, output) 2763 return output.getvalue() 2764 2765 2766 class SimpleTemplate(BaseTemplate): 2767 blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while', 2768 'with', 'def', 'class') 2769 dedent_blocks = ('elif', 'else', 'except', 'finally') 2770 2771 @lazy_attribute 2772 def re_pytokens(cls): 2773 ''' This matches comments and all kinds of quoted strings but does 2774 NOT match comments (#...) within quoted strings. (trust me) ''' 2775 return re.compile(r''' 2776 (''(?!')|""(?!")|'{6}|"{6} # Empty strings (all 4 types) 2777 |'(?:[^\\']|\\.)+?' # Single quotes (') 2778 |"(?:[^\\"]|\\.)+?" # Double quotes (") 2779 |'{3}(?:[^\\]|\\.|\n)+?'{3} # Triple-quoted strings (') 2780 |"{3}(?:[^\\]|\\.|\n)+?"{3} # Triple-quoted strings (") 2781 |\#.* # Comments 2782 )''', re.VERBOSE) 2783 2784 def prepare(self, escape_func=html_escape, noescape=False, **kwargs): 2785 self.cache = {} 2786 enc = self.encoding 2787 self._str = lambda x: touni(x, enc) 2788 self._escape = lambda x: escape_func(touni(x, enc)) 2789 if noescape: 2790 self._str, self._escape = self._escape, self._str 2791 2792 @classmethod 2793 def split_comment(cls, code): 2794 """ Removes comments (#...) from python code. """ 2795 if '#' not in code: return code 2796 #: Remove comments only (leave quoted strings as they are) 2797 subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0) 2798 return re.sub(cls.re_pytokens, subf, code) 2799 2800 @cached_property 2801 def co(self): 2802 return compile(self.code, self.filename or '<string>', 'exec') 2803 2804 @cached_property 2805 def code(self): 2806 stack = [] # Current Code indentation 2807 lineno = 0 # Current line of code 2808 ptrbuffer = [] # Buffer for printable strings and token tuple instances 2809 codebuffer = [] # Buffer for generated python code 2810 multiline = dedent = oneline = False 2811 template = self.source or open(self.filename, 'rb').read() 2812 2813 def yield_tokens(line): 2814 for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)): 2815 if i % 2: 2816 if part.startswith('!'): yield 'RAW', part[1:] 2817 else: yield 'CMD', part 2818 else: yield 'TXT', part 2819 2820 def flush(): # Flush the ptrbuffer 2821 if not ptrbuffer: return 2822 cline = '' 2823 for line in ptrbuffer: 2824 for token, value in line: 2825 if token == 'TXT': cline += repr(value) 2826 elif token == 'RAW': cline += '_str(%s)' % value 2827 elif token == 'CMD': cline += '_escape(%s)' % value 2828 cline += ', ' 2829 cline = cline[:-2] + '\\\n' 2830 cline = cline[:-2] 2831 if cline[:-1].endswith('\\\\\\\\\\n'): 2832 cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr' 2833 cline = '_printlist([' + cline + '])' 2834 del ptrbuffer[:] # Do this before calling code() again 2835 code(cline) 2836 2837 def code(stmt): 2838 for line in stmt.splitlines(): 2839 codebuffer.append(' ' * len(stack) + line.strip()) 2840 2841 for line in template.splitlines(True): 2842 lineno += 1 2843 line = touni(line, self.encoding) 2844 sline = line.lstrip() 2845 if lineno <= 2: 2846 m = re.match(r"%\s*#.*coding[:=]\s*([-\w.]+)", sline) 2847 if m: self.encoding = m.group(1) 2848 if m: line = line.replace('coding','coding (removed)') 2849 if sline and sline[0] == '%' and sline[:2] != '%%': 2850 line = line.split('%',1)[1].lstrip() # Full line following the % 2851 cline = self.split_comment(line).strip() 2852 cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0] 2853 flush() # You are actually reading this? Good luck, it's a mess :) 2854 if cmd in self.blocks or multiline: 2855 cmd = multiline or cmd 2856 dedent = cmd in self.dedent_blocks # "else:" 2857 if dedent and not oneline and not multiline: 2858 cmd = stack.pop() 2859 code(line) 2860 oneline = not cline.endswith(':') # "if 1: pass" 2861 multiline = cmd if cline.endswith('\\') else False 2862 if not oneline and not multiline: 2863 stack.append(cmd) 2864 elif cmd == 'end' and stack: 2865 code('#end(%s) %s' % (stack.pop(), line.strip()[3:])) 2866 elif cmd == 'include': 2867 p = cline.split(None, 2)[1:] 2868 if len(p) == 2: 2869 code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1])) 2870 elif p: 2871 code("_=_include(%s, _stdout)" % repr(p[0])) 2872 else: # Empty %include -> reverse of %rebase 2873 code("_printlist(_base)") 2874 elif cmd == 'rebase': 2875 p = cline.split(None, 2)[1:] 2876 if len(p) == 2: 2877 code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1])) 2878 elif p: 2879 code("globals()['_rebase']=(%s, {})" % repr(p[0])) 2880 else: 2881 code(line) 2882 else: # Line starting with text (not '%') or '%%' (escaped) 2883 if line.strip().startswith('%%'): 2884 line = line.replace('%%', '%', 1) 2885 ptrbuffer.append(yield_tokens(line)) 2886 flush() 2887 return '\n'.join(codebuffer) + '\n' 2888 2889 def subtemplate(self, _name, _stdout, *args, **kwargs): 2890 for dictarg in args: kwargs.update(dictarg) 2891 if _name not in self.cache: 2892 self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) 2893 return self.cache[_name].execute(_stdout, kwargs) 2894 2895 def execute(self, _stdout, *args, **kwargs): 2896 for dictarg in args: kwargs.update(dictarg) 2897 env = self.defaults.copy() 2898 env.update({'_stdout': _stdout, '_printlist': _stdout.extend, 2899 '_include': self.subtemplate, '_str': self._str, 2900 '_escape': self._escape, 'get': env.get, 2901 'setdefault': env.setdefault, 'defined': env.__contains__}) 2902 env.update(kwargs) 2903 eval(self.co, env) 2904 if '_rebase' in env: 2905 subtpl, rargs = env['_rebase'] 2906 rargs['_base'] = _stdout[:] #copy stdout 2907 del _stdout[:] # clear stdout 2908 return self.subtemplate(subtpl,_stdout,rargs) 2909 return env 2910 2911 def render(self, *args, **kwargs): 2912 """ Render the template using keyword arguments as local variables. """ 2913 for dictarg in args: kwargs.update(dictarg) 2914 stdout = [] 2915 self.execute(stdout, kwargs) 2916 return ''.join(stdout) 2917 2918 2919 def template(*args, **kwargs): 2920 ''' 2921 Get a rendered template as a string iterator. 2922 You can use a name, a filename or a template string as first parameter. 2923 Template rendering arguments can be passed as dictionaries 2924 or directly (as keyword arguments). 2925 ''' 2926 tpl = args[0] if args else None 2927 template_adapter = kwargs.pop('template_adapter', SimpleTemplate) 2928 if tpl not in TEMPLATES or DEBUG: 2929 settings = kwargs.pop('template_settings', {}) 2930 lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) 2931 if isinstance(tpl, template_adapter): 2932 TEMPLATES[tpl] = tpl 2933 if settings: TEMPLATES[tpl].prepare(**settings) 2934 elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: 2935 TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings) 2936 else: 2937 TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings) 2938 if not TEMPLATES[tpl]: 2939 abort(500, 'Template (%s) not found' % tpl) 2940 for dictarg in args[1:]: kwargs.update(dictarg) 2941 return TEMPLATES[tpl].render(kwargs) 2942 2943 mako_template = functools.partial(template, template_adapter=MakoTemplate) 2944 cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) 2945 jinja2_template = functools.partial(template, template_adapter=Jinja2Template) 2946 simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate) 2947 2948 2949 def view(tpl_name, **defaults): 2950 ''' Decorator: renders a template for a handler. 2951 The handler can control its behavior like that: 2952 2953 - return a dict of template vars to fill out the template 2954 - return something other than a dict and the view decorator will not 2955 process the template, but return the handler result as is. 2956 This includes returning a HTTPResponse(dict) to get, 2957 for instance, JSON with autojson or other castfilters. 2958 ''' 2959 def decorator(func): 2960 @functools.wraps(func) 2961 def wrapper(*args, **kwargs): 2962 result = func(*args, **kwargs) 2963 if isinstance(result, (dict, DictMixin)): 2964 tplvars = defaults.copy() 2965 tplvars.update(result) 2966 return template(tpl_name, **tplvars) 2967 return result 2968 return wrapper 2969 return decorator 2970 2971 mako_view = functools.partial(view, template_adapter=MakoTemplate) 2972 cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) 2973 jinja2_view = functools.partial(view, template_adapter=Jinja2Template) 2974 simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate) 2975 2976 2977 2978 2979 2980 2981 ############################################################################### 2982 # Constants and Globals ######################################################## 2983 ############################################################################### 2984 2985 2986 TEMPLATE_PATH = ['./', './views/'] 2987 TEMPLATES = {} 2988 DEBUG = False 2989 NORUN = False # If set, run() does nothing. Used by load_app() 2990 2991 #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') 2992 HTTP_CODES = httplib.responses 2993 HTTP_CODES[418] = "I'm a teapot" # RFC 2324 2994 HTTP_CODES[428] = "Precondition Required" 2995 HTTP_CODES[429] = "Too Many Requests" 2996 HTTP_CODES[431] = "Request Header Fields Too Large" 2997 HTTP_CODES[511] = "Network Authentication Required" 2998 _HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items()) 2999 3000 #: The default template used for error pages. Override with @error() 3001 ERROR_PAGE_TEMPLATE = """ 3002 %try: 3003 %from bottle import DEBUG, HTTP_CODES, request, touni 3004 %status_name = HTTP_CODES.get(e.status, 'Unknown').title() 3005 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 3006 <html> 3007 <head> 3008 <title>Error {{e.status}}: {{status_name}}</title> 3009 <style type="text/css"> 3010 html {background-color: #eee; font-family: sans;} 3011 body {background-color: #fff; border: 1px solid #ddd; 3012 padding: 15px; margin: 15px;} 3013 pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;} 3014 </style> 3015 </head> 3016 <body> 3017 <h1>Error {{e.status}}: {{status_name}}</h1> 3018 <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt> 3019 caused an error:</p> 3020 <pre>{{e.output}}</pre> 3021 %if DEBUG and e.exception: 3022 <h2>Exception:</h2> 3023 <pre>{{repr(e.exception)}}</pre> 3024 %end 3025 %if DEBUG and e.traceback: 3026 <h2>Traceback:</h2> 3027 <pre>{{e.traceback}}</pre> 3028 %end 3029 </body> 3030 </html> 3031 %except ImportError: 3032 <b>ImportError:</b> Could not generate the error page. Please add bottle to 3033 the import path. 3034 %end 3035 """ 3036 3037 #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a 3038 #: request callback, this instance always refers to the *current* request 3039 #: (even on a multithreaded server). 3040 request = LocalRequest() 3041 3042 #: A thread-safe instance of :class:`LocalResponse`. It is used to change the 3043 #: HTTP response for the *current* request. 3044 response = LocalResponse() 3045 3046 #: A thread-safe namespace. Not used by Bottle. 3047 local = threading.local() 3048 3049 # Initialize app stack (create first empty Bottle app) 3050 # BC: 0.6.4 and needed for run() 3051 app = default_app = AppStack() 3052 app.push() 3053 3054 #: A virtual package that redirects import statements. 3055 #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. 3056 ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module 3057 3058 if __name__ == '__main__': 3059 opt, args, parser = _cmd_options, _cmd_args, _cmd_parser 3060 if opt.version: 3061 _stdout('Bottle %s\n'%__version__) 3062 sys.exit(0) 3063 if not args: 3064 parser.print_help() 3065 _stderr('\nError: No application specified.\n') 3066 sys.exit(1) 3067 3068 sys.path.insert(0, '.') 3069 sys.modules.setdefault('bottle', sys.modules['__main__']) 3070 3071 host, port = (opt.bind or 'localhost'), 8080 3072 if ':' in host: 3073 host, port = host.rsplit(':', 1) 3074 3075 run(args[0], host=host, port=port, server=opt.server, 3076 reloader=opt.reload, plugins=opt.plugin, debug=opt.debug) 3077 3078 3079 3080 3081 # THE END