本文共 21335 字,大约阅读时间需要 71 分钟。
继上一篇博文,进一步分析Django URL解析过程。通过网上博客的学习以及对于源码阅读所做笔记。
邮箱地址:@gmail.com
个人博客: CSDN博客: 上一篇博文, run()函数,服务启动最后的动作是httpd.serve_forever,调用的是socketserver.BaseServer.serve_forever方法。该方法采用了selector网络模型进行等待数据。启动服务后服务器进入一个无限循环的状态,poll_interval用来设置循环间隔时间,默认为0.5秒。在这里先简单分析下Python 源码,上篇博文中这块的介绍没有好好的梳理。在这里进行补充下。
/usr/lib/python2.7/SocketServer.py:BaseServer.serve_forever()
class BaseServer: timeout = None def __init__(self, server_address, RequestHandlerClass): # 初始化,传入构建一个Socket服务器所必需的address以及每个请求到达时的处理类 """Constructor. May be extended, do not override.""" self.server_address = server_address # RequestHandlerClass 注册 handle 函数,在finish_request 中实例化,调用用户定义的 handle 函数 self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False def server_activate(self): """Called by constructor to activate the server. May be overridden. 服务器激活,由构造函数调用以激活服务器。可能被重写。 """ pass # 装饰器函数,重新启动被eintr中断的系统调用 def _eintr_retry(func, *args): """restart a system call interrupted by EINTR""" while True: try: return func(*args) except (OSError, select.error) as e: if e.args[0] != errno.EINTR: raise 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: while not self.__shutdown_request: # 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. # 调用 select 监视请求,处理 EINTR 异常 r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: # 有请求进来 self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set() … …
SocketServer.py中的 BaseServer 和 BaseRequestHandler
Python为网络编程提高了更高级的封装。 提供了不少网络服务的类。它们的设计很优雅。Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。在构建网络服务的时候,Server 和 RequestHandler 并不是分开的,RequestHandler的实例对象在Server 内配合 Server工作。 主要几个Server关系如下:+------------+| BaseServer |+------------+ | v+-----------+ +------------------+| TCPServer |------->| UnixStreamServer |+-----------+ +------------------+ | v+-----------+ +--------------------+| UDPServer |------->| UnixDatagramServer |+-----------+ +--------------------+
BaseServer 通过__init__初始化,对外提供serve_forever和 handler_request方法, run()函数中httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
触发,其中httpd_cls是通过type()函数动态加载的WSGIServer类,这里httpd_cls表示:<class 'django.core.servers.basehttp.WSGIServer'>
,继承自:<class 'django.core.servers.basehttp.WSGIServer'>
,socketserver.ThreadingMixIn
。
init 初始化:
def __init__(self, server_address, RequestHandlerClass): # 初始化,传入构建一个Socket服务器所必需的address以及每个请求到达时的处理类 """Constructor. May be extended, do not override.""" self.server_address = server_address # RequestHandlerClass 注册 handle 函数,在finish_request 中实例化,调用用户定义的 handle 函数 self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False
__init__源码很简单。主要作用是创建server对象,并初始化server地址和处理请求的class。熟悉socket编程应该很清楚,server_address是一个包含主机和端口的元组。
serve_forever
创建了server对象之后,使用server对象开启一个无限循环。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: while not self.__shutdown_request: # 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. # 调用 select 监视请求,处理 EINTR 异常 r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: # 有请求进来 self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set()
serve_forever接受一个参数poll_interval,用于表示select轮询的时间。然后进入一个无限循环,调用select方式进行网络IO的监听。
如果select函数返回,表示有IO连接或数据,那么将会调用_handle_request_noblock方法。_handle_request_noblock
def _handle_request_noblock(self): """Handle one request, without blocking. I assume that select.select has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). 处理一个请求,不会阻塞。 我假设select.select返回了在调用此函数之前套接字是可读的,因此get_request()中不应存在阻塞的风险。 """ try: # 接收请求 accept,get_request 由子类实现,一般为接收请求,返回 socket request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: # BaseServer.process_request 中有 BaseRequestHandler 的回调动作,实例化用户定义的 handler, __init__ 中完成对 handle() 的调用 self.process_request(request, client_address) except: self.handle_error(request, client_address) # 关闭连接 self.shutdown_request(request) else: self.shutdown_request(request)
_handle_request_noblock方法即开始处理一个请求,并且是非阻塞。该方法通过get_request方法获取连接,具体的实现在其子类。一旦得到了连接,调用verify_request方法验证请求。验证通过,即调用process_request处理请求。如果中途出现错误,则调用handle_error处理错误,以及shutdown_request结束连接。
get_request
在这里,调用的是子类class TCPServer(BaseServer)
中的get_request。TCPServer 继承了BaseServer,初始化的时候,进行了socket套接字的创建。
get_request:
该类最重要的方法就是 get_request。该方法进行返回socket对象的请求连接。def get_request(self): """Get the request and client address from the socket. May be overridden. 从套接字获取请求和客户端地址。可以覆盖。 """ return self.socket.accept()
get_request方法是在BaseServer基类中的_handle_request_noblock中调用,从那里里传入套接字对象获取的连接信息。如果是UDPServer,这里获取的就是UDP连接。
此外,TCPServer还提供了一个 fileno 方法,提供给基类的select调用返回文件描述符。verify_request
def verify_request(self, request, client_address): """Verify the request. May be overridden. Return True if we should proceed with this request. 验证请求。 可以覆盖。如果我们应该继续这个请求,则返回True。 """ return True
该方法对request进行验证,通常会被子类重写。简单的返回True即可,然后进入process_request方法处理请求。
process_request
在这里调用的是在ThreadingMixIn类定义的process_request函数,并不是BaseServer类中的process_request。class ThreadingMixIn: """Mix-in class to handle each request in a new thread.用于处理新线程中的每个请求的混合类。""" # Decides how threads will act upon termination of the # main process # 决定线程在主进程终止时的操作方式 daemon_threads = False def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. 与BaseServer相同,但这里作为线程。此外,此处完成异常处理。 """ try: self.finish_request(request, client_address) self.shutdown_request(request) except: self.handle_error(request, client_address) self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.启动一个新线程来处理请求""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start()
process_request方法是mixin的入口,MixIn子类通过重写该方法,进行多线程或多进程的配置。调用finish_request完成请求的处理,同时调用shutdown_request结束请求。
finish_request
def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass. 通过实例化RequestHandlerClass完成一个请求。 """ self.RequestHandlerClass(request, client_address, self)
finish_request方法将会处理完毕请求。创建RequestHandlerClass对象,并通过RequestHandlerClass做具体的处理。
其中self.RequestHandlerClass(request, client_address, self)
:在_init_()中定义为self.RequestHandlerClass = RequestHandlerClass
。
在这里,RequestHandlerClass
参数是上一博文中,在:
django.core.servers.basehttp.py
run()函数(1)中定义的WSGIRequestHandler类,在(2)中进行初始化,既调用BaseServer中的_init_()。 def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer): server_address = (addr, port) if threading: httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, server_cls), {}) # (1)这里,动态加载类,这里httpd_cls:,继承自: ,socketserver.ThreadingMixIn else: httpd_cls = server_cls httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) #(2)这里,参数WSGIRequestHandler: … …
因此,RequestHandlerClass是由django.core.servers.basehttp.py: def run() -> httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
传递过来的参数WSGIRequestHandler:django.core.servers.basehttp.WSGIRequestHandler。
也就是说当执行self.RequestHandler(request, client_address, self)时等同于执行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)
。
WSGIRequestHandler
django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)
。 在这里,初始化WSGIRequestHandler的时候可以看到并没有__init__()函数,查看继承关系, django.core.servers.basehttp.WSGIRequestHandler -> simple_server.WSGIRequestHandler -> BaseHTTPServer.BaseHTTPRequestHandler -> SocketServer.StreamRequestHandler -> SocketServer.BaseRequestHandler 在这里即调用BaseRequestHandler类_init_ 完成对 handle() 的调用。 BaseRequestHandler 分析
所有requestHandler都继承BaseRequestHandler基类。class BaseRequestHandler: def __init__(self, request, client_address, server): self.request = request # 客户端请求对象,self.client_address = client_address # 客户端地址,('192.168.31.178', 50029) self.server = server # 服务端 self.setup() # 调用子类的setup()函数,处理socket连接 try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass
BaseRequestHandler是请求处理程序类的基类,该类会处理每一个请求。
_init_()为每个要处理的请求实例化该类。 初始化对象的时候,设置请求request对象、client_address和server,然后调用setup方法,子类会重写该方法,用于处理socket连接,接下来的将是handler和finish方法。 要实现特定服务,需要做的就是派生一个定义handle()方法的类。所有对请求的处理,都可以重写handler方法。
可以简单的理解为:setup()是处理前的初始化操作,handle()是处理请求,finish()是清理操作。setup(self)
设置完请求request对象、client_address和server后,接着执行self.setup(),该函数在子类StreamRequestHandler实现。
StreamRequestHandler。它继承了BaseRequestHandler。基类的setup方法和finish方法被它重写,用于通过连接实现缓存文件的读写操作。
class StreamRequestHandler(BaseRequestHandler): """ rfile、wfile的默认缓冲区大小。我们默认将rfile设为buffered,否则对于大数据(每个字节的getc()调用)会很慢;我们将wfile设为无缓冲,因为(a)通常在写入()之后,我们想要读取并且需要刷新该行;(b)对未缓冲文件的大写入通常由stdio优化。 """ # 为流式套接字定义rfile和wfile rbufsize = -1 wbufsize = 0 # 应用于请求套接字的超时(如果不是None)。 timeout = None # 如果为True,则禁用此套接字的nagle算法。 # 仅在wbufsize!= 0时使用,以避免小数据包。 disable_nagle_algorithm = False def setup(self): self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) # 一个类似文件的对象,可以用来接收客户端的数据 self.wfile = self.connection.makefile('wb', self.wbufsize) # 一个类似文件的对象,可以向客户端返回数据 def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # 此处可能发生最终套接字错误,例如本地错误ECONNABORTED。 pass self.wfile.close() self.rfile.close()
setup
setup判断了是否使用nagle算法。然后设置对应的连接属性。最重要的就是创建了一个可读(rfile)和一个可写(wfile)的“文件”对象,他们实际上并不是创建了文件,而是封装了读取数据和发送数据的操作,抽象成为对文件的操作。可以理解为 self.rfile 就是读取客户端数据的对象,它有一些方法可以读取数据。self.wfile则是用来发送数据给客户端的对象。后面的操作,客户端数据到来会被写入缓冲区可读,需要向客户端发送数据的时候,只需要向可写的文件中write数据即可。
finish
在finish()中清理了对应的文件对象,同时wfile关闭之前需要刷新,将一些没有向客户端返回数据数据返回掉。def finish(self): if not self.wfile.closed: # wfile关闭之前需要刷新 try: self.wfile.flush() except socket.error: pass self.wfile.close() self.rfile.close()
handle()
django.core.servers.basehttp.handle()
handle(),调用子类django.core.servers.basehttp.WSGIRequestHandler
中的handle()函数,主要处理的是HTTP是否保持连接的问题,如果保持连接就持续处理客户请求,否则就结束,最后使用shutdown来关闭socket的功能。
def handle(self): self.close_connection = True self.handle_one_request() while not self.close_connection: # 连接没有关闭,循环处理每一个请求 self.handle_one_request() try: """ 使用shutdown来关闭socket的功能 SHUT_RDWR:关闭读写,即不能使用send/write/recv/read等 SHUT_RD:关闭读,即不能使用read/recv等 SHUT_WR:关闭写功能,即不能使用send/write等 除此之外,还将缓冲区中的内容清空 """ self.connection.shutdown(socket.SHUT_WR) except (socket.error, AttributeError): pass
handle_one_request()
def handle_one_request(self): """Copy of WSGIRequestHandler.handle() but with different ServerHandler WSGIRequestHandler.handle()的副本,但具有不同的ServerHandler """ # 读取第一行raw_requestline,一般格式应该是:COMMAND PATH VERSION\r\n;解析请求。这里:'GET /i18n/js/horizon+openstack_dashboard/ HTTP/1.1\r\n' self.raw_requestline = self.rfile.readline(65537) if len(self.raw_requestline) > 65536: # 如果解析的请求地址长度大于65536个字节,发送和记录一个414错误回复给client。 self.requestline = '' self.request_version = '' self.command = '' self.send_error(414) return # 解析请求(内部)。请求应存储在self.raw_requestline中; 结果在self.command,self.path,self.request_version和self.headers中。 # 返回True表示成功,False表示失败; 失败时,会发回错误。 if not self.parse_request(): return # 初始化一个服务器处理程序,初始化动作在父类中实现 # ServerHandler() -> # django.core.servers.basehttp.ServerHandler()-> # wsgiref.simple_server.ServerHandler() -> # wsgiref.handlers.SimpleHandler().__init__() # python重定向sys.stdin、sys.stdout和sys.stderr:标准输入、标准输出和错误输出 # handler:handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) # self: handler.request_handler = self # backpointer for logging & connection closing 用于记录和连接关闭的backpointer handler.run(self.server.get_app())
其中,self.parse_request(),既调用父类中的BaseHTTPServer.BaseHTTPRequestHandler.parse_request
(),解析请求(内部)。需要解析的请求存储在self.raw_requestline中; 结果在self.command,self.path,self.request_version和self.headers中。返回True表示成功,False表示失败; 失败时,会发回错误。 command:是一个(区分大小写)关键字,如GET或POST。 path:是一个包含请求的路径信息的字符串。应该是字符串“HTTP / 1.0”或“HTTP / 1.1”。使用URL编码方案进行编码(使用%xx表示带有十六进制代码xx的ASCII字符。 request_version:包括请求的HTTP协议版本号的字符串,样例:‘HTTP/1.0’。 headers:headers是包含头信息的mimetools.Message(或派生类)的实例;HTTP 请求(request)
http 请求分为三个部分: 1. 第一行:请求类型、地址和版本号 2. 头部信息:HTTP header 3. 数据部分 标准的 HTTP 请求是:GET / HTTP/1.1Host: cizixs.comUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateConnection: keep-aliveIf-Modified-Since: Thu, 25 Feb 2016 16:00:57 GMTCache-Control: max-age=0
标准的 HTTP 响应头部:
HTTP/1.1 304 Not ModifiedServer: GitHub.comDate: Thu, 24 Mar 2016 06:21:25 GMTLast-Modified: Thu, 25 Feb 2016 16:00:57 GMTaccess-control-allow-origin: *Expires: Thu, 24 Mar 2016 06:31:25 GMTCache-Control: max-age=600X-GitHub-Request-Id: 3AF60A59:7CE3:1C889201:56F38765 data...
parse_request()
def parse_request(self) self.command = None # set in case of error on the first line # 默认请求版本。这只会影响响应,直到请求行被解析为止,因此它主要决定客户端在发送请求格式错误时返回什么。大多数web服务器默认为HTTP 0.9,即不发送状态行。 self.request_version = version = self.default_request_version # 关闭连接设置为真 self.close_connection = 1 # 原始请求行 requestline = self.raw_requestline # 删除 requestline 字符串末尾的指定字符,这里表示换行 requestline = requestline.rstrip('\r\n') self.requestline = requestline # 默认一空格分割请求行 words = requestline.split() if len(words) == 3: command, path, version = words if version[:5] != 'HTTP/': # 如果请求版本不是HTTP/开头,则返回400 self.send_error(400, "Bad request version (%r)" % version) return False try: # 例如version=HTTP/1.1,则返回1.1 base_version_number = version.split('/', 1)[1] # 返回['1', '1'] version_number = base_version_number.split(".") # RFC 2145 3.1节规定只能有一个'.' 和主要和次要数字必须被视为单独的整数; if len(version_number) != 2: # 如果包含版本好的数字少于两个则抛出异常 raise ValueError # 类型转换(1, 1) version_number = int(version_number[0]), int(version_number[1]) except (ValueError, IndexError): self.send_error(400, "Bad request version (%r)" % version) return False # 如果版本好大于等于(1, 1),以及支持的HTTP协议版本大于等于"HTTP/1.1",则表示请求的版本号正确,关闭连接设置为假。 if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": self.close_connection = 0 if version_number >= (2, 0): self.send_error(505, "Invalid HTTP Version (%s)" % base_version_number) return False elif len(words) == 2: # 请求第一行2个,即“请求类型”、“地址”和“版本号”中缺少一个,则返回400错误。 command, path = words self.close_connection = 1 if command != 'GET': self.send_error(400, "Bad HTTP/0.9 request type (%r)" % command) return False elif not words: return False else: self.send_error(400, "Bad request syntax (%r)" % requestline) return False # 如果http请求第一行为:GET / HTTP/1.1,则:self.command=GET, self.path=/, self.request_version=HTTP/1.1 self.command, self.path, self.request_version = command, path, version # 检查标题并查找连接指令 self.headers = self.MessageClass(self.rfile, 0) # 请求头中查找“Connection: keep-alive” conntype = self.headers.get('Connection', "") if conntype.lower() == 'close': # 关闭请求连接 self.close_connection = 1 elif (conntype.lower() == 'keep-alive' and self.protocol_version >= "HTTP/1.1"): # 保持请求连接 self.close_connection = 0 return True
handle_one_request()最后执行到handler.run(self.server.get_app())
这里包含两个动作:self.server.get_app()、handler.run()self.server.get_app(),/usr/lib/python2.7/wsgiref/simple_server.py:WSGIServer().get_app()
def get_app(self): #return self.application
返回一个StaticFilesHandler类对象,继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。
这里请求的地址为:/i18n/js/horizon+openstack_dashboard/看上去是一个静态文件。handler.run(),/usr/lib/python2.7/wsgiref/handlers.py(76):BaseHandler.run()
def run(self, application): """Invoke the application调用应用程序""" try: # 为一个请求设置环境,包括输入、错误输出。 self.setup_environ() # 调用:/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/handlers.py(62)__call__() self.result = application(self.environ, self.start_response) # 发送任何可迭代数据,然后关闭self和iterable self.finish_response() except: try: self.handle_error() except: # If we get an error handling an error, just give up already! self.close() raise # ...and let the actual server figure it out.
self.result = application(self.environ, self.start_response) ->
/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/handlers.py(62)StaticFilesHandler(WSGIHandler) .call() -> /usr/local/lib/python2.7/dist-packages/django/core/handlers/wsgi.py(153)WSGIHandler(base.BaseHandler).call()def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ)# 向接受通知的注册者发送通知 request = self.request_class(environ)# 调用WSGIRequest实例化请求 response = self.get_response(request) # 调用处理方法处理request,返回给定HttpRequest的HttpResponse对象 response._handler_class = self.__class__# 设置_handler_class 为当前处理的类 status = '%d %s' % (response.status_code, response.reason_phrase)# 相应结果的状态码和对应描述 # 获取响应的响应头部信息,获取响应的cookie信息 response_headers = [ *response.items(), *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()), ] start_response(status, response_headers)# 设置响应的响应头部信息 if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):# 判断响应中是否有文件传输 response = environ['wsgi.file_wrapper'](response.file_to_stream) return response# 返回处理结果
调用处理方法处理request,返回给定HttpRequest的HttpResponse对象。
def get_response(self, request): """Return an HttpResponse object for the given HttpRequest.返回给定HttpRequest的HttpResponse对象。""" # Setup default url resolver for this thread为此线程设置默认URL解析程序 set_urlconf(settings.ROOT_URLCONF)# 设置url配置 # 调用settings.MIDDLEWARE中配置的中间件进行处理, response = self._middleware_chain(request) response._closable_objects.append(request) if response.status_code >= 400: log_response( '%s: %s', response.reason_phrase, request.path, response=response, request=request, ) return response
至此,简单的分析完成,后续随着自己的理解将进一步补充说明。
转载地址:http://utxws.baihongyu.com/