此文爲《用 Python 擼一個 Web 服務器》系列教程的一個補充,這個系列教程介紹瞭如何使用 Python 內置的 socket
庫實現一個簡易版的 Web 服務器。html
之因此寫這篇文章,是由於我發現不少人並不清楚 HTTP 客戶端的概念,覺得只有瀏覽器才叫 HTTP 客戶端。事實上並不是如此,咱們在 Web 開發中常見的 Postman
、爬蟲程序
、curl 命令行工具
等,這些均可以稱爲 HTTP 客戶端。python
這裏以一個 Hello World
程序來做爲示例服務器,實現以下:json
# 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 客戶端程序,接下來對其進行測試。curl
首先在終端中使用 Python 運行服務器端程序:python3 server.py
。而後在另外一個終端中使用 Python 運行客戶端程序:python3 client.py
。socket
能夠看到客戶端打印結果以下:函數
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
,看看打印結果如何。
Web 開發本質是圍繞着 HTTP 協議進行的,HTTP 協議是 Web 開發的基石。因此對於何爲 HTTP 服務端、何爲 HTTP 客戶端的概念不夠清晰的話,實際上都是對 HTTP 協議不夠理解。
最後,給你們留一道做業題,實現 requests
庫的 response.json()
方法。
聯繫我: