大體有如下幾部分javascript
下載: pip install bottlehtml
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 worldjava
默認app python
當第一次運行route() 會自動產生一個Bottle類的一個實例,bottleajax
request routingshell
route 裝飾器將url path 與回調函數聯繫起來。並給默認的app 添加一個url.一個app能夠添加好多個url.json
like the followingapi
@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()
Response 的元數據好比狀態嗎,響應頭,cookie 都存儲在response 對象裏。
默認200 ok ,一般不須要手動設置
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. 往下看。
訪問以前定義的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()
還接受一系列的關鍵字參數
None
) 存在時間None
) 到期時間,datetime 對象或者unix timestamp/
) 限制cookie 取得路徑
cookies 能夠輕易的被惡意的客戶端篡改。Bottle可以經過加密技術簽名你的cookie,來防止被篡改。
你所須要作的是提供一個key 用過secret 關鍵字參數傳進去。不管何時去讀或者設置cookie 保持這個key 是祕密的。若是cookie 沒有簽名或者不匹配,Resuqst.get_cookie將返回None
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', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count
.get_cookie() 能夠解密簽名的cookies
全部從客戶端發的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'
經過BaseRequest.query屬性(FormDict)
BaseRequest.query_string 是得到整個字符串。
POST FORM 表單
post data 存放在 BaseRequest.forms
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.
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