共计 4118 个字符,预计需要花费 11 分钟才能阅读完成。
此文为《用 Python 撸一个 Web 服务器》系列教程的一个补充,这个系列教程介绍了如何应用 Python 内置的 socket
库实现一个简易版的 Web 服务器。
之所以写这篇文章,是因为我发现很多人并不分明 HTTP 客户端的概念,认为只有浏览器才叫 HTTP 客户端。事实上并非如此,咱们在 Web 开发中常见的 Postman
、 爬虫程序
、curl 命令行工具
等,这些都能够称为 HTTP 客户端。
服务器程序示例
这里以一个 Hello World
程序来作为示例服务器,实现如下:
# server.py | |
import socket | |
import threading | |
def process_connection(client): | |
"""解决客户端连贯""" | |
# 接管客户端发来的数据 | |
data = b'' | |
while True: | |
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() | |
def main(): | |
# 创立 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) | |
while True: | |
# 期待客户端申请 | |
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
库实现服务器端程序,那么了解客户端程序的实现就非常容易了。客户端程序代码实现如下:
# 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'' | |
while True: | |
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
。
能够看到客户端打印后果如下:
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
服务端程序的示例代码:
# 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
运行此程序,将打印如下后果:
200 | |
-------------------- | |
{'Content-Type': 'text/html'} | |
-------------------- | |
<h1>Hello World</h1> |
接下来批改咱们下面实现的极简 HTTP 客户端程序,使其可能反对 response.status_code
、response.headers
和 response.text
性能。
# client.py | |
import socket | |
from urllib.parse import urlparse | |
class HTTPClient(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() | |
def connect(self, ip, port): | |
"""建设连贯""" | |
self.sock.connect((ip, port)) | |
def request(self, method, url): | |
"""申请""" | |
# URL 解析 | |
parse_result = urlparse(url) | |
ip = parse_result.hostname | |
port = parse_result.port or 80 | |
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) | |
def recv_data(self): | |
"""接收数据""" | |
data = b'' | |
while True: | |
chunk = self.sock.recv(1024) | |
data += chunk | |
if len(chunk) < 1024: | |
break | |
return data.decode('utf-8') | |
def parse_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
的后果完全相同。
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()
办法。
分割我:
- 微信:jianghushinian
- 邮箱:jianghushinian007@outlook.com
- 博客地址:https://jianghushinian.cn/