Python实现http客户端代码示例

Python 实现一个简易版 HTTP 客户端 此文为《用 Python 撸一个 Web 服务器》系列教程的一个补充,这个系列教程介绍了如何使用 Python 内置的 socket 库实现一个简易版的 Web 服务器。 之所以写这篇文章,是因为我发现很多人并不清楚 HTTP 客户端的概念,以为只有浏览器才叫 HTTP 客户端。事实上并非如此,我们在 Web 开发中常见的 Postman、爬虫程序、curl 命令行工具 等,这些都可以称为 HTTP 客户端。 服务器程序示例 这里以一个 Hello World 程序来作为示例服务器,实现如下: # serv 阅读详情


本文用python在TCP的基础上实现一个HTTP客户端, 该客户端能够复用TCP连接, 使用HTTP1.1协议.

一. 创建HTTP请求


HTTP是基于TCP连接的, 它的请求报文格式如下:

因此, 我们只需要创建一个到服务器的TCP连接, 然后按照上面的格式写好报文并发给服务器, 就实现了一个HTTP请求.

1. HTTPConnection类

基于以上的分析, 我们首先定义一个HTTPConnection类来管理连接和请求内容:

class HTTPConnection:
  default\_port = 80
  \_http\_vsn = 11
  \_http\_vsn\_str = 'HTTP/1.1'

  def \_\_init\_\_(self, host: str, port: int = None) -> None:
    self.sock = None
    self.\_buffer = \[\]
    self.host = host
    self.port = port if port is not None else self.default\_port
    self.\_state = \_CS\_IDLE
    self.\_response = None
    self.\_method = None
    self.block\_size = 8192

  def \_output(self, s: Union\[str, bytes\]) -> None:
    if hasattr(s, 'encode'):
      s = s.encode('latin-1')
    self.\_buffer.append(s)

  def connect(self) -> None:
    self.sock = socket.create\_connection((self.host, self.port))

对于这个HTTPConnection对象, 我们只需要创建TCP连接, 然后按照HTTP协议的格式把请求数据写入buffer中, 最后把buffer中的数据发送出去就行了.

2. 编写请求行

请求行的内容比较简单, 就是说明请求方法, 请求路径和HTTP协议. 使用下面的方法来编写一个请求行:

def put\_request(self, method: str, url: str) -> None:
  self.\_method = method

  url = url or '/'

  request = f'{method} {url} {self.\_http\_vsn\_str}'
  self.\_output(request)

3. 添加请求头

HTTP请求头和python的字典类似, 每行都是一个字段名与值的映射关系. HTTP协议并不要求设置所有合法的请求头的值, 我们只需要按照需要, 设置特定的请求头即可. 使用如下代码添加请求头:

def put\_header(self, header: Union\[bytes, str\], value: Union\[bytes, str, int\]) -> None:
  if hasattr(header, 'encode'):
    header = header.encode('ascii')

  if hasattr(value, 'encode'):
    value = value.encode('latin-1')
  elif isinstance(value, int):
    value = str(value).encode('ascii')

  header = header + b': ' + value
  self.\_output(header)

此外, 在HTTP请求中, Host请求头字段是必须的, 否则网站可能会拒绝响应. 因此, 如果用户没有设置这个字段, 这里就应该主动把它加上去:

def \_add\_host(self, url: str) -> None:
  # 所有HTTP / 1.1请求报文中必须包含一个Host头字段
  # 如果用户没给,就调用这个函数来生成
  netloc = ''
  if url.startswith('http'):
    nil, netloc, nil, nil, nil = urllib.parse.urlsplit(url)

  if netloc:
    try:
      netloc\_enc = netloc.encode('ascii')
    except UnicodeEncodeError:
      netloc\_enc = netloc.encode('idna')
    self.put\_header('Host', netloc\_enc)
  else:
    host = self.host
    port = self.port

    try:
      host\_enc = host.encode('ascii')
    except UnicodeEncodeError:
      host\_enc = host.encode('idna')

    # 对IPv6的地址进行额外处理
    if host.find(':') >= 0:
      host\_enc = b'\[' + host\_enc + b'\]'

    if port == self.default\_port:
      self.put\_header('Host', host\_enc)
    else:
      host\_enc = host\_enc.decode('ascii')
      self.put\_header('Host', f'{host\_enc}:{port}')

4. 发送请求正文

我们接受两种形式的body数据: 一个基于io.IOBase的可读文件对象, 或者是一个能通过迭代得到数据的对象. 在传输数据之前, 我们首先要确定数据是否采用分块传输:

def request(self, method: str, url: str, headers: dict = None, body: Union\[io.IOBase, Iterable\] = None,
      encode\_chunked: bool = False) -> None:
  ...
  if 'content-length' not in header\_names:
    if 'transfer-encoding' not in header\_names:
      encode\_chunked = False
      content\_length = self.\_get\_content\_length(body, method)
      if content\_length is None:
        if body is not None:
          # 在这种情况下, body一般是个生成器或者可读文件之类的东西,应该分块传输
          encode\_chunked = True
          self.put\_header('Transfer-Encoding', 'chunked')
      else:
        self.put\_header('Content-Length', str(content\_length))
    else:
      # 如果设置了transfer-encoding,则根据用户给的encode\_chunked参数决定是否分块
      pass
  else:
    # 只要给了content-length,那么一定不是分块传输
    encode\_chunked = False
  ...


@staticmethod
def \_get\_content\_length(body: Union\[str, bytes, bytearray, Iterable, io.IOBase\], method: str) -> Optional\[int\]:
  if body is None:
    # PUT,POST,PATCH三个方法默认是有body的
    if method.upper() in \_METHODS\_EXPECTING\_BODY:
      return 0
    else:
      return None

  if hasattr(body, 'read'):
    return None

  try:
    # 对于bytes或者bytearray格式的数据,通过memoryview获取它的长度
    return memoryview(body).nbytes
  except TypeError:
    pass

  if isinstance(body, str):
    return len(body)

  return None

在确定了是否分块之后, 就可以把正文发出去了. 如果body是一个可读文件的话, 就调用_read_readable方法把它封装为一个生成器:

def \_send\_body(self, message\_body: Union\[str, bytes, bytearray, Iterable, io.IOBase\], encode\_chunked: bool) -> None:
  if hasattr(message\_body, 'read'):
    chunks = self.\_read\_readable(message\_body)
  else:
    try:
      memoryview(message\_body)
    except TypeError:
      try:
        chunks = iter(message\_body)
      except TypeError:
        raise TypeError(
          f'message\_body should be a bytes-like object or an iterable, got {repr(type(message\_body))}')
    else:
      # 如果是字节类型的,通过一次迭代把它发出去
      chunks = (message\_body,)

  for chunk in chunks:
    if not chunk:
      continue

    if encode\_chunked:
      chunk = f'{len(chunk):X}\\r\\n'.encode('ascii') + chunk + b'\\r\\n'
    self.send(chunk)

  if encode\_chunked:
    self.send(b'0\\r\\n\\r\\n')


def \_read\_readable(self, readable: io.IOBase) -> Generator\[bytes, None, None\]:
  need\_encode = False
  if isinstance(readable, io.TextIOBase):
    need\_encode = True
  while True:
    data\_block = readable.read(self.block\_size)
    if not data\_block:
      break
    if need\_encode:
      data\_block = data\_block.encode('utf-8')
    yield data\_block

二. 获取响应数据


HTTP响应报文的格式与请求报文大同小异, 它大致是这样的:

因此, 我们只要用HTTPConnection的socket对象读取服务器发送的数据, 然后按照上面的格式对数据进行解析就行了.

1. HTTPResponse类

我们首先定义一个简单的HTTPResponse类. 它的属性大致上就是socket的文件对象以及一些请求的信息等等, 调用它的begin方法来解析响应行和响应头的数据, 然后调用read方法读取响应正文:

class HTTPResponse:

  def \_\_init\_\_(self, sock: socket.socket, method: str = None) -> None:
    self.fp = sock.makefile('rb')
    self.\_method = method
    self.headers = None
    self.version = \_UNKNOWN
    self.status = \_UNKNOWN
    self.reason = \_UNKNOWN
    self.chunked = \_UNKNOWN
    self.chunk\_left = \_UNKNOWN
    self.length = \_UNKNOWN
    self.will\_close = \_UNKNOWN

  def begin(self) -> None:
    ...

  def read(self, amount: int = None) -> bytes:
    ...

2. 解析状态行

状态行的解析比较简单, 我们只需要读取响应的第一行数据, 然后把它解析为HTTP协议版本,状态码和原因短语三部分就行了:

def \_read\_status(self) -> Tuple\[str, int, str\]:
  line = str(self.\_read\_line(), 'latin-1')
  if not line:
    raise RemoteDisconnected('Remote end closed connection without response')
  try:
    version, status, reason = line.split(None, 2)
  except ValueError:
    # reason只是给人看的, 一般和status对应, 所以它有可能不存在
    try:
      version, status = line.split(None, 1)
      reason = ''
    except ValueError:
      version, status, reason = '', '', ''
  if not version.startswith('HTTP/'):
    self.\_close\_conn()
    raise BadStatusLine(line)

  try:
    status = int(status)
    if status < 100 or status > 999:
      raise BadStatusLine(line)
  except ValueError:
    raise BadStatusLine(line)
  return version, status, reason.strip()

如果状态码为100, 则客户端需要解析多个响应状态行. 它的原理是这样的: 在请求数据过大的时候, 有的客户端会先不发送请求数据, 而是先在header中添加一个Expect: 100-continue, 如果服务器愿意接收数据, 会返回100的状态码, 这时候客户端再把数据发过去. 因此, 如果读取到100的状态码, 那么后面往往还会收到一个正式的响应数据, 应该继续读取响应头. 这部分的代码如下:

def begin(self) -> None:
  while True:
    version, status, reason = self.\_read\_status()
    if status != HTTPStatus.CONTINUE:
      break
    # 跳过100状态码部分的响应头
    while True:
      skip = self.\_read\_line().strip()
      if not skip:
        breakself.status = status
  self.reason = reason
  if version in ('HTTP/1.0', 'HTTP/0.9'):
    self.version = 10
  elif version.startswith('HTTP/1.'):
    self.version = 11
  else:
    # HTTP2还没研究, 这里就不写了
    raise UnknownProtocol(version)

  ...

3. 解析响应头

解析响应头比响应行还要简单. 因为每个header字段占一行, 我们只需要一直调用read_line方法读取字段, 直到读完header为止就行了.

def \_parse\_header(self) -> None:
  headers = {}
  while True:
    line = self.\_read\_line()
    if len(headers) > \_MAX\_HEADERS:
      raise HTTPException('got more than %d headers' % \_MAX\_HEADERS)
    if line in \_EMPTY\_LINE:
      break
    line = line.decode('latin-1')
    i = line.find(':')
    if i == -1:
      raise BadHeaderLine(line)
    # 这里默认没有重名的情况
    key, value = line\[:i\].lower(), line\[i + 1:\].strip()
    headers\[key\] = value
  self.headers = headers

4. 接收响应正文

在接收响应正文之前, 首先要确定它的传输方式和长度:

Python实现HTTP客户端和服务器 Python中,我们可以使用内置的模块来创建HTTP客户端和服务器。在本文中,我将向您展示如何使用Python实现HTTP客户端和服务器,并提供相应的源代码。这就是使用Python实现HTTP客户端和服务器的简单示例。您可以根据需要对代码进行修改和扩展,以满足实际的需求。类创建了一个HTTP服务器,并指定服务器的地址和请求处理类。上述代码演示了一个简单的HTTP服务器。最后,我们打印出状态码和响应内容,并关闭连接。方法中,我们设置了响应状态码、响应头部和响应内容。上述代码演示了一个简单的HTTP客户端 阅读详情

相关推荐

使用Python中的异步HTTP客户端HTTPX

HTTPX是一个强大而易于使用的异步HTTP客户端库,它提供了简单的API来发送异步HTTP请求。本文介绍了HTTPX的安装过程以及如何发送GET和POST请求,并处理可能出现的异常。使用HTTPX,您可以轻松地与服务器进行异步通信,从而提高应用程序的性能和响应能力。

Meta_C的博客 374

Python 基础】网络编程 - Python写一个简单的HTTP服务端和客户端实现Client/Server交互

1.HTTP 首先讲一下httphttps 2.python http 3.源码实例 4.完整下载

AiFool的博客 8795

使用Pythonhttplib模块创建HTTP客户端

在本文中,我们将详细介绍如何使用httplib模块创建一个简单的HTTP客户端,并提供相应的源代码。发送HTTP请求后,我们可以使用conn.getresponse()方法获取服务器的HTTP响应。该方法接受三个参数:请求方法(如GET、POST等),请求路径和可选的请求正文。我们可以使用response.status属性获取HTTP响应的状态码,使用response.read()方法获取响应的内容。如果需要发送POST请求,我们可以在第一个参数中指定"POST",并在第三个参数中提供请求正文的数据。

pytorchCode的博客 261

python网络编程——HTTP客户端

1.

qq_40909772的博客 3467

python客户端软件开发_用 Python 实现一个简易版 HTTP 客户端

此文为《用 Python 撸一个 Web 服务器》系列教程的一个补充,这个系列教程介绍了如何使用 Python 内置的 socket 库实现一个简易版的 Web 服务器。之所以写这篇文章,是因为我发现很多人并不清楚 HTTP 客户端的概念,以为只有浏览器才叫 HTTP 客户端。事实上并非如此,我们在 Web 开发中常见的 Postman、爬虫程序、curl 命令行工具 等,这些都可以称为 HTTP...

weixin_29273849的博客 1235

python 客户端 版本更新_用 Python 实现一个简易版 HTTP 客户端

此文为《用 Python 撸一个 Web 服务器》系列教程的一个补充,这个系列教程介绍了如何使用 Python 内置的 socket 库实现一个简易版的 Web 服务器。之所以写这篇文章,是因为我发现很多人并不清楚 HTTP 客户端的概念,以为只有浏览器才叫 HTTP 客户端。事实上并非如此,我们在 Web 开发中常见的 Postman、爬虫程序、curl 命令行工具 等,这些都可以称为 HTTP...

weixin_42519497的博客 186

python 实现TCP socket通信和 HTTP服务器、服务器和客户端通信python实例

python 实现TCP socket通信和 HTTP服务器、服务器和客户端通信实例 socket是什么? 由下图可理解:Socket是应用层与TCP/IP协议族通信的中间软件抽象层。 复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 服务器和客户端通信的流程 由下图可理解服务器和客户端通信的流程: 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端

及时总结 8747

python实现高并发tcp请求客户端_高并发HTTP请求客户端 - python语言编写

20200902 -(这篇文章其实应该放在高并发、服务器性能一类的专栏下,但是暂时没有,不过后续还会研究利用C编程来进行这部分的内容,就放在这个下面)0. 引言之前的时候接触过高并发相关的内容,当时接触了类似epool这种编程模型,或者更高级的libev这种时间编程;但是这些都是面向服务端的内容,类似C10K这种经典问题。而对于客户端来说,虽然有很多不同语言或者不同类型的压测工具,但很少有介绍自己...

weixin_39947396的博客 1038

Python实现一个简单的HTTP代理

一个简单的HTTP代理

公羽向阳的博客 1万+

mqtt服务端python代码_python mqtt 客户端实现代码实例

paho在Django实现了MQTT的客户端,如何把获取的消在Django里面用paho模块,成功的实现了MQTT的客户端功能,可以实现和MQT一、MQTT(消息队列)简介 MQTT(MQ Telemetry Transport)是IBM开发的一种网络应用层的协议,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器。刚学习Pyth...

weixin_42446705的博客 741

Gitee码云Webhook实现Python代码示例

Webhook是Gitee为用户提供的一项实用功能,它允许在Gitee仓库有新的提交时,自动发送HTTP POST请求到指定的URL。这样,无论是自动部署代码,还是通知第三方应用更新,都可以实时、自动地完成,大大提升工作效率。

weixin_42466857的博客 1110

http.client --- HTTP协议客户端

注解HTTPS 支持仅在 Python 启动了 SSL 支持进行编译时(通过ssl模块)可用。 该模块支持以下类: classhttp.client.HTTPConnection(host,port = None,[timeout,]source_address = None,blocksize = 8192) HTTPConnection的实例代表与 HTTP 的一个连接事务...

生命在于休息的博客 2693

Python Http客户端和服务器(串行)

Python Http客户端和服务器(串行)

shr986796621的专栏 712

Python企业级项目中的HTTP客户端使用优化

在现代企业级Python项目中,与外部服务和API的交互已成为常态。这些交互通常通过HTTP协议实现,因此高效、可靠且易于维护的HTTP客户端使用变得至关重要。通过采用统一的API客户端层、选择合适的HTTP库、实施最佳实践,我们可以显著改善Python企业级项目中的HTTP客户端使用。这个示例展示了如何创建一个基础的异步API客户端类,并在此基础上实现特定的API客户端。采用依赖注入模式,便于管理不同的API客户端实例,也有利于单元测试。实现统一的错误处理机制和日志记录策略,提高系统的可维护性。

探索云原生与智能化驱动下的安全运维新范式。关注DevSecOps、可观测性、AIOps等前沿领域,与您共赴技术前沿。 462

hessian的python客户端

今天简单看了一下hessian的客户端,全部代码就480行,非常简单啊,完全可以模仿此代码写各种http客户端。 最核心的代码: request = HessianWriter().write_call(method, params) import httplib h = httplib.HTTP(self._host) h.putrequest(...

SAN_YUN的专栏 294

pythontcp多客户端_Python TCPServer 多线程多客户端通信的实现

最简单、原始的TCP通信demo服务端Http请求:import socket# 创建一个servicesockeserviceSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 给服务器绑定地址(ip地址,端口号)serviceSocket.bind(("192.168.171.1", 80))print("等待客户端接入")#...

weixin_39629467的博客 185

Python模块学习 ---- httplib HTTP协议客户端实现

  httplib 是 pythonhttp 协议的客户端实现,可以使用该模块来与 HTTP 服务器进行交互。httplib的内容不是很多,也比较简单。以下是一个非常简单的例子,使用httplib获取google首页的html:#coding=gbkimport httplibconn = httplib.HTTPConnection("www.google.cn")conn

5万+

python模块介绍-httplib:HTTP协议客户端HTTPS协议客户端

python模块介绍-httplib:HTTP协议客户端  注意:httplib模块在Python3.0中已更名为http.client。2to3脚本会自动修改import当你的源代码转换时到3.0时。 httplib实现HTTPHTTPS的客户端协议,一般不直接使用,在python更高层的封装模块中(urllib,urllib2)使用了它的HTTPHTTPS实现。其中HTTPS

chengdegong的专栏 4310
上一篇: 浅谈python实现简单区块链结构
下一篇: Python中的五种特性:切片,迭代,列表生成式,生成器,迭代器。
一秋的编程笔记
博客等级 码龄3年 1574粉丝 319原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值