V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
akmonde
V2EX  ›  Python

Python 使用 http 库(如 requests 等),能否获取像 socket 的完整 html 响应包

  •  
  •   akmonde · 2019-10-09 09:48:20 +08:00 · 5255 次点击
    这是一个创建于 1654 天前的主题,其中的信息可能已经有所发展或是发生改变。

    RT,我这边原先使用的 socket 去获取的完整响应包,包括 header 的那种,这样方便复现数据包。 现在有其他需求需要直接使用 http 库,比如 requests 或者 urllib2 之类的,不知道能否获取像 socket 这样的效果。 这样方便和以前的数据处理机制做兼容,因为想要改的话,代码量是比较多的。 原先的 socket 发送完整 http 包,可获得类似下面的返回( header 和 body 是在一块的),我昨天试了好几个库好像都不能模拟获取 socket 这种返回方式:

    HTTP/1.1 200
    Server: ADSSERVER/45863
    Date: Fri, 13 Oct 2017 06:48:23 GMT
    Content-Type: text/html
    Transfer-Encoding: chunked
    Connection: close
    Location: https://sec.douban.com/b?r=https%3A%2F%2Fwww.douban.com%2Fsubject%2F27076001%2F
    Strict-Transport-Security: max-age=15552000;
    Set-Cookie: __ads_session=uY8l3pLW/AjCKJ8Y4wA=; domain=.douban.com; path=/
    X-Powered-By-ADS: uni-jnads-1-02
    
    <html>
    #这里是完整的 html resp 内容
    ...
    </html>
    

    求大腿指教,有没有办法,让我获取像 socket 的完整 html 响应包?

    22 条回复    2019-10-10 17:09:30 +08:00
    jxxz
        1
    jxxz  
       2019-10-09 10:06:18 +08:00
    要不自己拼接下
    用 rsp.status_code、rsp.headers 和 rsp.text 拼接下
    不过 headers 获取到的是 dict,需要再处理下
    arrow8899
        2
    arrow8899  
       2019-10-09 10:13:22 +08:00
    r = requests.get('https://www.example.com', stream=True)
    r.raw.read(10) # 可以读到原始的数据流,但是需要自己处理各种编码问题
    akmonde
        3
    akmonde  
    OP
       2019-10-09 10:21:58 +08:00
    @arrow8899 嗯嗯,昨儿试了下 r.raw.readlines,好像读不了 headers,只能读到 body。

    @jxxz 老哥,我也想过,不过这样太麻烦了..
    littlespider89
        4
    littlespider89  
       2019-10-09 10:22:54 +08:00
    那不叫完整的 html 响应包,那是 http
    jxxz
        5
    jxxz  
       2019-10-09 11:00:48 +08:00   ❤️ 1
    @akmonde 看了下源码,requests 用 urllib3 的 response 封装的响应,具体位置在 requests --> adapters.py --> HTTPAdapter --> build_response()这个方法里,你可以看下能不能改源码

    ```
    def build_response(self, req, resp):
    """Builds a :class:`Response <requests.Response>` object from a urllib3
    response. This should not be called from user code, and is only exposed
    for use when subclassing the
    :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`

    :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
    :param resp: The urllib3 response object.
    :rtype: requests.Response
    ```
    akmonde
        6
    akmonde  
    OP
       2019-10-09 11:42:14 +08:00
    @littlespider89 差不多差不多..就那意思..

    @jxxz 老哥,这项目需要迁移的,改原生库不大合适啊..
    wwqgtxx
        7
    wwqgtxx  
       2019-10-09 11:56:24 +08:00 via iPhone
    自己给系统库的 socket.socket 打一个 monkey patch ?拦截所有的 recv 函数,记录下来再转发给上层
    est
        8
    est  
       2019-10-09 14:08:54 +08:00
    @jxxz requests 调用的 urllib3.HTTPResponse 最为返回,实例化里读取头用的是 httplib.HTTPMessage 这个类。继承自 rfc822.Message,所以最简单的办法是:

    a=requests.get('http://jd.com')
    print a.raw._fp.msg
    est
        9
    est  
       2019-10-09 14:15:37 +08:00   ❤️ 1
    然后需要自己处理一下 HTTPResponse.begin 里的 version, status, reason 部分。


    if version == 'HTTP/1.0':
    self.version = 10
    elif version.startswith('HTTP/1.'):
    self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
    elif version == 'HTTP/0.9':
    self.version = 9
    akmonde
        10
    akmonde  
    OP
       2019-10-09 15:31:06 +08:00
    @est 感谢老哥,讲道理是比重组 headers 要强一些,不过好像差的不是很多...
    @jxxz 实在找不到我只有重组了..

    @wwqgtxx 老哥,我不用 socket 的原因是现在没有原始包,只有 url,所以需要用 http 库,没法去打什么 monkey patch 吧?
    zhuangzhuang1988
        11
    zhuangzhuang1988  
       2019-10-09 17:30:12 +08:00
    ```python
    import socket
    from contextlib import contextmanager
    import requests
    from socket import SocketIO as _SocketIO


    @contextmanager
    def hook_socket_context():
    buffer = []

    class SocketIO(_SocketIO):
    def readinto(self, b):
    print('hooking')
    res = super().readinto(b)
    if res > 0:
    buffer.append(bytes(b[:res]))
    return res
    socket.SocketIO = SocketIO
    yield buffer
    socket.SocketIO = _SocketIO


    with hook_socket_context() as buf:
    requests.get('https://v2ex.com/t/607316#reply10')
    print(b''.join(buf))

    print('with out hook')
    requests.get('https://v2ex.com/t/607316#reply10')
    print('done!')

    ```
    python3.7 测试无问题
    zhuangzhuang1988
        12
    zhuangzhuang1988  
       2019-10-09 17:36:48 +08:00   ❤️ 1
    https://bitbucket.org/snippets/supermouse/onRK7x
    高亮问题, 放在 bitbucket 上了
    cz5424
        13
    cz5424  
       2019-10-09 18:41:28 +08:00
    支持 8 楼老哥的
    ```python
    a=requests.get('http://jd.com')
    str(a.raw._fp.msg) + a.text
    ```
    wwqgtxx
        14
    wwqgtxx  
       2019-10-09 20:14:10 +08:00   ❤️ 1
    @akmonde 无论是 http.client 还是 requests 底层都是调用系统 socket 库的(除非你用 libcurl 这种 C 库的封装),所以你可以按照 @zhuangzhuang1988 给出的方法给 socket 库打 monkey patch,从而获得原始 socket 流的所有数据包
    akmonde
        15
    akmonde  
    OP
       2019-10-09 22:39:51 +08:00
    @wwqgtxx @cz5424 @zhuangzhuang1988 感谢,我明儿试试~
    ClericPy
        16
    ClericPy  
       2019-10-09 23:23:10 +08:00
    楼上好多 talk is cheap 的大佬....... 学到老活到老

    python 的最大乐趣就是管你是不是猴子, 只要不是 built-ins 我就给你屁股上镶补丁

    requests 库已经被魔改过好几种变种了
    wwqgtxx
        17
    wwqgtxx  
       2019-10-09 23:25:37 +08:00 via iPhone
    @ClericPy builtins 也能打补丁呀,gevent 很久以前就干过
    ClericPy
        18
    ClericPy  
       2019-10-09 23:51:25 +08:00
    @wwqgtxx 它是它 我是我...
    我只用过替换的方式 import builtins 然后改, 直接在已有对象上打当场报错...
    walleL
        19
    walleL  
       2019-10-10 01:20:40 +08:00
    我比较好奇为什么需要改用 http client 库,然后还要获取原始的 tcp 数据

    楼主方便详细说下需求吗?可能从原始需求出发更好解决呢
    akmonde
        20
    akmonde  
    OP
       2019-10-10 11:58:00 +08:00
    @walleL 是这样的,主要我这边是给 QA 做数据复现,然后 QA 那边做的分析规则,原本是分析流量得到的完整的 http 响应包的。
    我这边的话,是需要原始 url 等分散的数据,重新去获取,所以拿到的不是完整的 http 响应包,而是分组的数据,做规则分析的时候,可能会形成差异,所以我这边需要做兼容。
    walleL
        21
    walleL  
       2019-10-10 15:05:56 +08:00
    @akmonde

    > 原本是分析流量得到的完整的 http 响应包的

    你分析的流量来自哪里呢?是如何抓取的呢?
    我的理解是之前你直接分析流量得到 http 包,现在需要自已发起 http 请求来生成流量。
    这样的话是不是可以分析流量的程序不变,抓取方案也不变,只是用另一个程序来发起 http 请求产生流量就行了?
    akmonde
        22
    akmonde  
    OP
       2019-10-10 17:09:30 +08:00
    @walleL 分析的流量原来是用 burpsuite 抓的包,做代理获取的完整响应包,是需要用另一个程序或者脚本发起请求,但发起二次请求已经不是抓取了,是直接获取 http 返回包,所以获取的内容形式跟原来用代理工具抓到的包是不太一样的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   913 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 21:01 · PVG 05:01 · LAX 14:01 · JFK 17:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.