目录
  • 安装
  • 创建请求
  • 自定义头部
  • 超时时间
  • ssl证书
  • 认证
  • 请求类型
    • query params
    • form表单
    • 文件上传
    • json
    • 二进制数据
  • 响应
    • 响应类型
    • cookie
    • 重定向历史
  • httpx.client
    • 合并/共享配置
    • base_url
    • limits
    • 调用python web app
    • 事件钩子
  • 代理
    • 异步支持

      httpx是python3的功能齐全的http客户端,它提供同步和异步api,并支持http/1.1和http/2

      安装

      pip install httpx

      创建请求

      通过httpx库发出一个请求非常简单,如下:

      import httpx
      
      response = httpx.get('https://www.baidu.com/')
      print(type(response), response)     # <class 'httpx.response'> <response [200 ok]>

      同样,我们再来发出一个post请求:

      response = httpx.post('http://localhost:5000/login', data={'username': 'httpx', 'password': '123456'})
      

      put, delete, head和options请求都遵循相同的样式:

      response = httpx.put('http://www.baidu.com/', data={key: value})
      response = httpx.head('http://www.baidu.com/')
      response = httpx.delete('http://www.baidu.com/')
      response = httpx.options('http://www.baidu.com/')
      

      自定义头部

      要在传入请求中包含其他标头,请使用headers关键字参数:

      header = {"user-agent": 'my_test/0001'}
      response = httpx.get("https://api.github.com/events", headers=header)
      

      超时时间

      httpx设置默认的超时时间为5秒,超过此时间未响应将引发错误。我们可以通过timeout关键字参数来手动修改它:

      response = httpx.get('http://localhost:5000/update', timeout=10)
      

      你也可以将其设置为none完全禁用超时行为

      response = httpx.get('http://localhost:5000/update', timeout=none)
      

      超时又可以分为connect, read,write和pool超时。如果想详细设置,我们可以通过httpx.timeout类来实现:

      # 读取超时为10s,其他超时为5秒
      timeout = httpx.timeout(5, read=10)
      response = httpx.get('http://localhost:5000/update', timeout=timeout)
      

      ssl证书

      通过httpx发出https请求时,需要验证所请求主机的身份。我们可以通过verify来指定我们存在的ca证书:

      response = httpx.get('https://example.org', verify='../../client.pem')
      

      或者你可以传递标准库ssl.sslcontext

      import ssl
      import httpx
      
      context = ssl.create_default_context()
      context.load_verify_locations(cafile='../../client.pem')
      response = httpx.get('https://example.org', verify='../../client.pem')

      又或者,你可以将verify设置为false禁用ssl验证:

      response = httpx.get('https://example.org', verify=false)
      

      认证

      httpx支持basic auth和digest auth身份验证。要提供身份验证凭据,请将2个元组得纯文本str或bytes对象作为auth参数传递给请求函数:

      response = httpx.get('https://example.com', auth=('my_user', 'password123'))
      

      要提供digest auth身份验证得凭据,你需要digest auth使用纯文本用户名和密码作为参数实例化一个对象。然后可以将该对象作为auth参数传递给上述请求方法:

      from httpx import digestauth
      
      auth = digestauth('my_user', 'password123')
      response = httpx.get('https://example.com', auth=auth)

      httpx还提供了一个functionauth类,允许我们传入一个callable对象,该callable接收request参数,并返回request。如下:

      import httpx
      from httpx._auth import functionauth
      
      def init_authorization(request):
          request.headers['authorization'] = 'bearer 12334'
          yield request
      
      auth = functionauth(init_authorization)
      response = httpx.get('http://localhost:5000/home', auth=auth)

      请求类型

      query params

      params = {"name":"zhangsan", "age":18}
      response = httpx.get("https://www.baidu.com/s", params=params)
      

      此时我们打印一下url,发现该url已经被正确编码:

      print(response.url)    # https://www.baidu.com/s?name=zhangsan&age=18
      

      也可以传递一个列表数据进去:

      params = {"name":"zhangsan", "favorite": ["football", "basketball"]}
      response = httpx.get("https://www.baidu.com/s", params=params)
      

      form表单

      通常情况下,你想要发送一些表单编码数据,就像html表单一样。要做到这一点,你只需要将字典传递给data关键字参数即可:

      data = {'name': '张三'}
      response = httpx.post('http://127.0.0.1:5000/test/post', data=data)
      

      文件上传

      你还可以使用http分段编码上传文件

      f = open('a.txt', 'rb')
      files = {'file': f}
      response = httpx.post('http://localhost:5000/post', files=files)
      f.close()
      

      json

      如果你想要发送一个json数据,你可以通过将数据传递给json关键字参数即可:

      response = httpx.post('http://127.0.0.1:5000/test/post', json={'name': '张三'})
      

      二进制数据

      对于其他编码,应使用content关键字参数,传递一个bytes类型数据

      content = b'hello world'
      response = httpx.post('http://127.0.0.1:5000/test/post', content=content)
      

      响应

      响应类型

      在上面的栗子可以知道,我们每次请求之后都会返回一个httpx.response对象,我们可以从此对象中获取响应内容:

      response = httpx.get("https://api.github.com/events")
      print(type(response.text), response.text)      # <class 'str'> [{"id":"14551634865","type":"pushevent", ...}]
      

      二进制响应

      print(type(response.content), response.content) # <class 'bytes'> b'[{"id":"14551634865","type":"pushevent", ...}]
      

      json响应

      print(type(response.json()), response.json())   # <class 'list'> [{'id': '14551634865', 'type': 'pushevent', ...}]
      

      流式响应

      对于大型下载,你可能需要使用不将整个响应主体立即加载到内存中的流式响应。你可以流式传输响应的二进制内容:

      for data in response.iter_bytes():
          print(data)
      

      流式传输响应的文本内容:

      for text in response.iter_text():
          print(text)
      

      逐行流文本:

      for line in response.iter_lines():
          print(line)
      

      原始字节:

      for chunk in response.iter_raw():
          print(chunk)
      

      cookie

      如果响应包含了cookie,你可以这样快速的访问它:

      response = httpx.get('http://localhost:5050/get')
      print(response.cookies['user'])
      

      重定向历史

      history响应的属性可用于检查任何后续的重定向。它包含遵循它们的顺序的所有重定向响应列表。例如github将所有http请求重定向到https:

      response = httpx.get('http://github.com/')
      print(response, response.url)       # <response [200 ok]> https://github.com/
      print(response.history, response.history[0].url)    # [<response [301 moved permanently]>] http://github.com/
      

      你还可以使用allow_redirects关键字参数来修改默认得重定向处理:

      response = httpx.get('http://github.com/', allow_redirects=false)
      print(response)         # <response [301 moved permanently]>
      print(response.history) # []
      

      httpx.client

      如果你会使用requests,那么可以使用httpx.client代替requests.session

      with httpx.client() as client:
          response = client.get('http://localhost:5000/details')
      

      另外,还可以使用.close()方法明确关闭连接池,而不会阻塞:

      client = httpx.client()
      try:
          response = client.get('http://localhost:5000/details')
      finally:
          client.close()
      

      一旦你拥有了一个httpx.client实例,那么你就可以通过调用.get()、.post()等方法发送请求。这些方法同样支持timeout、auth、headers等参数来满足我们的需求

      合并/共享配置

      httpx.client还接收headers、cookie和params参数,对于同一组的请求操作,将共享同样的headers、cookie和params参数。如果请求方法中也包含了这些参数,那么它们将进行合并:

      with httpx.client(headers={'token': '12345678'}, params={'page_size': 1, 'size': 20}) as client:
          resp1 = client.get('http://localhost:5000/get', params={'search': 'laozhang'})
          resp2 = client.post('http://localhost:5000/post')
      

      如此,这两个请求的头部都将包含{‘token’: ‘12345678’}。请求1的params将会合并,请求2将会使用{‘page_size’: 1, ‘size’: 20}查询参数

      对于其他参数,如auth等,那么将会优先使用请求方法里面的auth

      base_url

      httpx.client还允许使用base_url参数来设置基础url,如下:

      with httpx.client(base_url='http://localhost:5000') as client:
          response = client.get('/user/detail')
          print(response.url)     # http://localhost:5050/user/detail
      

      limits

      可以使用limits关键字参数来控制连接池的大小。它需要传递一个httpx.limits类实例,httpx.limits类接收以下两个参数:

      • max_keepalive: 最大活跃连接数,设置为none表示无限制。默认为10
      • max_connections:最大连接数,设置为none表示苏限制。默认为100
      limits = httpx.limits(max_keepalive=2, max_connections=5)
      client = httpx.client(limits=limits)
      

      调用python web app

      你可以配置httpx客户端以使用wsgi协议直接调用python web应用程序。这对于两个主要用例特别有用:

      • 使用httpx的测试案例中的客户端
      • 在测试期间或在dev/staging环境中模拟外部服务
      import httpx
      from flask import flask
      
      app = flask(__name__)
      
      @app.route("/home")
      def home():
          return 'home api success'
      
      with httpx.client(app=app, base_url='http://testapi') as client:
          response = client.get('/home')
          print(response)                     # <response [200 ok]>
          print(response.text, response.url)  # home api success http://testapi/home

      我们还可以通过使用wsgitransport来使用给定的客户端地址用于请求,如下:

      transport = httpx.wsgitransport(app=app, remote_addr='1.2.3.4')
      with httpx.client(transport=transport, base_url='http://testapi') as client:
          response = client.get('/home')
          print(response)                     # <response [200 ok]>
          print(response.text, response.url)  # home api success http://testapi/home
      

      如此,视图函数home中request.remote_addr将会是1.2.3.4

      事件钩子

      httpx允许你向客户端注册事件钩子,每次发生特定类型的事件时都会调用该钩子。httpx支持两个事件钩子:

      • request: 在即将发生请求时调用。为一个callable列表,callable接收httpx.request实例参数
      • response: 响应返回后调用。为一个callable列表,callable接收httpx.response实例参数
      def _log_request(request):
          print(type(request), request.url, request.method)       # <class 'httpx.request'> http://localhost:5000/hello get
          
      def _log_response(response):
          print(type(response), response.url, response.text)      # <class 'httpx.response'> http://localhost:5000/hello home api success
          
      with httpx.client(base_url='http://localhost:5000') as client:
          client.get('/home')
      

      代理

      要将所有请求使用http://localhost:8030的代理,请将代理url传递给client:

      with httpx.client(proxies='http://localhost:8030') as client:
          pass
      

      对于更高级的使用,请使用dict。例如,要将http和https请求路由到两个不同的代理: http://localhost:8030和http:localhost:8031:

      proxies = {
          'http://': 'http://localhost:8030',
          'https://': 'http:localhost:8031'
      }
      with httpx.client(proxies=proxies) as client:
          pass
      

      代理所有请求:

      proxies = {
          'all://': 'http://localhost:8030'
      }
      

      代理域名为“example.com”的所有请求:

      proxies = {
          'all://example.com': 'http://localhost:8030'
      }
      

      代理域名为“example.com”的所有http请求:

      proxies = {
          'http://example.com': 'http://localhost:8030'
      }
      

      代理所有包含“example.com”的所有请求:

      proxies = {
          'all://*example.com': 'http://localhost:8030'
      }
      

      对于上面匹配,如果未匹配到将不使用代理。域名后面还可以添加端口号,用于更加严格的匹配。此外,我们还可以将匹配设置为none,用于排除,如下:

      proxies = {
          'all://': 'http://localhost:8030',
          'all://example.com': none
      }
      

      即除使用“example.com”域名的路由,将使用“http://localhost:8030”代理

      异步支持

      httpx默认情况下提供标准的同步api,但是如果需要,还可以为你提供异步客户端的选项。要发出异步请求,你需要一个httpx.asyncclient

      import asyncio
      import httpx
      
      async def main():
          async with httpx.asyncclient() as client:
              response = await client.get('https://example.org/')
      
      loop = asyncio.get_event_loop()
      try:
          loop.run_until_complete(main())
      finally:
          loop.close()

      发出请求

      请求方法都是异步的,因此你应该使用response = await client.get(…)样式对以下所有内容使用:

      • asyncclient.get(url, …)
      • asyncclient.options(url, …)
      • asyncclient.head(url, …)
      • asyncclient.post(url, …)
      • asyncclient.put(url, …)
      • asyncclient.patch(url, …)
      • asyncclient.delete(url, …)
      • asyncclient.request(url, …)
      • asyncclient.send(url, …)

      流式响应

      • response.aread()
      • response.aiter_bytes()
      • response.aiter_text()
      • response.aiter_lines()
      • response.aiter_raw()