用 Python 实现一个简易版 HTTP 客户端

简介: 用 Python 实现一个简易版 HTTP 客户端

此文为《用 Python 撸一个 Web 服务器》系列教程的一个补充,这个系列教程介绍了如何使用 Python 内置的 socket 库实现一个简易版的 Web 服务器。

之所以写这篇文章,是因为我发现很多人并不清楚 HTTP 客户端的概念,以为只有浏览器才叫 HTTP 客户端。事实上并非如此,我们在 Web 开发中常见的 Postman爬虫程序curl 命令行工具 等,这些都可以称为 HTTP 客户端。

服务器程序示例

这里以一个 Hello World 程序来作为示例服务器,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# server.py
import socket
import threading
defprocess_connection(client):
"""处理客户端连接"""
# 接收客户端发来的数据
    data = b''
whileTrue:
        chunk = client.recv(1024)
        data += chunk
if len(chunk) < 1024:
break
# 打印从客户端接收的数据
    print(f'data: {data}')
# 给客户端发送响应数据
    client.sendall(b'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello World</h1>')
# 关闭客户端连接对象
    client.close()
defmain():
# 创建 socket 对象
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 允许端口复用
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定 IP 和端口
    sock.bind(('127.0.0.1', 8000))
# 开始监听
    sock.listen(5)
whileTrue:
# 等待客户端请求
        client, addr = sock.accept()
        print(f'client type: {type(client)}\naddr: {addr}')
# 创建新的线程来处理客户端连接
        t = threading.Thread(target=process_connection, args=(client,))
        t.start()
if __name__ == '__main__':
    main()

服务器端程序不做过多解释,如有不明白的地方可以参考 用 Python 撸一个 Web 服务器-第2章:Hello-World 一节。

极简客户端

知道了如何用 socket 库实现服务器端程序,那么理解客户端程序的实现就非常容易了。客户端程序代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# client.py
import socket
# 创建 socket 对象
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 指定服务器 IP 和端口,进行连接
sock.connect(('127.0.0.1', 8000))
# 向 URL "/" 发送 GET 请求
sock.send(b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\n\r\n')
# 接收服务端响应数据
data = b''
whileTrue:
    chunk = sock.recv(1024)
    data += chunk
if len(chunk) < 1024:
break
# 打印响应数据
print(data)
# 关闭连接
sock.close()

相对来说客户端程序要简单一些,创建 socket 对象的代码与服务器端程序并无差别,客户端 socket 对象根据 IP 和端口来连接指定的服务器,建立好连接后就可以发送数据了,这里根据 HTTP 协议构造了一个针对 / URL 路径的 GET 请求,为了简单起见,请求头中仅携带了 HTTP 协议规定的必传字段 Host,请求发送成功后便可以接收服务器端响应,最后别忘了关闭 socket连接。

仅用几行代码,我们就实现了一个极简的 HTTP 客户端程序,接下来对其进行测试。

首先在终端中使用 Python 运行服务器端程序:python3 server.py。然后在另一个终端中使用 Python 运行客户端程序:python3 client.py

可以看到客户端打印结果如下:

1
b'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello World</h1>'

以上,我们实现了一个极简的 HTTP 客户端。

参考 requests 实现客户端

用 Python 写过爬虫的同学,一定听说或使用过 requests 库,以下是使用 requests 访问 Hello World 服务端程序的示例代码:

1
2
3
4
5
6
7
8
9
10
# demo_requests.py
import requests
response = requests.request('GET', 'http://127.0.0.1:8000/')
print(response.status_code)  # 响应状态码
print('--------------------')
print(response.headers)  # 响应头
print('--------------------')
print(response.text)  # 响应体

在终端中使用 python3 demo_requests.py 运行此程序,将打印如下结果:

1
2
3
4
5
200
--------------------
{'Content-Type': 'text/html'}
--------------------
<h1>Hello World</h1>

接下来修改我们上面实现的极简 HTTP 客户端程序,使其能够支持 response.status_coderesponse.headersresponse.text功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# client.py
import socket
from urllib.parse import urlparse
classHTTPClient(object):
"""HTTP 客户端"""
def__init__(self):
# 创建 socket 对象
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 初始化数据
        self.status_code = 200
        self.headers = {}
        self.text = ''
def__del__(self):
# 关闭连接
        self.sock.close()
defconnect(self, ip, port):
"""建立连接"""
        self.sock.connect((ip, port))
defrequest(self, method, url):
"""请求"""
# URL 解析
        parse_result = urlparse(url)
        ip = parse_result.hostname
        port = parse_result.port or80
        host = parse_result.netloc
        path = parse_result.path
# 建立连接
        self.connect(ip, port)
# 构造请求数据
        send_data = f'{method}{path} HTTP/1.1\r\nHost: {host}\r\n\r\n'.encode('utf-8')
# 发送请求
        self.sock.send(send_data)
# 接收服务端响应的数据
        data = self.recv_data()
# 解析响应数据
        self.parse_data(data)
defrecv_data(self):
"""接收数据"""
        data = b''
whileTrue:
            chunk = self.sock.recv(1024)
            data += chunk
if len(chunk) < 1024:
break
return data.decode('utf-8')
defparse_data(self, data):
"""解析数据"""
        header, self.text = data.split('\r\n\r\n', 1)
        status_line, header = header.split('\r\n', 1)
for item in header.split('\r\n'):
            k, v = item.split(': ')
            self.headers[k] = v
        self.status_code = status_line.split(' ')[1]
if __name__ == '__main__':
    client = HTTPClient()
    client.request('GET', 'http://127.0.0.1:8000/')
    print(client.status_code)
    print('--------------------')
    print(client.headers)
    print('--------------------')
    print(client.text)

代码实现比较简单,我写了较为详细的注释,相信你能够看懂。其中使用了内置函数 urlparse ,此函数能够根据 URL 格式规则将 URL 拆分成多个部分。

在终端中使用 python3 client.py 运行此程序,打印结果与使用 requests 的结果完全相同。

1
2
3
4
5
200
--------------------
{'Content-Type': 'text/html'}
--------------------
<h1>Hello World</h1>

仅用几十行代码,我们就实现了一个简易版的 HTTP 客户端程序,并且还实现了类似 requests 库的功能。

接下来你可以尝试用它去访问现实世界中真实的 URL,比如访问 http://httpbin.org/get,看看打印结果如何。

P.S.

Web 开发本质是围绕着 HTTP 协议进行的,HTTP 协议是 Web 开发的基石。所以对于何为 HTTP 服务端、何为 HTTP 客户端的概念不够清晰的话,实际上都是对 HTTP 协议不够理解。

最后,给大家留一道作业题,实现 requests 库的 response.json() 方法。

相关文章
|
6月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
356 61
|
4月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
8月前
|
Python
使用Python实现multipart/form-data文件接收的http服务器
至此,使用Python实现一个可以接收 'multipart/form-data' 文件的HTTP服务器的步骤就讲解完毕了。希望通过我的讲解,你可以更好地理解其中的逻辑,另外,你也可以尝试在实际项目中运用这方面的知识。
369 69
|
7月前
|
网络协议 API 开发者
分析http.client与requests在Python中的性能差异并优化。
合理地选择 `http.client`和 `requests`库以及在此基础上优化代码,可以帮助你的Python网络编程更加顺利,无论是在性能还是在易用性上。我们通常推荐使用 `requests`库,因为它的易用性。对于需要大量详细控制的任务,或者对性能有严格要求的情况,可以考虑使用 `http.client`库。同时,不断优化并管理员连接、设定合理超时和重试都是提高网络访问效率和稳定性的好方式。
172 19
|
6月前
|
网络协议 API Python
解析http.client与requests在Python中的性能比较和改进策略。
最后,需要明确的是,这两种库各有其优点和适用场景。`http.client` 更适合于基础且并行的请求,`requests` 则因其易用且强大的功能,更适用于复杂的 HTTP 场景。对于哪种更适合你的应用,可能需要你自己进行实际的测试来确定。
175 10
|
9月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
864 29
|
12月前
|
数据采集 网络安全 Python
【Python】怎么解决:urllib.error.HTTPError: HTTP Error 403: Forbidden
解决 `urllib.error.HTTPError: HTTP Error 403: Forbidden`错误需要根据具体情况进行不同的尝试。通过检查URL、模拟浏览器请求、使用代理服务器和Cookies、减慢请求速度、使用随机的User-Agent以及使用更加方便的 `requests`库,可以有效解决此类问题。通过逐步分析和调试,可以找到最合适的解决方案。
835 18
|
12月前
|
数据采集 数据安全/隐私保护 Python
【Python】已解决:urllib.error.HTTPError: HTTP Error 403: Forbidden
通过上述方法,可以有效解决 `urllib.error.HTTPError: HTTP Error 403: Forbidden` 错误。具体选择哪种方法取决于服务器对请求的限制。通常情况下,添加用户代理和模拟浏览器请求是最常见且有效的解决方案。
681 10
|
数据采集 前端开发 算法
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
本文介绍了如何使用 Python 的 `requests` 库应对复杂的 HTTP 请求场景,包括 Spider Trap(蜘蛛陷阱)、SESSION 访问限制和请求频率限制。通过代理、CSS 类链接数控制、多账号切换和限流算法等技术手段,提高爬虫的稳定性和效率,增强在反爬虫环境中的生存能力。文中提供了详细的代码示例,帮助读者掌握这些高级用法。
737 1
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
|
数据采集 JSON 测试技术
Grequests,非常 Nice 的 Python 异步 HTTP 请求神器
在Python开发中,处理HTTP请求至关重要。`grequests`库基于`requests`,支持异步请求,通过`gevent`实现并发,提高性能。本文介绍了`grequests`的安装、基本与高级功能,如GET/POST请求、并发控制等,并探讨其在实际项目中的应用。
250 3

推荐镜像

更多