上回说道,IOLoop接收到客户端的连接并生成HTTPConnection对象。接上回:HTTPConnection的主要功能是读取客户端发来的数据,并以http协议解析,生成HTTPRequest对象。HTTP协议在相关的rfc文档中有描述,心里头有个大概的了解就行。用python代码来实现http解析也不是很难,大概就这么几步:解析消息头,一般的get,head,delete,option类型的请求只有消息头,无消息体。如果是post或者put请求,会有消息体,主要是提交页面<form>中的表单数据,如果有上传的文件,还需要解析这上传文件数据。现在详细说说代码,发一下HTTPConnection的构造函数的code,因为这里的故事,从构造HTTPConnection开始。翠花,上代码:
- def __init__(self, stream, address, request_callback, no_keep_alive=False,
- xheaders=False):
- self.stream = stream
- if self.stream.socket.family not in (socket.AF_INET, socket.AF_INET6):
- # Unix (or other) socket; fake the remote address
- address = ('0.0.0.0', 0)
- self.address = address
- self.request_callback = request_callback # 亲,留意一下这个回调函数
- self.no_keep_alive = no_keep_alive
- self.xheaders = xheaders
- self._request = None
- self._request_finished = False
- # Save stack context here, outside of any request. This keeps
- # contexts from one request from leaking into the next.
- self._header_callback = stack_context.wrap(self._on_headers)
- self.stream.read_until(b("\r\n\r\n"), self._header_callback) # 向epoll注册read事件
- 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 (代码有所省略):
- data = native_str(data.decode('latin1'))
- eol = data.find("\r\n")
- start_line = data[:eol]
- try:
- method, uri, version = start_line.split(" ") # 解析第一行,获取:HTTP_METHOD, REQUEST_URI, HTTP_VERSION
- except ValueError:
- raise _BadRequestException("Malformed HTTP request line")
- if not version.startswith("HTTP/"):
- raise _BadRequestException("Malformed HTTP version in HTTP Request-Line")
- headers = httputil.HTTPHeaders.parse(data[eol:]) # 解析请求头的剩余部分
- self._request = HTTPRequest(
- connection=self, method=method, uri=uri, version=version,
- headers=headers, remote_ip=self.address[0])
- content_length = headers.get("Content-Length")
- if content_length: # 一般 post请求 有content-length,而 get请求 只有 request header
- content_length = int(content_length)
- if content_length > self.stream.max_buffer_size:
- raise _BadRequestException("Content-Length too long")
- if headers.get("Expect") == "100-continue":
- self.stream.write(b("HTTP/1.1 100 (Continue)\r\n\r\n"))
- self.stream.read_bytes(content_length, self._on_request_body) # 这是一个异步操作,读取body。
- return
- self.request_callback(self._request) # 注意这一行,如果是GET, HEAD等这种没有请求体请求,就直接把请求提交给相应的处理函数。
针对POST和PUT有消息体的请求,还需要接着解析消息体, self.stream.read_bytes(content_length, self._on_request_body) 也是异步操作,解析消息体的方法是_on_request_body,主要的代码:
- if content_type.startswith("application/x-www-form-urlencoded"):
- arguments = parse_qs_bytes(native_str(self._request.body))
- for name, values in arguments.iteritems():
- values = [v for v in values if v]
- if values:
- self._request.arguments.setdefault(name, []).extend(
- values)
- elif content_type.startswith("multipart/form-data"): # 包含文件上传
- fields = content_type.split(";")
- for field in fields:
- k, sep, v = field.strip().partition("=")
- if k == "boundary" and v:
- httputil.parse_multipart_form_data(
- utf8(v), data,
- self._request.arguments,
- self._request.files)
- break
- else:
- logging.warning("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/