tornado异步HTTP坑~

一、基于tornado的异步http在高qps下返回599错误

基于tornado协程实现的异步http,在高qps情况下会返回599错误码。原因就在于ioloop计算超时时间的时候,是从request请求放入queue中时开始计算的。也就是说超时时间= queue等待时间 + min(request建立连接时间 + request请求时间)。如果queue太长,实际上请求大部分时间都卡在queue中。
SimpleAsyncHTTPClient中fetch_impl实现源码:
可以看到超时时间为:self.io_loop.time() + min(request.connect_timeout,request.request_timeout)

def fetch_impl(self, request, callback):
    key = object()
    self.queue.append((key, request, callback))
    if not len(self.active) < self.max_clients:
        timeout_handle = self.io_loop.add_timeout(
            self.io_loop.time() + min(request.connect_timeout,
                                      request.request_timeout),
            functools.partial(self._on_timeout, key, "in request queue"))
    else:
        timeout_handle = None
    self.waiting[key] = (request, callback, timeout_handle)
    self._process_queue()
    if self.queue:
        gen_log.debug("max_clients limit reached, request queued. "
                      "%d active, %d queued requests." % (
                          len(self.active), len(self.queue)))

想要修改也很简单,自己定义一个HTTPClient,继承SimpleAsyncHTTPClient,然后重写fetch_impl跟_connection_class方法。
如何重写呢?
对于异步http,完全可以根据await或者yield关键字等待超时来判断,所以可以直接删掉add_timeout

response = await http_client.fetch("http://www.google.com")

具体实现如下:

class NoQueueTimeoutHTTPClient(SimpleAsyncHTTPClient):
    # 队列
    def fetch_impl(self, request, callback):
        key = object()

        self.queue.append((key, request, callback))
        self.waiting[key] = (request, callback, None)

        self._process_queue()

        if self.queue:
            gen_log.debug("max_clients limit reached, request queued. %d active, %d queued requests." % (
                len(self.active), len(self.queue)))

    # 重写连接
    def _connection_class(self):
        return _HTTPConnection1

二、tornado 204响应码强校验

背景

项目在开发http相关的某个功能的时候,没有按http规范的要求,返回了204的响应码,但是携带content内容。
在用request库发请求的时候,能够正常收到响应,但是在tornado中使用异步http却只收到了599错误码

引言

设计http相关功能的时候,务必要按照http规范进行!!!不然好多坑啊

tornado 204响应码强校验

HTTP1Connection下的_read_body方法中,有一段源码是这样的:

if code == 204:
    # This response code is not allowed to have a non-empty body,
    # and has an implicit length of zero instead of read-until-close.
    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
    if ("Transfer-Encoding" in headers or
            content_length not in (None, 0)):
        raise httputil.HTTPInputError(
            "Response with code %d should not have body" % code)
    content_length = 0

当响应码是204的时候,会强制校验content_length是否是None或者0,不是的话会抛异常。
tornado在捕获异常之后,_HTTPConnection下_handle_exception处理异常时会返回599的错误码。

def _handle_exception(self, typ, value, tb):
    if self.final_callback:
        self._remove_timeout()
        if isinstance(value, StreamClosedError):
            if value.real_error is None:
                value = HTTPStreamClosedError("Stream closed")
            else:
                value = value.real_error
        self._run_callback(HTTPResponse(self.request, 599, error=value,
                                        request_time=self.io_loop.time() - self.start_time,
                                        start_time=self.start_wall_time,
                                        ))

        if hasattr(self, "stream"):
            # TODO: this may cause a StreamClosedError to be raised
            # by the connection's Future.  Should we cancel the
            # connection more gracefully?
            self.stream.close()
        return True
    else:
        # If our callback has already been called, we are probably
        # catching an exception that is not caused by us but rather
        # some child of our callback. Rather than drop it on the floor,
        # pass it along, unless it's just the stream being closed.
        return isinstance(value, StreamClosedError)

适配方案:
自定义_HTTPConnection1,继承_HTTPConnection,重写headers_received方法,如果收到204响应码,将headers中的content-length强制改为0

class _HTTPConnection1(_HTTPConnection):
    # 添加204content_length处理
    def headers_received(self, first_line, headers):
        if self.request.expect_100_continue and first_line.code == 100:
            self._write_body(False)
            return
        self.code = first_line.code
        self.reason = first_line.reason
        self.headers = headers

        # 设置204 content_length为0
        if self.code == 204:
            length = self.headers.get('content-length', 0)
            self.headers["Content-Length"] = "0"
            gen_log.info("turn headers content-length from {} to 0".format(length))

        if self._should_follow_redirect():
            return

        if self.request.header_callback is not None:
            # Reassemble the start line.
            self.request.header_callback('%s %s %s\r\n' % first_line)
            for k, v in self.headers.get_all():
                self.request.header_callback("%s: %s\r\n" % (k, v))
            self.request.header_callback('\r\n')
#HTTP#
一天一个知识点 文章被收录于专栏

1

全部评论
感谢分享,收藏了
点赞 回复 分享
发布于 2022-08-05 14:06

相关推荐

11-03 17:42
门头沟学院 Java
点赞 评论 收藏
分享
emmm别问我为啥上一条帖子隔了两个月我才开始投简历和拿offer,因为我懒😰简单流程如下:周一凌晨改好的简历,然后到处乱投简历;周二接到了三维家的一面通知,临时抱佛脚的背了一些八股;周三上午一面下午通知第二天hr面;周四上午hr面下午拿offer,遂收手支线:在BOSS上顺手投了几个大厂,投字节的时候不小心投城客户端了,结果过了一天HR突然把我简历要走了,还问我能不能整客户端,我直接一口答应(脏面评警告😢)结果在周三下午的时候给我打电话,说前端有空缺实习岗,问我有没有兴趣,然后就跟我约了周四下午一面😰我都没咋准备啊,咩都不会啊😭结果周四下午面完,晚上打电话通知过一面了,赶紧把二面约在下周一下午,留点缓冲时间。逆大天了,我一半的问题都不会,他居然给我过了?运气未免有点好了😥现在正在恶补计网、网安、性能优化的东西(这三大板块我是几乎一点不会,一面几乎一点答不出来,加上我又没怎么背八股,这块被干烂了😵)心得体会与经验:1.&nbsp;我giao怎么这么快就结束了,我还以为要找好久😨2.&nbsp;大厂的面试问题真的和中厂小厂很大不同,比如在三维家我能自己吹水到vue的数据劫持、Proxy代理响应式之类的他们就觉得很不错了,但是在字节你但凡敢提到一下就会追问你细节了,一追问马脚就全漏出来了3.&nbsp;有信心真的很重要,我感觉我能拿中厂offer最重要的就是吹水吹出自信来了,以至于三维家面试反问面试官有哪里还需要改进的时候,他就说很不错了解的很多😦4.&nbsp;理解很重要,我从头到尾真没背过很多八股,不过有一些知识确实是敲过代码验证过,所以面试的时候能吹水吹得出来😇想了解面经啥的可以直接评论区问我,但我可能也说不全,因为我没有记录,而且今天摆了一天感觉记忆快清空了😵下面是故事时间:我暑假刚开始的时候才开始准备八股,印象很深那个时候连什么原型、事件循环、闭包这些名词都没听过,资料也不知道怎么找,就一直零零散散的准备,感觉也只有js稍微背了一下八股,其他很多时候都是靠完全理解和手写熟悉一些机制的,但这样做效率很低,反正准备了一个多星期半个月就开摆了😭结果一摆就摆到了开学,笔记是乱七八糟的,八股是忘光光的,简历是一直没改的,实习也是一直没投过的。直到上周日晚上偶然和师兄聊天,他突然问我“你怎么还不找实习”,那天晚上才幡然醒悟,是时候做点事情了😡然后就按照上面描述的来走了。其实我感觉我从头到尾都没背特别多八股,也没怎么找刷题资料啥的,早期就是翻尚硅谷或者黑马的入门视频从头学起,中期用面试鸭看了一点点题,主要是在学js机制和敲js代码,后期才发现了w3c的面经网站,然后在那里看着学(那个时候已经懒得敲了,因为有些问题与代码感觉不像是给找实习的看的,忒细了点😂)接下来继续准备字节二面吧,虽然几乎没啥可能可以通过,但是万一有奇迹呢?😍😍😍也祝大家能够早日拿到心仪的offer
我的offer呢😡:我已经预见10天后你会发,节孝子启动了
投递三维家等公司10个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务