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

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

服务器程序示例

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

# server.pyimport socketimport threadingdef 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.pyimport 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.pyimport requestsresponse = 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_coderesponse.headersresponse.text性能。

# client.pyimport socketfrom urllib.parse import urlparseclass 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/