调整Tornado路由策略后为何触发TypeError错误?
问题描述
尝试将网页从绝对路径改为相对路径后,请求后端Tornado 5.0服务器出现异常。前端AJAX代码:
$.ajax({ url:'../pyctp/get_ctp_info.ctp', type:'post', dataType:'json', data:{"InstrumentID":""}, success:function(response){
Nginx会将所有.ctp结尾的请求反向代理至服务器,此前使用绝对路径/pyctp/get_ctp_info.ctp时同样返回500错误码。
修改路由规则,在原规则r'/pyctp/(.*)\.(.*)'前添加(.*),改为:
app = Application([ ... (r'(.*)/pyctp/(.*)\.(.*)', CTPManageMentHandler), ], ...
目的是允许/pyctp/get_ctp_info.ctp前存在任意路径。
Python报错信息:
TypeError: write() only accepts bytes, unicode, and dict objects ERROR:tornado.access:500 POST /pyctp/get_ctp_info.ctp (127.0.0.1) 1.93ms ERROR:tornado.application:Uncaught exception POST /pyctp/get_ctp_info.ctp (127.0.0.1) HTTPServerRequest(protocol='http', host='139.198.17.235', method='POST', uri='/pyctp/get_ctp_info.ctp', version='HTTP/1.0', remote_ip='127.0.0.1') Traceback (most recent call last): File "/pyenv/quotecli/lib/python3.8/site-packages/tornado/web.py", line 1499, in _stack_context_handle_exception raise_exc_info((type, value, traceback)) File "<string>", line 4, in raise_exc_info File "/pyenv/quotecli/lib/python3.8/site-packages/tornado/stack_context.py", line 315, in wrapped ret = fn(*args, **kwargs) File "/pyenv/quotecli/lib/python3.8/site-packages/tornado/web.py", line 1711, in future_complete f.result() File "/pyenv/quotecli/lib/python3.8/site-packages/tornado/gen.py", line 1113, in run yielded = self.gen.send(value) File "/root/QuoteClientMajor/server_router.py", line 118, in post self.write(result) File "/pyenv/quotecli/lib/python3.8/site-packages/tornado/web.py", line 739, in write raise TypeError(message)
CTPManageMentHandler类完整代码:
class CTPManageMentHandler(base.RequestHandler): executor = ThreadPoolExecutor(8) @tornado.web.asynchronous @tornado.gen.coroutine def get(self, *args, **kwargs): url_pattern = args[0] param = get_param(self.request.query_arguments, self.session_id) result = yield self.get_result(url_pattern, param=param, *args, **kwargs) self.write(result) self.finish() @run_on_executor def get_result(self, url_type, *args, **kwargs): result = self.ctp_management( url_type, *args, **kwargs) return result @tornado.web.asynchronous @tornado.gen.coroutine def post(self, *args, **kwargs): url_pattern = args[0] #print(self.request.headers.get("X-Real-IP")) request_data = self.request.body.decode('utf8') param = get_param(self.request.query_arguments, self.session_id) if '&' in request_data or '=' in request_data: request_data = json.dumps(utils.map_to_json(request_data)) result = yield self.post_result(url_pattern, request_data, param=param, *args, **kwargs) self.write(result) #【【here is where the error occured】】 self.finish() @run_on_executor def post_result(self, url_type, request_data, *args, **kwargs): result = self.ctp_management(url_type, request_data, *args, **kwargs) return result def ctp_management(self, url_type, request_data=None, *args, **kwargs): if url_type == 'get_ctp_info': return PyCTPManager.CTPInterface.get_ctp_info(self,request_data)
需解决两个问题:
- TypeError错误的原因是什么?
- 如何编写能匹配以
/pyctp/get_ctp_info.ctp结尾的URL的Tornado路由规则?
问题解答
一、TypeError错误原因
Tornado的self.write()方法仅接受bytes、unicode(Python3中对应str)、dict三种类型参数,而你传入的result不符合该要求。
具体触发逻辑:
post方法调用post_result,后者调用ctp_management,最终返回PyCTPManager.CTPInterface.get_ctp_info()的结果。- 若
get_ctp_info返回None、列表、自定义对象等非允许类型,就会触发该TypeError。 - 结合路由问题来看:当前路由
r'(.*)/pyctp/(.*)\.(.*)'会捕获三个分组,当请求为/pyctp/get_ctp_info.ctp时,第一个分组是空字符串,你却把args[0](空字符串)赋值给url_pattern,导致ctp_management中url_type == 'get_ctp_info'判断不成立,最终返回None,直接触发write()报错。
排查&修复方向:
- 在
self.write(result)前添加print(type(result), result),确认get_ctp_info的返回值类型和内容。 - 若返回列表,需包装为dict(如
return {"data": 你的列表});若为自定义对象,需转换为dict或json字符串。 - 修正
url_pattern的取值:将url_pattern = args[0]改为url_pattern = args[1],取第二个分组(即get_ctp_info)作为业务标识。
二、匹配以/pyctp/get_ctp_info.ctp结尾的Tornado路由规则
1. 精确匹配单个URL
如果仅需匹配/pyctp/get_ctp_info.ctp(支持前缀任意路径),路由规则写为:
(r'.*/pyctp/get_ctp_info\.ctp', CTPManageMentHandler),
注意:正则中.是通配符,需转义为\.才能匹配真实的点号。
2. 匹配所有/pyctp/xxx.ctp格式的URL
如果要保留动态匹配(如get_ctp_info是可变部分),修正后的路由规则为:
(r'(.*)/pyctp/(.*)\.ctp', CTPManageMentHandler),
- 将原规则的
\.(.*)改为\.ctp,精确匹配.ctp后缀,避免匹配其他后缀。 - 同时记得在Handler的
get和post方法中,将url_pattern的取值从args[0]改为args[1],确保获取到正确的业务标识。
内容的提问来源于stack exchange,提问作者George Y
相关产品推荐
相关产品推荐

