上回说道,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/