web.py源码主体流程认识
- 简要内部流程图
- 1.上述代码是项目启动应用的入口:
if __name__=="__main__":
app = web.application(urls, globals())
#session_hook为自定义方法(hook)
app.add_processor(web.loadhook(session_hook))
#request_unlock_hook为自定义方法(hook)
app.add_processor(web.unloadhook(request_unlock_hook))
app.run() #点run方法进入源码
说明:可以点开run方法真正进入源码
—————— * 2.run方法源码:
def run(self, *middleware):
"""
Starts handling requests. If called in a CGI or FastCGI context, it will follow that protocol. If called from the command line, it will start an HTTP server on the port named in the first command line argument, or, if there is no argument, on port 8080.
`middleware` is a list of WSGI middleware which is applied to the resulting WSGI function.
"""
return wsgi.runwsgi(self.wsgifunc(*middleware))
说明:可以点开wsgi.runwsgi方法进入源码
————– * 3.wsgi.runwsgi方法源码:
def runwsgi(func):
"""
Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
as appropriate based on context and `sys.argv`.
"""
if os.environ.has_key('SERVER_SOFTWARE'): # cgi
os.environ['FCGI_FORCE_CGI'] = 'Y'
if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
or os.environ.has_key('SERVER_SOFTWARE')):
return runfcgi(func, None)
if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
args = sys.argv[1:]
if 'fastcgi' in args: args.remove('fastcgi')
elif 'fcgi' in args: args.remove('fcgi')
if args:
return runfcgi(func, validaddr(args[0]))
else:
return runfcgi(func, None)
if 'scgi' in sys.argv:
args = sys.argv[1:]
args.remove('scgi')
if args:
return runscgi(func, validaddr(args[0]))
else:
return runscgi(func)
return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
说明:因为项目默认都是以httpserver方式启动,这里看httpserver.runsimple源码
—— * 4.httpserver.runsimple源码:
def runsimple(func, server_address=("0.0.0.0", 8080)): #说明,默认以8080端口启动 """ Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. The directory `static/` is hosted statically. [cp]: http://www.cherrypy.org """ global server func = StaticMiddleware(func) func = LogMiddleware(func) server = WSGIServer(server_address, func) if server.ssl_adapter: print "https://%s:%d/" % server_address else: print "http://%s:%d/" % server_address try: server.start() except (KeyboardInterrupt, SystemExit): server.stop() server = None
说明:这里主要看两行代码WSGIServer(server_address, func)和server.start()
———— * 5.WSGIServer源码:
def WSGIServer(server_address, wsgi_app):
"""
Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
This function can be overwritten to customize the webserver or use a different webserver.
"""
import wsgiserver
"""
Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
prefix. Overwriting it make it work with web.wsgiserver.
"""
wsgiserver.ssl_adapters = {
'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
'pyopenssl':'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
}
server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
def create_ssl_adapter(cert, key):
# wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
# That doesn't work as not it is web.wsgiserver.
# Patching sys.modules temporarily to make it work.
import types
cherrypy = types.ModuleType('cherrypy')
cherrypy.wsgiserver = wsgiserver
sys.modules['cherrypy'] = cherrypy
sys.modules['cherrypy.wsgiserver'] = wsgiserver
from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
adapter = pyOpenSSLAdapter(cert, key)
# We are done with our work. Cleanup the patches.
del sys.modules['cherrypy']
del sys.modules['cherrypy.wsgiserver']
return adapter
# SSL backward compatibility
if (server.ssl_adapter is None and
getattr(server, 'ssl_certificate', None) and
getattr(server, 'ssl_private_key', None)):
server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
server.nodelay = not sys.platform.startswith('java')
# TCP_NODELAY isn't supported on the JVM
return server
说明:主要定义了一个CherryPyWSGIServer对象返回。
CherryPyWSGIServer的作用: A subclass of HTTPServer which calls a WSGI application. 它集成了HTTPServer,会启一个线程池,把从socket中获取request包装成httpconnect,然后扔给wsgi_app处理。
5.1
CherryPyWSGIServer源码
:class CherryPyWSGIServer(HTTPServer): wsgi_version = (1, 0) def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5): self.requests = ThreadPool(self, min=numthreads or 1, max=max) self.wsgi_app = wsgi_app self.gateway = wsgi_gateways[self.wsgi_version] self.bind_addr = bind_addr if not server_name: server_name = socket.gethostname() self.server_name = server_name self.request_queue_size = request_queue_size self.timeout = timeout self.shutdown_timeout = shutdown_timeout self.clear_stats() def _get_numthreads(self): return self.requests.min def _set_numthreads(self, value): self.requests.min = value numthreads = property(_get_numthreads, _set_numthreads)
说明:最主要是看self.requests = ThreadPool(self, min=numthreads or 1, max=max),这里看到request是创建了一个线程池,后面会关联起来看是什么用的。
- 6.server.start()源码:
"""Run the server forever."""
# We don't have to trap KeyboardInterrupt or SystemExit here,
# because cherrpy.server already does so, calling self.stop() for us.
# If you're using this server with another framework, you should
# trap those exceptions in whatever code block calls start().
self._interrupt = None
if self.software is None:
self.software = "%s Server" % self.version
# Select the appropriate socket
if isinstance(self.bind_addr, basestring):
# AF_UNIX socket
# So we can reuse the socket...
try:
os.unlink(self.bind_addr)
except:
pass
# So everyone can access the socket...
try:
os.chmod(self.bind_addr, 0o777)
except:
pass
info = [
(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
else:
# AF_INET or AF_INET6 socket
# Get the correct address family for our host (allows IPv6
# addresses)
host, port = self.bind_addr
try:
info = socket.getaddrinfo(
host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
except socket.gaierror:
if ':' in self.bind_addr[0]:
info = [(socket.AF_INET6, socket.SOCK_STREAM,
0, "", self.bind_addr + (0, 0))]
else:
info = [(socket.AF_INET, socket.SOCK_STREAM,
0, "", self.bind_addr)]
self.socket = None
msg = "No socket could be created"
for res in info:
af, socktype, proto, canonname, sa = res
try:
self.bind(af, socktype, proto)
except socket.error, serr:
msg = "%s -- (%s: %s)" % (msg, sa, serr)
if self.socket:
self.socket.close()
self.socket = None
continue
break
if not self.socket:
raise socket.error(msg)
# Timeout so KeyboardInterrupt can be caught on Win32
self.socket.settimeout(1)
self.socket.listen(self.request_queue_size)
# Create worker threads
self.requests.start()
self.ready = True
self._start_time = time.time()
while self.ready:
try:
self.tick()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.error_log("Error in HTTPServer.tick", level=logging.ERROR,
traceback=True)
if self.interrupt:
while self.interrupt is True:
# Wait for self.stop() to complete. See _set_interrupt.
time.sleep(0.1)
if self.interrupt:
raise self.interrupt
说明:
启动之前的request(self.requests = ThreadPool)线程;启动、监听socket;将socket信息包装成connection扔到request队列中。主要看self.tick()
- 7.server.start()源码:
"""说明:接受一个新的connect,然后put到队列中"""
try:
s, addr = self.socket.accept()
if self.stats['Enabled']:
self.stats['Accepts'] += 1
if not self.ready:
return
prevent_socket_inheritance(s)
if hasattr(s, 'settimeout'):
s.settimeout(self.timeout)
makefile = CP_fileobject
ssl_env = {}
if self.ssl_adapter is not None:
try:
s, ssl_env = self.ssl_adapter.wrap(s)
except NoSSLError:
msg = ("The client sent a plain HTTP request, but this server only speaks HTTPS on this port.")
buf = ["%s 400 Bad Request\r\n" % self.protocol,"Content-Length: %s\r\n" % len(msg),
"Content-Type: text/plain\r\n\r\n",msg]
wfile = makefile(s._sock, "wb", DEFAULT_BUFFER_SIZE)
try:
wfile.sendall("".join(buf))
except socket.error:
x = sys.exc_info()[1]
if x.args[0] not in socket_errors_to_ignore:
raise
return
if not s:
return
makefile = self.ssl_adapter.makefile
if hasattr(s, 'settimeout'):
s.settimeout(self.timeout)
conn = self.ConnectionClass(self, s, makefile)
if not isinstance(self.bind_addr, basestring):
if addr is None:
if len(s.getsockname()) == 2:
addr = ('0.0.0.0', 0)
else:
addr = ('::', 0)
conn.remote_addr = addr[0]
conn.remote_port = addr[1]
conn.ssl_env = ssl_env
try:
self.requests.put(conn)
except queue.Full:
conn.close()
return
except socket.timeout:
return
except socket.error:
x = sys.exc_info()[1]
if self.stats['Enabled']:
self.stats['Socket Errors'] += 1
if x.args[0] in socket_error_eintr:
return
if x.args[0] in socket_errors_nonblocking:
return
if x.args[0] in socket_errors_to_ignore:
return
raise
说明:从socket中接收信息,包装成connection扔到队列里。
上述简单罗列和简要说明了WSGI处理流程。后面逐一看看需要关心的重点细节.