上回说道,IOLoop接收到客户端的连接并生成HTTPConnection对象。接上回:HTTPConnection的主要功能是读取客户端发来的数据,并以http协议解析,生成HTTPRequest对象。HTTP协议在相关的rfc文档中有描述,心里头有个大概的了解就行。用python代码来实现http解析也不是很难,大概就这么几步:解析消息头,一般的get,head,delete,option类型的请求只有消息头,无消息体。如果是post或者put请求,会有消息体,主要是提交页面<form>中的表单数据,如果有上传的文件,还需要解析这上传文件数据。现在详细说说代码,发一下HTTPConnection的构造函数的code,因为这里的故事,从构造HTTPConnection开始。翠花,上代码:


1. def __init__(self, stream, address, request_callback, no_keep_alive=False,  
2. False):  
3. self.stream = stream  
4. if self.stream.socket.family not in (socket.AF_INET, socket.AF_INET6):  
5. # Unix (or other) socket; fake the remote address  
6. '0.0.0.0', 0)  
7. self.address = address  
8. self.request_callback = request_callback  # 亲,留意一下这个回调函数  
9. self.no_keep_alive = no_keep_alive  
10. self.xheaders = xheaders  
11.       
12. self._request = None  
13. self._request_finished = False  
14. # Save stack context here, outside of any request.  This keeps  
15. # contexts from one request from leaking into the next.  
16. self._header_callback = stack_context.wrap(self._on_headers)  
17. self.stream.read_until(b("\r\n\r\n"), self._header_callback)    # 向epoll注册read事件  
18. self._write_callback = None


代码不是很难,关键的一行是:self.stream.read_until(b("\r\n\r\n"), self._header_callback)。 这是一个异步操作,当读取到 "\r\n\r\n"时,就触发 _header_callback,这个回调函数,其实就是HTTPConnection的_on_headers方法,主要用于解析请求头。Http请求头大概是这个样子的(如果脑子里完全没有概念的话,读代码会很辛苦的,有时候一图抵万言):

除了图的第一行外,其他都是以 Key: Value 这种样式,第一行有三个部分组成:HTTP_METHOD, REQUEST_URI, HTTP_VERSION

      有了上面一张图,现在就分析一下_on_headers方法,主要用于解析request header (代码有所省略):



1. data = native_str(data.decode('latin1'))  
2. eol = data.find("\r\n")  
3. start_line = data[:eol]  
4. try:  
5. " ")  # 解析第一行,获取:HTTP_METHOD, REQUEST_URI, HTTP_VERSION  
6. except ValueError:  
7. raise _BadRequestException("Malformed HTTP request line")  
8. if not version.startswith("HTTP/"):  
9. raise _BadRequestException("Malformed HTTP version in HTTP Request-Line")  
10. headers = httputil.HTTPHeaders.parse(data[eol:])   # 解析请求头的剩余部分  
11. self._request = HTTPRequest(  
12. self, method=method, uri=uri, version=version,  
13. self.address[0])  
14.   
15. content_length = headers.get("Content-Length")  
16. if content_length:  # 一般 post请求 有content-length,而 get请求 只有 request header  
17.     content_length = int(content_length)  
18. if content_length > self.stream.max_buffer_size:  
19. raise _BadRequestException("Content-Length too long")  
20. if headers.get("Expect") == "100-continue":  
21. self.stream.write(b("HTTP/1.1 100 (Continue)\r\n\r\n"))  
22. self.stream.read_bytes(content_length, self._on_request_body)  # 这是一个异步操作,读取body。  
23. return  
24.   
25. self.request_callback(self._request)  # 注意这一行,如果是GET, HEAD等这种没有请求体请求,就直接把请求提交给相应的处理函数。


针对POST和PUT有消息体的请求,还需要接着解析消息体, self.stream.read_bytes(content_length, self._on_request_body) 也是异步操作,解析消息体的方法是_on_request_body,主要的代码:


1. if content_type.startswith("application/x-www-form-urlencoded"):  
2. self._request.body))  
3. for name, values in arguments.iteritems():  
4. for v in values if v]  
5. if values:  
6. self._request.arguments.setdefault(name, []).extend(  
7.                 values)  
8. elif content_type.startswith("multipart/form-data"):    # 包含文件上传  
9. ";")  
10. for field in fields:  
11. "=")  
12. if k == "boundary" and v:  
13.             httputil.parse_multipart_form_data(  
14.                 utf8(v), data,  
15. self._request.arguments,  
16. self._request.files)  
17. break  
18. else:  
19. "Invalid multipart/form-data")


代码也不是很难,看到application/x-www-form-urlencoded 与 multipart/form-data 是不是觉得很熟悉,写表单,上传文件的时候<form>中的属性。呵呵,原来就这么一回事。

      言而总之,总而言之,HTTPConnection主要将socket连接接收到的请求数据解析成一个HttpRequest对象,最后将这个对象提交给相应的handler处理,完成最后一击的是:self.request_callback(self._request) 。self.request_callback 在构造函数中初始化。详细功能,待下回分解。

参考 :http://www.darkbull.net/article/tornado_src_study_httpconnection/