|
|
@ -29,9 +29,12 @@ __license__ = 'MIT'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cli_parse(args): # pragma: no coverage
|
|
|
|
def _cli_parse(args): # pragma: no coverage
|
|
|
|
|
|
|
|
# 导入ArgumentParser模块
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建ArgumentParser对象,设置程序名称和用法
|
|
|
|
parser = ArgumentParser(prog=args[0], usage="%(prog)s [options] package.module:app")
|
|
|
|
parser = ArgumentParser(prog=args[0], usage="%(prog)s [options] package.module:app")
|
|
|
|
|
|
|
|
# 添加参数
|
|
|
|
opt = parser.add_argument
|
|
|
|
opt = parser.add_argument
|
|
|
|
opt("--version", action="store_true", help="show version number.")
|
|
|
|
opt("--version", action="store_true", help="show version number.")
|
|
|
|
opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
|
|
|
|
opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
|
|
|
@ -45,6 +48,7 @@ def _cli_parse(args): # pragma: no coverage
|
|
|
|
opt("--reload", action="store_true", help="auto-reload on file changes.")
|
|
|
|
opt("--reload", action="store_true", help="auto-reload on file changes.")
|
|
|
|
opt('app', help='WSGI app entry point.', nargs='?')
|
|
|
|
opt('app', help='WSGI app entry point.', nargs='?')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 解析命令行参数
|
|
|
|
cli_args = parser.parse_args(args[1:])
|
|
|
|
cli_args = parser.parse_args(args[1:])
|
|
|
|
|
|
|
|
|
|
|
|
return cli_args, parser
|
|
|
|
return cli_args, parser
|
|
|
@ -179,7 +183,9 @@ def depr(major, minor, cause, fix):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def makelist(data): # This is just too handy
|
|
|
|
def makelist(data): # This is just too handy
|
|
|
|
|
|
|
|
# 判断data是否为元组、列表、集合或字典类型
|
|
|
|
if isinstance(data, (tuple, list, set, dict)):
|
|
|
|
if isinstance(data, (tuple, list, set, dict)):
|
|
|
|
|
|
|
|
# 如果是,则返回data的列表形式
|
|
|
|
return list(data)
|
|
|
|
return list(data)
|
|
|
|
elif data:
|
|
|
|
elif data:
|
|
|
|
return [data]
|
|
|
|
return [data]
|
|
|
@ -198,18 +204,24 @@ class DictProperty(object):
|
|
|
|
self.getter, self.key = func, self.key or func.__name__
|
|
|
|
self.getter, self.key = func, self.key or func.__name__
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 如果obj为None,则返回self
|
|
|
|
def __get__(self, obj, cls):
|
|
|
|
def __get__(self, obj, cls):
|
|
|
|
|
|
|
|
# 获取属性名和存储对象
|
|
|
|
if obj is None: return self
|
|
|
|
if obj is None: return self
|
|
|
|
|
|
|
|
# 如果属性名不在存储对象中,则调用getter方法获取值并存储
|
|
|
|
key, storage = self.key, getattr(obj, self.attr)
|
|
|
|
key, storage = self.key, getattr(obj, self.attr)
|
|
|
|
if key not in storage: storage[key] = self.getter(obj)
|
|
|
|
if key not in storage: storage[key] = self.getter(obj)
|
|
|
|
return storage[key]
|
|
|
|
return storage[key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 如果属性是只读的,则抛出AttributeError异常
|
|
|
|
def __set__(self, obj, value):
|
|
|
|
def __set__(self, obj, value):
|
|
|
|
if self.read_only: raise AttributeError("Read-Only property.")
|
|
|
|
if self.read_only: raise AttributeError("Read-Only property.")
|
|
|
|
getattr(obj, self.attr)[self.key] = value
|
|
|
|
getattr(obj, self.attr)[self.key] = value
|
|
|
|
|
|
|
|
|
|
|
|
def __delete__(self, obj):
|
|
|
|
def __delete__(self, obj):
|
|
|
|
|
|
|
|
# 如果属性是只读的,则抛出AttributeError异常
|
|
|
|
if self.read_only: raise AttributeError("Read-Only property.")
|
|
|
|
if self.read_only: raise AttributeError("Read-Only property.")
|
|
|
|
|
|
|
|
# 从存储对象中删除对应的值
|
|
|
|
del getattr(obj, self.attr)[self.key]
|
|
|
|
del getattr(obj, self.attr)[self.key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -737,26 +749,38 @@ class Bottle(object):
|
|
|
|
self.route('/' + '/'.join(segments), **options)
|
|
|
|
self.route('/' + '/'.join(segments), **options)
|
|
|
|
|
|
|
|
|
|
|
|
def _mount_app(self, prefix, app, **options):
|
|
|
|
def _mount_app(self, prefix, app, **options):
|
|
|
|
|
|
|
|
# 检查app是否已经被挂载,或者app的config中是否已经存在'_mount.app'键
|
|
|
|
if app in self._mounts or '_mount.app' in app.config:
|
|
|
|
if app in self._mounts or '_mount.app' in app.config:
|
|
|
|
|
|
|
|
# 如果app已经被挂载,或者app的config中已经存在'_mount.app'键,则发出警告,并回退到WSGI挂载
|
|
|
|
depr(0, 13, "Application mounted multiple times. Falling back to WSGI mount.",
|
|
|
|
depr(0, 13, "Application mounted multiple times. Falling back to WSGI mount.",
|
|
|
|
"Clone application before mounting to a different location.")
|
|
|
|
"Clone application before mounting to a different location.")
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 检查options是否为空
|
|
|
|
if options:
|
|
|
|
if options:
|
|
|
|
|
|
|
|
# 如果options不为空,则发出警告,并回退到WSGI挂载
|
|
|
|
depr(0, 13, "Unsupported mount options. Falling back to WSGI mount.",
|
|
|
|
depr(0, 13, "Unsupported mount options. Falling back to WSGI mount.",
|
|
|
|
"Do not specify any route options when mounting bottle application.")
|
|
|
|
"Do not specify any route options when mounting bottle application.")
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 检查prefix是否以'/'结尾
|
|
|
|
if not prefix.endswith("/"):
|
|
|
|
if not prefix.endswith("/"):
|
|
|
|
|
|
|
|
# 如果prefix不以'/'结尾,则发出警告,并回退到WSGI挂载
|
|
|
|
depr(0, 13, "Prefix must end in '/'. Falling back to WSGI mount.",
|
|
|
|
depr(0, 13, "Prefix must end in '/'. Falling back to WSGI mount.",
|
|
|
|
"Consider adding an explicit redirect from '/prefix' to '/prefix/' in the parent application.")
|
|
|
|
"Consider adding an explicit redirect from '/prefix' to '/prefix/' in the parent application.")
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 将app添加到_mounts列表中
|
|
|
|
self._mounts.append(app)
|
|
|
|
self._mounts.append(app)
|
|
|
|
|
|
|
|
# 将prefix添加到app的config中
|
|
|
|
app.config['_mount.prefix'] = prefix
|
|
|
|
app.config['_mount.prefix'] = prefix
|
|
|
|
|
|
|
|
# 将self添加到app的config中
|
|
|
|
app.config['_mount.app'] = self
|
|
|
|
app.config['_mount.app'] = self
|
|
|
|
|
|
|
|
# 遍历app的routes
|
|
|
|
for route in app.routes:
|
|
|
|
for route in app.routes:
|
|
|
|
|
|
|
|
# 将route的rule修改为prefix + route.rule.lstrip('/')
|
|
|
|
route.rule = prefix + route.rule.lstrip('/')
|
|
|
|
route.rule = prefix + route.rule.lstrip('/')
|
|
|
|
|
|
|
|
# 将修改后的route添加到self的routes中
|
|
|
|
self.add_route(route)
|
|
|
|
self.add_route(route)
|
|
|
|
|
|
|
|
|
|
|
|
def mount(self, prefix, app, **options):
|
|
|
|
def mount(self, prefix, app, **options):
|
|
|
@ -781,11 +805,15 @@ class Bottle(object):
|
|
|
|
parent application.
|
|
|
|
parent application.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 检查prefix是否以'/'开头
|
|
|
|
if not prefix.startswith('/'):
|
|
|
|
if not prefix.startswith('/'):
|
|
|
|
|
|
|
|
# 如果prefix不以'/'开头,则抛出ValueError异常
|
|
|
|
raise ValueError("Prefix must start with '/'")
|
|
|
|
raise ValueError("Prefix must start with '/'")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 如果app是Bottle实例,则调用_mount_app方法
|
|
|
|
if isinstance(app, Bottle):
|
|
|
|
if isinstance(app, Bottle):
|
|
|
|
return self._mount_app(prefix, app, **options)
|
|
|
|
return self._mount_app(prefix, app, **options)
|
|
|
|
|
|
|
|
# 否则,调用_mount_wsgi方法
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
return self._mount_wsgi(prefix, app, **options)
|
|
|
|
|
|
|
|
|
|
|
@ -1089,31 +1117,46 @@ class Bottle(object):
|
|
|
|
def wsgi(self, environ, start_response):
|
|
|
|
def wsgi(self, environ, start_response):
|
|
|
|
""" The bottle WSGI-interface. """
|
|
|
|
""" The bottle WSGI-interface. """
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
|
|
|
|
# 将environ传递给_handle方法,获取返回值
|
|
|
|
out = self._cast(self._handle(environ))
|
|
|
|
out = self._cast(self._handle(environ))
|
|
|
|
# rfc2616 section 4.3
|
|
|
|
# rfc2616 section 4.3
|
|
|
|
|
|
|
|
# 如果返回的状态码是100, 101, 204, 304,或者请求方法是HEAD,则关闭输出流
|
|
|
|
if response._status_code in (100, 101, 204, 304)\
|
|
|
|
if response._status_code in (100, 101, 204, 304)\
|
|
|
|
or environ['REQUEST_METHOD'] == 'HEAD':
|
|
|
|
or environ['REQUEST_METHOD'] == 'HEAD':
|
|
|
|
if hasattr(out, 'close'): out.close()
|
|
|
|
if hasattr(out, 'close'): out.close()
|
|
|
|
out = []
|
|
|
|
out = []
|
|
|
|
|
|
|
|
# 获取environ中的bottle.exc_info
|
|
|
|
exc_info = environ.get('bottle.exc_info')
|
|
|
|
exc_info = environ.get('bottle.exc_info')
|
|
|
|
|
|
|
|
# 如果有异常信息,则删除environ中的bottle.exc_info
|
|
|
|
if exc_info is not None:
|
|
|
|
if exc_info is not None:
|
|
|
|
del environ['bottle.exc_info']
|
|
|
|
del environ['bottle.exc_info']
|
|
|
|
|
|
|
|
# 调用start_response方法,设置响应状态行、响应头和异常信息
|
|
|
|
start_response(response._wsgi_status_line(), response.headerlist, exc_info)
|
|
|
|
start_response(response._wsgi_status_line(), response.headerlist, exc_info)
|
|
|
|
|
|
|
|
# 返回输出流
|
|
|
|
return out
|
|
|
|
return out
|
|
|
|
except (KeyboardInterrupt, SystemExit, MemoryError):
|
|
|
|
except (KeyboardInterrupt, SystemExit, MemoryError):
|
|
|
|
|
|
|
|
# 如果捕获到KeyboardInterrupt, SystemExit, MemoryError异常,则抛出
|
|
|
|
raise
|
|
|
|
raise
|
|
|
|
except Exception as E:
|
|
|
|
except Exception as E:
|
|
|
|
|
|
|
|
# 如果没有开启catchall,则抛出异常
|
|
|
|
if not self.catchall: raise
|
|
|
|
if not self.catchall: raise
|
|
|
|
|
|
|
|
# 构造错误页面
|
|
|
|
err = '<h1>Critical error while processing request: %s</h1>' \
|
|
|
|
err = '<h1>Critical error while processing request: %s</h1>' \
|
|
|
|
% html_escape(environ.get('PATH_INFO', '/'))
|
|
|
|
% html_escape(environ.get('PATH_INFO', '/'))
|
|
|
|
|
|
|
|
# 如果开启了DEBUG模式,则输出错误信息和堆栈信息
|
|
|
|
if DEBUG:
|
|
|
|
if DEBUG:
|
|
|
|
err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
|
|
|
|
err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
|
|
|
|
'<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
|
|
|
|
'<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
|
|
|
|
% (html_escape(repr(E)), html_escape(format_exc()))
|
|
|
|
% (html_escape(repr(E)), html_escape(format_exc()))
|
|
|
|
|
|
|
|
# 将错误页面写入environ中的wsgi.errors
|
|
|
|
environ['wsgi.errors'].write(err)
|
|
|
|
environ['wsgi.errors'].write(err)
|
|
|
|
|
|
|
|
# 刷新wsgi.errors
|
|
|
|
environ['wsgi.errors'].flush()
|
|
|
|
environ['wsgi.errors'].flush()
|
|
|
|
|
|
|
|
# 设置响应头
|
|
|
|
headers = [('Content-Type', 'text/html; charset=UTF-8')]
|
|
|
|
headers = [('Content-Type', 'text/html; charset=UTF-8')]
|
|
|
|
|
|
|
|
# 调用start_response方法,设置响应状态行、响应头和异常信息
|
|
|
|
start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
|
|
|
|
start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
|
|
|
|
|
|
|
|
# 返回错误页面
|
|
|
|
return [tob(err)]
|
|
|
|
return [tob(err)]
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self, environ, start_response):
|
|
|
|
def __call__(self, environ, start_response):
|
|
|
|