用 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() 方法。

相关文章
|
13天前
|
数据采集 网络安全 Python
【Python】怎么解决:urllib.error.HTTPError: HTTP Error 403: Forbidden
解决 `urllib.error.HTTPError: HTTP Error 403: Forbidden`错误需要根据具体情况进行不同的尝试。通过检查URL、模拟浏览器请求、使用代理服务器和Cookies、减慢请求速度、使用随机的User-Agent以及使用更加方便的 `requests`库,可以有效解决此类问题。通过逐步分析和调试,可以找到最合适的解决方案。
85 18
|
27天前
|
数据采集 数据安全/隐私保护 Python
【Python】已解决:urllib.error.HTTPError: HTTP Error 403: Forbidden
通过上述方法,可以有效解决 `urllib.error.HTTPError: HTTP Error 403: Forbidden` 错误。具体选择哪种方法取决于服务器对请求的限制。通常情况下,添加用户代理和模拟浏览器请求是最常见且有效的解决方案。
118 10
|
1月前
|
数据采集 JSON 测试技术
Grequests,非常 Nice 的 Python 异步 HTTP 请求神器
在Python开发中,处理HTTP请求至关重要。`grequests`库基于`requests`,支持异步请求,通过`gevent`实现并发,提高性能。本文介绍了`grequests`的安装、基本与高级功能,如GET/POST请求、并发控制等,并探讨其在实际项目中的应用。
42 3
|
3月前
|
数据采集 前端开发 算法
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
本文介绍了如何使用 Python 的 `requests` 库应对复杂的 HTTP 请求场景,包括 Spider Trap(蜘蛛陷阱)、SESSION 访问限制和请求频率限制。通过代理、CSS 类链接数控制、多账号切换和限流算法等技术手段,提高爬虫的稳定性和效率,增强在反爬虫环境中的生存能力。文中提供了详细的代码示例,帮助读者掌握这些高级用法。
147 1
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
|
2月前
|
JSON API 数据格式
Python中获取HTTP请求响应体的详解
本文介绍了如何使用Python的`requests`和`urllib`库发送HTTP请求并处理响应体。`requests`库简化了HTTP请求过程,适合快速开发;`urllib`库则更为底层,适用于性能要求较高的场景。文章详细演示了发送GET请求、处理JSON响应等常见操作。
51 3
|
3月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
180 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
3月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
199 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
3月前
使用Netty实现文件传输的HTTP服务器和客户端
本文通过详细的代码示例,展示了如何使用Netty框架实现一个文件传输的HTTP服务器和客户端,包括服务端的文件处理和客户端的文件请求与接收。
86 1
使用Netty实现文件传输的HTTP服务器和客户端
|
4月前
|
关系型数据库 MySQL Python
mysql之python客户端封装类
mysql之python客户端封装类
|
4月前
|
数据采集 JSON API
🎓Python网络请求新手指南:requests库带你轻松玩转HTTP协议
本文介绍Python网络编程中不可或缺的HTTP协议基础,并以requests库为例,详细讲解如何执行GET与POST请求、处理响应及自定义请求头等操作。通过简洁易懂的代码示例,帮助初学者快速掌握网络爬虫与API开发所需的关键技能。无论是安装配置还是会话管理,requests库均提供了强大而直观的接口,助力读者轻松应对各类网络编程任务。
131 3