You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调整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)

需解决两个问题:

  1. TypeError错误的原因是什么?
  2. 如何编写能匹配以/pyctp/get_ctp_info.ctp结尾的URL的Tornado路由规则?

问题解答

一、TypeError错误原因

Tornado的self.write()方法仅接受bytes、unicode(Python3中对应str)、dict三种类型参数,而你传入的result不符合该要求。

具体触发逻辑:

  1. post方法调用post_result,后者调用ctp_management,最终返回PyCTPManager.CTPInterface.get_ctp_info()的结果。
  2. 若get_ctp_info返回None、列表、自定义对象等非允许类型,就会触发该TypeError。
  3. 结合路由问题来看:当前路由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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:53:19