在最近写的一个RESTful API Server过程中,发现tornaod对解析POST BODY的内容有限制。
而在以前用web.py则没有这个限制,使用httplib2作为客户端。
客户端代码:
def request(self, url, method, **kwargs): request_kwargs = copy.copy(kwargs) request_kwargs.setdefault('headers', kwargs.get('headers', {})) request_kwargs['headers']['Accept'] = 'application/json' request_kwargs['headers']['Content-type'] = 'application/x-www-form-urlencoded' try: request_kwargs['body']['token'] = self.token request_kwargs['body'] = urllib.urlencode(kwargs['body']) except KeyError: pass resp, body = super(HTTPClient, self).request(self.api_url + url, method, **request_kwargs) return resp, body
上面加粗的部分中,设置header的Content-type参数,为 application/x-www-form-urlencoded,否则tornado不会解析request body中的内容。
tornado解析uri中参数的过程就不解释了,关键的地方是解析body中的内容。
body中可以有两种类型的内容,一为form提交的,二为被urlencod过的内容(也放在body)里面。
因为httplib2在使用POST/PUT/PATCH提交body内容时,不会自动设置Content-type参数,导致了tornado找不到对应的类型。
在使用self.get_argument函数时,根本得不到字段的内容。
关键代码:
首先是在HTTPConnection类中的_on_request_body回调函数:
1 def _on_request_body(self, data): 2 self._request.body = data 3 if self._request.method in ("POST", "PATCH", "PUT"): 4 httputil.parse_body_arguments( 5 self._request.headers.get("Content-Type", ""), data, 6 self._request.arguments, self._request.files) 7 self.request_callback(self._request)
httputil.parse_body_arguments函数完成对客户端使用POST/PATCH/PUT方法时,BODY内容的解析:
1 def parse_body_arguments(content_type, body, arguments, files): 2 """Parses a form request body. 3 4 Supports "application/x-www-form-urlencoded" and "multipart/form-data". 5 The content_type parameter should be a string and body should be 6 a byte string. The arguments and files parameters are dictionaries 7 that will be updated with the parsed contents. 8 """ 9 if content_type.startswith("application/x-www-form-urlencoded"): 10 uri_arguments = parse_qs_bytes(native_str(body)) 11 for name, values in uri_arguments.iteritems(): 12 values = [v for v in values if v] 13 if values: 14 arguments.setdefault(name, []).extend(values) 15 elif content_type.startswith("multipart/form-data"): 16 fields = content_type.split(";") 17 for field in fields: 18 k, sep, v = field.strip().partition("=") 19 if k == "boundary" and v: 20 parse_multipart_form_data(utf8(v), body, arguments, files) 21 break 22 else: 23 logging.warning("Invalid multipart/form-data")
函数的注释里面已经写的很清楚了:
Supports "application/x-www-form-urlencoded" and "multipart/form-data".
只支持 application/x-www-form-urlencoded 和 multipart/form-data这两种在body中提交内容的方式。