美文网首页
异步HTTP请求

异步HTTP请求

作者: 大海无垠_af22 | 来源:发表于2019-03-02 19:55 被阅读0次

    基于《简易协程-2》提供的协程框架,实现一个异步的HTTP请求的函数。HTTP协议广泛使用,提供这样一个函数实现可以方便的各处使用。
    整个函数基本分为两个部分,发送请求和解析响应。
    HTTP请求的格式如下所示。

    {method} {url} {HTTP version}\r\n
    {head1}:{value1}\r\n
    ...
    \r\n
    {data}
    

    发送请求的第一步就是将输入的参数拼接成如上格式的字节流。
    接着便是监听socket的可写事件,一旦可写了则不断写入请求数据。由于请求可能很大,一次只能传输一段数据,所以这个过程会反复几次,直至所有数据发送完成。
    然后便是接受服务端的响应。
    HTTP的响应格式如下所示。

    {HTTP version} {code} {reason}\r\n
    {head1}:{value1}\r\n
    ...
    Content-Length: xxx\r\n
    ...
    \r\n
    {data}
    

    响应的格式和请求的格式很类似,仅仅是首行不太一样而已。接收响应有一点需要注意的就是响应数据长度。正常的话,这个是由头部Content-Length表示,有的服务端不给出这个字段,则默认data长度是0。另外一个特殊情况就是HEAD请求,这个情况下,data必定是空,可以忽略Content-Length值。
    接收响应的方法主要是监听socket的可读事件,一旦可读则接收数据,最多接收32KB。这个数字是经过实际测试得到的一个比较好的结果,即使在10Gb的网卡上也能取得很好的性能,太小太大都降低性能。
    在解析头部之前,每收取到数据都要尝试解析头部,标志是\r\n\r\n,之前是头部,之后就是响应的data部分。头部解析完成,才能知道data的具体长度,然后一心一意的接收data数据。
    流程大致如上。具体代码如下所示。

    def async_urlopen(sock, url, method="GET", headers=(), data=""):
        """
        async HTTP request
    
        :param sock:
        :param url: 
        :param method:
        :param headers: (head, value) headers list
        :param data:
        :return response: (code, reason, headers, body)
        """
        # 拼接请求数据
        pieces = [method, ' ', url, ' HTTP/1.1\r\n', ]
        for head, val in headers:
            pieces.extend((head, ':', val, '\r\n'))
        pieces.extend(('Content-Length:', str(len(data)), '\r\n'))
        pieces.append('Connection: keep-alive\r\n\r\n')
        pieces.append(data)
        req_bin = ''.join(pieces)
        # 发送请求
        while req_bin:
            # 发出监听可写事件的请求
            yield SocketIO(sock.fileno(), read=False)
            # 协程框架检查到可写事件,当前sock可发送数据了
            sent = sock.send(req_bin)
            req_bin = req_bin[sent:]
        resp_bin = ""
        resp_len = -1
        # 接收响应
        while resp_len != len(resp_bin):
            yield SocketIO(sock.fileno(), read=True)
            data = sock.recv(32 << 10)
            # 头部已经解析,当前数据追加到 resp_bin尾部
            if resp_len > 0:
                resp_bin += data
            else:
                resp_bin += data
                # 尝试解析头部
                parts = resp_bin.split('\r\n\r\n', 1)
                # '\r\n\r\n'分隔符未找到,头部还没有全部接收到
                if len(parts) != 2:
                    continue
                head_bin, resp_bin = parts
                lines = head_bin.split('\r\n')
                status_line = lines[0]
                version, code, reason = status_line.split(' ', 2)
                code = int(code)
                headers = [line.split(':', 1) for line in lines[1:-1]]
                # HEAD 请求不需要计算data长度
                if method == 'HEAD':
                    break
                # 查找content-length头部,计算data长度
                resp_len = 0
                for head, val in headers:
                    if head.lower() == 'content-length':
                        resp_len = int(val)
                        break
        yield (code, reason, headers, resp_bin)
    

    相关文章

      网友评论

          本文标题:异步HTTP请求

          本文链接:https://www.haomeiwen.com/subject/yruyuqtx.html