bottle框架剖析
bottle框架剖析
使用
源码分析
一、使用
大致有以下几部分
quick start
request routing
generate contents
request Data
templates
quick start
下载: pip install bottle
from bottle import route, run @route('/hello')
def hello():
return "Hello World!" run(host='localhost', port=8080, debug=True)
visit http://localhost:8080/hello将会看到hello world
默认app
当第一次运行route() 会自动产生一个Bottle类的一个实例,bottle
request routing
route 装饰器将url path 与回调函数联系起来。并给默认的app 添加一个url.一个app可以添加好多个url.
like the following
@route('/')
@route('/hello/<name>')
def greet(name='Stranger'):
return template('Hello {{name}}, how are you?', name=name)
多个url
from bottle import route, run, template @route('/')
@route('/hello/<name>')
def index(name='stranger'):
return template('<b>hello</b>{{name}}',name=name) run(host='127.0.0.1', port=8080)
1可以将多个路由绑定到一个回调函数上。
2您可以添加通配符到URL并通过关键字参数访问它们。
动态路由
url里面有通配符的叫做动态路由,匹配一个或者多个url 在同样时间里。
A simple wildcard consists of a name enclosed in angle brackets (e.g. <name>
) and accepts one or more characters up to the next slash (/
).
For example, the route /hello/<name>
accepts requests for /hello/alice
as well as /hello/bob
, but not for /hello
, /hello/
or /hello/mr/smith
.
每一个通配符都会将URL包含到那部分作为关键字参数传给请求的回调函数。
HTTP 请求方法
使用route()装饰器的method关键字参数或者是用5种装饰器 get() post() put() delete() or patch()
reponse object
Response 的元数据比如状态吗,响应头,cookie 都存储在response 对象里。
Status Code
默认200 ok ,通常不需要手动设置
Response Header
Response headers such as Cache-Control
or Location
are defined via Response.set_header()
. This method takes two parameters, a header name and a value. The name part is case-insensitive:
响应头。比如Cache-Control or Location 通过Response.set_header() 定义。这个方法需要两个参数,一个是头名字,一个是值,名称部分不区分大小写。
大部分的头是独一无二的。意味着只有一个标头会被发给客户端,有些特别的头允许在相应出现不止一次,为了添加额外的标头,使用Response.add_header() 去代替Response.set_header()
response.set_header('Set-Cookie', 'name=value')
response.add_header('Set-Cookie', 'name2=value2')
这只是个例子,如果想使用cookie. 往下看。
Cookies
访问之前定义的cookie 可以通过 Request.get_cookie(),设置新的cookies 用Response.set_cookie()
@route('/hello')
def hello_again():
if request.get_cookie("visited"):
return "Welcome back! Nice to see you again"
else:
response.set_cookie("visited", "yes")
return "Hello there! Nice to meet you"
Response.set_cookie()
还接受一系列的关键字参数
- max_age: Maximum age in seconds. (default:
None
) 存在时间 - expires: A datetime object or UNIX timestamp. (default:
None
) 到期时间,datetime 对象或者unix timestamp - domain: The domain that is allowed to read the cookie. (default: current domain)
- path: Limit the cookie to a given path (default:
/
) 限制cookie 取得路径 - secure: Limit the cookie to HTTPS connections (default: off).
- httponly: Prevent client-side javascript to read this cookie (default: off, requires Python 2.7 or newer).
- same_site: Disables third-party use for a cookie. Allowed attributes: lax and strict.
- In strict mode the cookie will never be sent. In lax mode the cookie is only sent with a top-level GET request.
使用cookie 注意的几点
- cookie 被限制在4KB以内。
- 有时候用户会选择将cookie 服务关掉,确保你的应用在没哟cookie情况下仍然能够使用
- cookie 被存储在客户端,并且没有被加密,用户能够读取它。更糟糕的是,攻击者可能偷掉用户的cookies,通过xss 攻击,
- 一些病i毒也知道读取浏览器的cookie,,因此永远不要吧机密信息放在cookie中。
- cookie很容易被篡改。不要信任cookie
signed cookie
cookies 可以轻易的被恶意的客户端篡改。Bottle能够通过加密技术签名你的cookie,来防止被篡改。
你所需要做的是提供一个key 用过secret 关键字参数传进去。无论什么时候去读或者设置cookie 保持这个key 是秘密的。如果cookie 没有签名或者不匹配,Resuqst.get_cookie将返回None
REQUEST DATA
request object 是 BaseRequest 的子类。有丰富的借口去访问数据。
FormDic
使用一种特别的字典类型去存储data 和cookies ,FormDict 的行为像是一个正常的字典,但是额外的特色让你生活更简单,轻松。
属性访问
多值一个key getall()
FormsDict
is a subclass of MultiDict
and can store more than one value per key.
The standard dictionary access methods will only return a single value, but the getall()
method returns a (possibly empty) list of all values for a specific key:
cookies
所有被客户端发来的cookies 通过BaseRequest.cookies(a FormDcit)都是可用的
from bottle import route, request, response
@route('/counter')
def counter():
count = int( request.cookies.get('counter', '') )
count += 1
response.set_cookie('counter', str(count))
return 'You visited this page %d times' % count
.get_cookie() 可以解密签名的cookies
HTTP HEADERS
所有从客户端发的HTTP headers 都被存放在 WSGIHeaderDcit 里,并能通过BaseRequest.headers 属性访问到,WSGIHeaderDcit 是基于key 大小写不敏感的字典。
from bottle import route, request
@route('/is_ajax')
def is_ajax():
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return 'This is an AJAX request'
else:
return 'This is a normal request'
Query Variables
通过BaseRequest.query属性(FormDict)
BaseRequest.query_string 是获得整个字符串。
POST FORM 表单
post data 存放在 BaseRequest.forms
JSON Content
BaseRequest.json 包含解析后的数据结构。
源码分析:
从request 入手
bootle 框架,只有一个bottle.py 文件,
若不指明,以下所有代码都是bottl.py 文件里的
bottle.py
request = LocalRequest()
class LocalRequest(BaseRequest):
''' A thread-local subclass of :class:`BaseRequest` with a different
set of attributes for each thread. There is usually only one global
instance of this class (:data:`request`). If accessed during a
request/response cycle, this instance always refers to the *current*
request (even on a multithreaded server). '''
bind = BaseRequest.__init__
environ = local_property()
def local_property(name=None):
if name: depr('local_property() is deprecated and will be removed.') #0.12
ls = threading.local()
def fget(self):
try: return ls.var
except AttributeError:
raise RuntimeError("Request context not initialized.")
def fset(self, value): ls.var = value
def fdel(self): del ls.var
return property(fget, fset, fdel, 'Thread-local property')
在local_prperty() 函数中,定义了三个子函数,并用到了property修饰,使得ls 可以获取值,设置值。删除值,
比如request.environ #获取
request.environ= 'asdasds' #设置
del request.environ #删除
自己测试
dic={} 没用
'
>>> class Foo():
dic={}
def __init__(self,na):
self._name = na
def fget(self):
return self._name
def fset(self,value):
self._name = value
return value
def fdel(self):
del self._name
name = property(fget,fset,fdel) >>> f1=Foo()
Traceback (most recent call last):
File "<pyshell#15>", line 1, in <module>
f1=Foo()
TypeError: __init__() missing 1 required positional argument: 'na'
>>> f1=Foo('yuyang')
>>> f1.name
'yuyang'
>>> f1.name = 'alex'
>>> f1.name
'alex'
>>> del f1.name
>>> f1.name
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
f1.name
File "<pyshell#14>", line 6, in fget
return self._name
AttributeError: 'Foo' object has no attribute '_name'
>>> class Foo():
dic={}
def __init__(self,na):
self._name = na
def fget(self):
return self._name
def fset(self,value):
self._name = value
return value name = property(fget,fset) >>> f1=Foo('yuyang')
>>> f1.name
'yuyang'
>>> f1.name ='da
SyntaxError: EOL while scanning string literal
>>> f1.name ='da'
>>> del f1.name
Traceback (most recent call last):
File "<pyshell#28>", line 1, in <module>
del f1.name
AttributeError: can't delete attribute
>>>
property([fget[, fset[, fdel[, doc]]]])
Return a property attribute for new-style classes (classes that derive from object).
fget is a function for getting an attribute value, likewise fset is a function for setting, and fdel a function for del’ing, an attribute. Typical use is to define a managed attribute x:
https://docs.python.org/release/2.6/library/functions.html#property
environ = property(fget, fset, fdel, 'Thread-local property') #用到了闭包函数。
ls = threading.local()
Thread-local data is data whose values are thread specific. To manage thread-local data, just create an instance of local
(or a subclass) and store attributes on it:
mydata = threading.local()
mydata.x = 1
The instance’s values will be different for separate threads.
- class
threading.
local
-
A class that represents thread-local data.
For more details and extensive examples, see the documentation string of the
_threading_local
module.import time
import threading
from threading import Thread def func(i):
mydata.x=i
print('xiancheng %s mydata.x is %s' %(i,mydata.x)) mydata = threading.local()
mydata.x = 'zhuxiancheng'
ls = []
for i in range(2):
t = Thread(target=func,args=(i,))
ls.append(t)
t.start() for i in ls:
i.join() print(mydata.x)
print('end')
从上面的代码可以看出threading.local() 在不同的线程的值是不一样的。
总的来说 bottle框架只有一个py文件 当import bottle 时
做了以下几件事
request = LocalRequest() #: A thread-safe instance of :class:`LocalResponse`. It is used to change the
#: HTTP response for the *current* request.
response = LocalResponse() #: A thread-safe namespace. Not used by Bottle.
local = threading.local() # Initialize app stack (create first empty Bottle app)
# BC: 0.6.4 and needed for run()
app = default_app = AppStack() app.push()
实例化一个request对象,response对象,执行,app = default_app = AppStack() 返回app=AppStack 实例对象 app.push() 会在列表里添加一个bottle 实例对象。
class AppStack(list):
""" A stack-like list. Calling it returns the head of the stack. """ def __call__(self):
""" Return the current default application. """
return self[-1] def push(self, value=None):
""" Add a new :class:`Bottle` instance to the stack """
if not isinstance(value, Bottle):
value = Bottle()
self.append(value)
return value
AppStack 类继承Python列表
local = threading.local()
这是一个线程安全的名称空间
第二部分 route
first_bottle.py
@route('/hello')
def hello():
if request.get_cookie('account'):
return 'welcome back! nice to see you again!'
else:
return 'you are not logged in ,access denied!'
源码:
route = make_default_app_wrapper('route') def make_default_app_wrapper(name):
''' Return a callable that relays calls to the current default app. '''
@functools.wraps(getattr(Bottle, name))
def wrapper(*a, **ka):
return getattr(app(), name)(*a, **ka)
return wrapper
@route('/hello') 是一个有参装饰器。 关于有参装饰器
@route('/hello')
def login():
pass
实际会执行以下的代码:
tmp = route('/hello') #第一步 def login(): #第二步
pass
login = tmp(login) #第三步
当然在import bottle.py 时,就执行了这句代码:
route = make_default_app_wrapper('route')
所以这时候route 实际是 wrapper 函数。
执行步骤应该是:
tmp = wrapper('/hello')
tmp = getattr(app(), name)(*a, **ka)
app 之前分析是由AppStack类实例出的对象,app() 会调用__call__() 方法,返回self[-1] 实际就是bottle 实例对象。 我们知道name= 'route'
从bottle对象去反射,找route方法,并调用 让我们看下Bottle 类下的route 方法做了什么
def route(self, path=None, method='GET', callback=None, name=None,
apply=None, skip=None, **config):
""" A decorator to bind a function to a request URL. Example:: @app.route('/hello/:name')
def hello(name):
return 'Hello %s' % name The ``:name`` part is a wildcard. See :class:`Router` for syntax
details. :param path: Request path or a list of paths to listen to. If no
path is specified, it is automatically generated from the
signature of the function.
:param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
methods to listen to. (default: `GET`)
:param callback: An optional shortcut to avoid the decorator
syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
:param name: The name for this route. (default: None)
:param apply: A decorator or plugin or a list of plugins. These are
applied to the route callback in addition to installed plugins.
:param skip: A list of plugins, plugin classes or names. Matching
plugins are not installed to this route. ``True`` skips all. Any additional keyword arguments are stored as route-specific
configuration and passed to plugins (see :meth:`Plugin.apply`).
"""
if callable(path): path, callback = None, path
plugins = makelist(apply)
skiplist = makelist(skip)
def decorator(callback):
# TODO: Documentation and tests
if isinstance(callback, basestring): callback = load(callback)
for rule in makelist(path) or yieldroutes(callback):
for verb in makelist(method):
verb = verb.upper()
route = Route(self, rule, verb, callback, name=name,
plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)
return callback
return decorator(callback) if callback else decorator
分析这段代码,不难得知,我们将'/hello'传进去,path = '/hello'
先判断path 是不是可调用对象,比如函数,实例等,如果是的话,callback 和path 的值互换。
makelist() 是吧一个对象放在列表中
def makelist(data): # This is just to handy
if isinstance(data, (tuple, list, set, dict)): return list(data)
elif data: return [data]
else: return []
这个方法其实也是一个装饰器。 所以回到最初
getattr(app(), name)(*a, **ka) 这段代码执行结果会得到decorator(callback) if callback else decorator 这次我们并没有传递callback 进去,所以会得到decorator 所以tmp = decorator 至此有参装饰器执行完第一步, 第二步是定义login 函数
第三步
login = decorator(login)
def decorator(callback):
# TODO: Documentation and tests
if isinstance(callback, basestring): callback = load(callback)
for rule in makelist(path) or yieldroutes(callback):
for verb in makelist(method):
verb = verb.upper()
route = Route(self, rule, verb, callback, name=name,
plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)
return callback
callback参数对应是login 函数
basestring = str #bottle.py 提前定义的 所以这段代码执行完,会实例化一个route 对象,并吧route 对象添加到bottle 对象里。返回callback 函数,所以得出被route装饰后的login 实际还是原来的login 函数。
def add_route(self, route):
''' Add a route object, but do not change the :data:`Route.app`
attribute.'''
self.routes.append(route)
self.router.add(route.rule, route.method, route, name=route.name)
class Bottle
self.routes = [] # List of installed :class:`Route` instances.
self.router = Router() # Maps requests to :class:`Route` instances.
有点复杂,先略过 执行路由大致分析完了 现在让我们分析最后让服务器启动的代码
first_bottle.py
run(host='127.0.0.1', port=8080,reloader=True)
def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
interval=1, reloader=False, quiet=False, plugins=None,
debug=None, **kargs):
""" Start a server instance. This method blocks until the server terminates. :param app: WSGI application or target string supported by
:func:`load_app`. (default: :func:`default_app`)
:param server: Server adapter to use. See :data:`server_names` keys
for valid names or pass a :class:`ServerAdapter` subclass.
(default: `wsgiref`)
:param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
all interfaces including the external one. (default: 127.0.0.1)
:param port: Server port to bind to. Values below 1024 require root
privileges. (default: 8080)
:param reloader: Start auto-reloading server? (default: False)
:param interval: Auto-reloader interval in seconds (default: 1)
:param quiet: Suppress output to stdout and stderr? (default: False)
:param options: Options passed to the server adapter.
reloader 参数表示是不是重新加载,默认是false ,interval 表示隔多长时间加载一次,默认是一秒。
quiet 表示是不是废止输出错误信息等。默认是输出错误信息等。如果是True.不会再终端打印某些错误信息等。
if reloader and not os.environ.get('BOTTLE_CHILD'):
try:
lockfile = None
fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
os.close(fd) # We only need this file to exist. We never write to it
while os.path.exists(lockfile):
args = [sys.executable] + sys.argv
environ = os.environ.copy()
environ['BOTTLE_CHILD'] = 'true'
environ['BOTTLE_LOCKFILE'] = lockfile
p = subprocess.Popen(args, env=environ)
while p.poll() is None: # Busy wait...
os.utime(lockfile, None) # I am alive!
time.sleep(interval)
if p.poll() != 3:
if os.path.exists(lockfile): os.unlink(lockfile)
sys.exit(p.poll())
except KeyboardInterrupt:
pass
finally:
if os.path.exists(lockfile):
os.unlink(lockfile)
return
上面这段是当你设置参数重新加载为True 执行的代码,暂不分析这段代码,
以下应该是run 函数的核心代码。
try:
if debug is not None: _debug(debug)
app = app or default_app()
if isinstance(app, basestring):
app = load_app(app)
if not callable(app):
raise ValueError("Application is not callable: %r" % app) for plugin in plugins or []:
app.install(plugin) if server in server_names:
server = server_names.get(server)
if isinstance(server, basestring):
server = load(server)
if isinstance(server, type):
server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
raise ValueError("Unknown or unsupported server: %r" % server) server.quiet = server.quiet or quiet
if not server.quiet:
_stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server)))
_stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
_stderr("Hit Ctrl-C to quit.\n\n") if reloader:
lockfile = os.environ.get('BOTTLE_LOCKFILE')
bgcheck = FileCheckerThread(lockfile, interval)
with bgcheck:
server.run(app)
if bgcheck.status == 'reload':
sys.exit(3)
else:
server.run(app)
except KeyboardInterrupt:
pass
except (SystemExit, MemoryError):
raise
except:
if not reloader: raise
if not getattr(server, 'quiet', quiet):
print_exc()
time.sleep(interval)
sys.exit(3)
debug 默认是None, 下面这句并不执行。当debug is None
if debug is not None: _debug(debug)
app = app or default_app()
if isinstance(app, basestring):
app = load_app(app)
if not callable(app):
raise ValueError("Application is not callable: %r" % app)
app 由之前分析,是个AppStack 实例对象,app 有__call__方法,所以是可调用对象
测试: >>> class Foo:
pass >>> isinstance(Foo,type)
True
>>> callable(Foo)
True
>>> f1 = Foo()
>>> callable(f1)
False
>>> class Foo:
def __call__(self):
pass >>> f1 = Foo()
>>> callable(f1)
True
只要一个对象有__call__ 属性就是可调用对象
if server in server_names:
server = server_names.get(server)
if isinstance(server, basestring):
server = load(server)
if isinstance(server, type):
server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
raise ValueError("Unknown or unsupported server: %r" % server)
server = 'wsgiref'
server_names = {
'cgi': CGIServer,
'flup': FlupFCGIServer,
'wsgiref': WSGIRefServer,
'waitress': WaitressServer,
'cherrypy': CherryPyServer,
'paste': PasteServer,
'fapws3': FapwsServer,
'tornado': TornadoServer,
'gae': AppEngineServer,
'twisted': TwistedServer,
'diesel': DieselServer,
'meinheld': MeinheldServer,
'gunicorn': GunicornServer,
'eventlet': EventletServer,
'gevent': GeventServer,
'geventSocketIO':GeventSocketIOServer,
'rocket': RocketServer,
'bjoern' : BjoernServer,
'auto': AutoServer,
}
server_names 是个字典,key 是server 字符串,value 是对应的类。 至此,server = WSGIRefServer
执行下面代码:
if isinstance(server, type):
server = server(host=host, port=port, **kargs)
server 是一个WSGIRefServer 实例化的对象。
ServerAdapter 类是一个抽象类,所有服务器的类都继承这个类。 到了关键的服务器启动的一步
server.run(app)
server.run(app)
来看WSGIRefServe 类
class WSGIRefServer(ServerAdapter):
def run(self, app): # pragma: no cover
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
from wsgiref.simple_server import make_server
import socket class FixedHandler(WSGIRequestHandler):
def address_string(self): # Prevent reverse DNS lookups please.
return self.client_address[0]
def log_request(*args, **kw):
if not self.quiet:
return WSGIRequestHandler.log_request(*args, **kw) handler_cls = self.options.get('handler_class', FixedHandler)
server_cls = self.options.get('server_class', WSGIServer) if ':' in self.host: # Fix wsgiref for IPv6 addresses.
if getattr(server_cls, 'address_family') == socket.AF_INET:
class server_cls(server_cls):
address_family = socket.AF_INET6 srv = make_server(self.host, self.port, app, server_cls, handler_cls)
srv.serve_forever()
ServerAdapter类,初始化,run方法又子类重写。
class ServerAdapter(object):
quiet = False
def __init__(self, host='127.0.0.1', port=8080, **options):
self.options = options
self.host = host
self.port = int(port) def run(self, handler): # pragma: no cover
pass
看到这里,我想我要去研究WSGIREF 模块的源码去了。。。。。。。
server.run(app)主要看红色部分的代码,调用了wsgiref 模块
调用wsgiref的make_server 实例了一个srv对象,srv调用serve_forever() 方法
simple_server.py
def make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server
实例了WSGIServer对象
WSGIServer 继承自HTTPServer ,
class WSGIServer(HTTPServer): """BaseHTTPServer that implements the Python WSGI protocol""" application = None def server_bind(self):
HTTPServer ,继承自socketserver.TCPServer
class HTTPServer(socketserver.TCPServer): allow_reuse_address = 1 # Seems to make sense in testing environment def server_bind(self):
"""Override server_bind to store the server name."""
socketserver.TCPServer.server_bind(self)
host, port = self.server_address[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
TCPServer继承自BaseServer、
创建一个socket对象
然后执行server_bind()方法和server_active()方法
server_bind()绑定ip端口,设置基本环境变量
class TCPServer(BaseServer): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
绑定
TCPServer 类下的 def server_bind(self):
"""Called by constructor to bind the socket. May be overridden. """
if self.allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()
监听
def server_activate(self):
"""Called by constructor to activate the server. May be overridden. """
self.socket.listen(self.request_queue_size)
至此实例化结束,执行srv.serve_forever() 这个函数是处理请求,直到关机。使用轮询,用到了selector 模块,
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
# XXX: Consider using another file descriptor or connecting to the
# socket to wake this up instead of polling. Polling reduces our
# responsiveness to a shutdown request and wastes cpu at all other
# times.
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ) while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock() self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
note :这里的self.__is_shut_down 是一个event 对象,
self.__is_shut_down = threading.Event()
self.__shutdown_request = False
bottle框架剖析的更多相关文章
- 关于python的bottle框架跨域请求报错问题的处理
在用python的bottle框架开发时,前端使用ajax跨域访问时,js代码老是进入不了success,而是进入了error,而返回的状态却是200.url直接在浏览器访问也是正常的,浏览器按F12 ...
- python bottle框架
python bottle框架 简介: Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Python的标准库外,其不依赖任何其他模块. Bottl ...
- Python开发者须知 —— Bottle框架常见的几个坑
Bottle是一个小巧实用的python框架,整个框架只有一个几十K的文件,但却包含了路径映射.模板.简单的数据库访问等web框架组件,而且语法简单,部署方便,很受python开发者的青睐.Pytho ...
- Python自动化运维之29、Bottle框架
Bottle 官网:http://bottlepy.org/docs/dev/index.html Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除 ...
- python bottle框架(WEB开发、运维开发)教程
教程目录 一:python基础(略,基础还是自己看书学吧) 二:bottle基础 python bottle web框架简介 python bottle 框架环境安装 python bottle 框架 ...
- 让python bottle框架支持jquery ajax的RESTful风格的PUT和DELETE等请求
这两天在用python的bottle框架开发后台管理系统,接口约定使用RESTful风格请求,前端使用jquery ajax与接口进行交互,使用POST与GET请求时都正常,而Request Meth ...
- python之Bottle框架
一.简单的Bottle框架 1)bottle框架简介 安装 pip install bottle Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架. 此框架只由一个 .py 文件,除 ...
- Bottle + WebUploader 修改Bottle框架从而大文件上传实现方案
Bottle 是个轻量级的Web框架,小巧又强大,真不愧是个轻量级的框架.可扩展性非常好,可以扩展很多功能,但是有些功能就不得不自己动手修改了. Bottle:http://www.bottlepy. ...
- Python之Bottle框架使用
本文主要包含的内容是Bottle框架介绍和安装使用. 一.Bottle框架介绍 Bottle是一个快速小巧,轻量级的 WSGI 微型 web 框架.同时Bottle也是一个简单高效的遵循WSGI的微型 ...
随机推荐
- 【Beta阶段】第十次Scrum Meeting!!!
每日任务内容: 本次会议为第十次Scrum Meeting会议~ 本次会议为团队Beta阶段的最后一次会议!! 队员 今日完成任务 刘乾 #136(完成一半,今晨发布) 团队博客撰写 https:// ...
- 在Windows Server 2008R2中部署 AspNetCore
1.部署时,先安装运行时 https://dotnet.microsoft.com/download 2.安装vc_redist.x64 https://www.microsoft.com/en- ...
- [2017BUAA软工]第一次个人项目 数独的生成与求解
零.Github链接 https://github.com/xxr5566833/sudo 一.PSP表格 PSP2.1 Personal Software Process Stages 预估耗时(分 ...
- JavaScript中给onclick绑定事件后return false遇到的问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- FICO基础知识(三)
成本中心: 成本中心是企业内的最小职责单位,是每一笔费用的具体接收者.创建成本中心主数据时必须将每个成本中心分配给标准层次结构的某个节点,标准层次结构反映了成本中心与成本中心.成本中心与成本中心组.成 ...
- python中常用的九种数据预处理方法分享
Spyder Ctrl + 4/5: 块注释/块反注释 本文总结的是我们大家在python中常见的数据预处理方法,以下通过sklearn的preprocessing模块来介绍; 1. 标准化(St ...
- python学习笔记十——模块与函数
第五章 模块与函数 5.1 python程序的结构 函数+类->模块 模块+模块->包 函数+类+模块+包=Python pyth ...
- Test Scenarios for Excel Export functionality
1 File should get exported in proper file extension2 File name for the exported excel file should be ...
- General Test Scenarios
1 all mandatory fields should be validated and indicated by askterisk(*) symbol2 validation error me ...
- CSS等高布局的7种方式
前面的话 等高布局是指子元素在父元素中高度相等的布局方式.等高布局的实现包括伪等高和真等高,伪等高只是看上去等高而已,真等高是实实在在的等高.本文将介绍边框模拟.负margin这两种伪等高以及tabl ...