本文我們通過 Socket,寫一個 HTTP 協議,直觀的感受一下上篇文章中的請求和響應。
定義 socket server
通過上篇文章,我們知道 HTTP 協議底層是通過 Socket 實現的,所以我們先通過 socket 定義一個 server
import socket#初始化 socke
sock=socket.socket()
#綁定 地址
sock.bind(('127.0.0.1',8081))#在 sock.listen(5) 中,參數 5 表示最多可以排隊等待處理的連接數量為 5。
# 如果有更多的連接請求到達,超過該數量的連接將被拒絕。
sock.listen(5)
while True:#接受客戶端請求conn,addr=sock.accept()data=conn.recv(1024)print('客戶端的請求數據\r\n',data.decode('utf-8'))print("打印完畢=====")#響應客戶端的請求conn.send(b'Hello world')conn.close()
在 PyCharm 中執行這段代碼后,通過瀏覽器訪問 http://127.0.0.1:8081/
Sever 端 PyCharm 打印結果
客戶端的請求數據
GET / HTTP/1.1
Host: 127.0.0.1:8081
Connection: keep-alive
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7打印完畢=====
分析客戶端請求參數-GET請求
在上篇文章中我們講到 HTTP 協議在發送請求的時候,必須要包含請求行、請求頭、請求體。這是瀏覽器幫我們組織好的。
此處的請求行為
GET / HTTP/1.1
請求頭為:
Host: 127.0.0.1:8081
Connection: keep-alive
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
請求體為:
之所以為空,是因為 GET 請求沒有請求體。
分析客戶端請求參數-PUT請求
首先通過 python request 包發送 put 請求,因為請求必須要包括請求行、請求頭以及請求體,所以 python request 模板會幫我們組織好。
import requestsdata={"username":"test","password":"<PASSWORD>"}
respone=requests.post("http://127.0.0.1:8081",json=data)
print(respone)
Sever 端 PyCharm打印結果
客戶端的請求數據
POST / HTTP/1.1
Host: 127.0.0.1:8081
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 46
Content-Type: application/json{"username": "test", "password": "<PASSWORD>"}
打印完畢=====
此處的請求行為:
POST / HTTP/1.1
請求頭為:
Host: 127.0.0.1:8081
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Content-Length: 46
Content-Type: application/json
請求體為:
{"username": "test", "password": "<PASSWORD>"}
服務端響應參數
通過瀏覽器訪問 http://127.0.0.1:8081/ 時,雖然 server 端接受到請求了,也給瀏覽器反回了 hello world
但瀏覽器仍然報錯了
另外當我們通過 python request 發送 put 請求時,同樣 server 端接受到請求了,也返回了 hello world
但 request 程序仍然報錯了
Traceback (most recent call last):File "/Users/isx/opt/anaconda3/lib/python3.11/site-packages/urllib3/connectionpool.py", line 791, in urlopenresponse = self._make_request(^^^^^^^^^^^^^^^^^^^File "/Users/isx/opt/anaconda3/lib/python3.11/site-packages/urllib3/connectionpool.py", line 537, in _make_requestresponse = conn.getresponse()^^^^^^^^^^^^^^^^^^File "/Users/isx/opt/anaconda3/lib/python3.11/site-packages/urllib3/connection.py", line 461, in getresponsehttplib_response = super().getresponse()^^^^^^^^^^^^^^^^^^^^^File "/Users/isx/opt/anaconda3/lib/python3.11/http/client.py", line 1390, in getresponseresponse.begin()File "/Users/isx/opt/anaconda3/lib/python3.11/http/client.py", line 325, in beginversion, status, reason = self._read_status()^^^^^^^^^^^^^^^^^^^File "/Users/isx/opt/anaconda3/lib/python3.11/http/client.py", line 307, in _read_statusraise BadStatusLine(line)
http.client.BadStatusLine: Hello world
這是為什么?
上篇文章中,我們也講過,服務端的響應也必須要包括響應行、響應頭以及響應體,而我們寫的 sever 中代碼,趙括響應體,所以瀏覽器和 python request 包會報錯。
#響應客戶端的請求
conn.send(b'Hello world')
我們遵循服務端的響應也必須要包括響應行、響應頭以及響應體這個要求,改進 server 代碼
import socketsock=socket.socket()sock.bind(('127.0.0.1',8081))
#在 sock.listen(5) 中,參數 5 表示最多可以排隊等待處理的連接數量為 5。
# 如果有更多的連接請求到達,超過該數量的連接將被拒絕。
sock.listen(5)
while True:conn,addr=sock.accept()data=conn.recv(1024)print('客戶端的請求數據\r\n',data.decode('utf-8'))print("打印完畢=====")conn.send(b'HTTP/1.1 200 OK \r\nDate: Tue, 02 Mar 2024 12:00:00 GMT\r\nServer: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips\r\nContent-Type: text/plain\r\nHello world')conn.close()
擴展
content-type
content-type 是請求頭以及響應頭中最重要的參數,它可以分別告訴客戶端和服務端該如何處理請求體或者響應體中的參數。舉個例子:
server代碼
import socketsock=socket.socket()sock.bind(('127.0.0.1',8081))
#在 sock.listen(5) 中,參數 5 表示最多可以排隊等待處理的連接數量為 5。
# 如果有更多的連接請求到達,超過該數量的連接將被拒絕。
sock.listen(5)
while True:conn,addr=sock.accept()data=conn.recv(1024)print('客戶端的請求數據\r\n',data.decode('utf-8'))print("打印完畢=====")conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n{"username": "test", "password": "<PASSWORD>"}')conn.close()
為了更好的呈現響應的結果,這個我們借助 postman 工具。當 Content-Type: text/plain,postman 接受服務端返回的數據類型為 text
當 Content-Type:application/json 時,postman 服務端返回的數據類型為 json