python實現簡單http伺服器
- 2019 年 10 月 6 日
- 筆記
首先http協議是基於tcp協議的,這裡會用到我們前幾天寫的tcp伺服器的知識。
我們暫且把http協議當做一個規定,就是說在瀏覽器訪問一個頁面時候,瀏覽器會發送一些東西給伺服器,那麼你發送的這些東西就是基於http協議發送的。
以百度為例我們來看一下在訪問的時候,瀏覽器給伺服器發送了什麼?
1、打開Google瀏覽器
2、F12,打開開發者模式
3、輸入www.baidu.com
會看到如圖所示:

我們只需要先了解前兩個:
GET / HTTP/1.1:GET表示請求,/表示訪問主頁,HTTP/1.1表示http協議1.1版本
Host:網址或者ip地址
看一下百度伺服器給我們返回了什麼

這些也是根據http協議返回的,那麼必須有的是什麼?
必須有的是第一條:HTTP/1.1 200 OK
其他都可以沒有,但是我們模擬肯定要返回數據。
上面這張圖叫做Headers,就是頭,那我們的body部分就是我們的html頁面,有了html頁面,頁面才變得好看。
瀏覽器如何區分是headers部分還是body部分?
很簡單,中間加一個空行。了解了這些,我們來實現一個簡單的http伺服器。
其實http協議是基於tcp協議的。http協議在tcp的基礎上,對伺服器返回的數據的一些規定。
http伺服器實例
import socket
def dump_data(cli_socket):
# 接收瀏覽器發送來的請求
recv_data = cli_socket.recv(1024)
# 輸出瀏覽器給我們發送了什麼
print(recv_data)
# 更具上面的http協議模擬返回數據,rn表示換行
resp_data = 'HTTP/1.1 200 OKrn'
# 一行空數據,區別headers部分和body部分
resp_data += 'rn'
# body部分
resp_data += '<h1>hello,word.</h1>'
cli_socket.send(resp_data.encode('utf-8'))
cli_socket.close()
def main():
tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(("",7891))
tcp_server.listen(128)
print('等待')
while True:
cli_socket,cli_addr = tcp_server.accept()
dump_data(cli_socket)
tcp_server.close()
if __name__ == "__main__":
main()
我們運行,會顯示等待,等待我們的瀏覽器發送請求,我們打開瀏覽器,輸入127.0.0.1:7891
就會看到我們發送給瀏覽器的內容,127.0.0.1表示本地,用冒號隔開,7891是我tcp伺服器的埠號。這樣我們就實現了一個簡單的http伺服器。
tcp的3次握手
第一次握手:客戶端將標誌位SYN賦值為1,隨機產生一個參數賦值給seq,發送給伺服器。等待伺服器確定。
第二次握手:伺服器通過SYN=1知道了該客戶端要請求建立連接,再添加一個ACK=1,產生一個隨機參數給seq,ack賦值為客戶端隨機數加1,發送給客戶端確認連接,伺服器進入SYN_RCVD狀態。
第三次握手:客戶端收到數據後,檢查ack的值是否是隨機數加1,ACK是否為1,正確就把ack加1,再發送給伺服器,伺服器確認數據,客戶端和伺服器都進入ESTABLISHED狀態,完成三次握手。就可以傳輸數據了。
tcp的4次揮手
握手建立連接,揮手就是斷開連接。
第一次揮手:簡單來說就是發送一個數據表示我想斷開連接,客戶端進入FIN_WAIT_1狀態。
第二次揮手:伺服器收到數據,告訴客戶端,我正在準備。請你確認是否斷開。客戶端進入FIN_WAIT_2狀態
第三次揮手:發送一個數據表示我確認要斷開。伺服器進入LAST_ACK狀態
第四次揮手:服務受收到消息,說我知道要關閉了,並且會發一個消息給伺服器,之後進入TIME_WAIT。
根據瀏覽器的需求對應返回頁面
這個程式相比上面的數據多了一個對url的判斷而已。
這次我們用一個html文件來嘗試。
我們在一個文件夾下創建一個txt文件,寫上這樣一段程式碼:
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>我是登陸頁面</h1>
</body>
</html>
將後綴名txt修改成html。
再創建一個create.html頁面。只需要把裡面的程式碼修改成:
<html>
<head>
<meta charset="UTF-8">
<title>create</title>
</head>
<body>
<h1>我是註冊頁面</h1>
</body>
</html>
接下來來寫我們的python程式碼。
import socket
import re
def dump_data(cli_socket):
# 接收到的是bytes類型,我們解碼
recv_data = cli_socket.recv(1024).decode('utf-8')
# 將我們接收到的數據以空格切開變成一個列表,便於我們取值,判斷請求的那個頁面
recv_data_lines = recv_data.splitlines()
print(recv_data_lines)
# 正則提取瀏覽器需要的頁面
# 數據:GET /index.html HTTP/1.1
# 或者:POST /index.html HTTP/1.1 或者其他請求方式
f_name = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])
resp_data = 'HTTP/1.1 200 OKrn'
resp_data += 'rn'
# body部分
# 讀取文件
file = open('F:'+f_name.group(1),'rb')
html_con = file.read()
file.close()
# 由於我們讀取文件是以二進位的方式讀取,所以不能直接相加
cli_socket.send(resp_data.encode('utf-8'))
# 分開發送就好了
cli_socket.send(html_con)
cli_socket.close()
def main():
tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(("",7891))
tcp_server.listen(128)
print('等待')
while True:
cli_socket,cli_addr = tcp_server.accept()
dump_data(cli_socket)
tcp_server.close()
if __name__ == "__main__":
main()
運行一下,提示我們沒有favicon.ico文件(圖標),我們創建一個就好了(直接文本創建該名字就好了,後綴也需要修改的)
接下來運行,我們在瀏覽器輸入127.0.0.1:7891/index.html 頁面就會顯示我是登陸頁面,輸入127.0.0.1:7891/create.html就會顯示我是註冊頁面,當然如果找不到文件你可以試著添加一個404頁面。或者返回為HTTP/1.1 404 NOT FOUND。
完整版:
import socket
import re
def dump_data(cli_socket):
recv_data = cli_socket.recv(1024).decode('utf-8')
recv_data_lines = recv_data.splitlines()
print(recv_data_lines)
ret = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])
if ret:
f_name = ret.group(1)
if f_name=="/":
f_name = '/index.html'
try:
file = open('F:'+f_name,'rb')
except:
resp_data = 'HTTP/1.1 404 NOT FOUNDrn'
resp_data += 'rn'
resp_data += '404'
cli_socket.send(resp_data.encode('utf-8'))
else:
html_con = file.read()
file.close()
resp_data = 'HTTP/1.1 200 OKrn'
resp_data += 'rn'
cli_socket.send(resp_data.encode('utf-8'))
cli_socket.send(html_con)
cli_socket.close()
def main():
tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(("",7891))
tcp_server.listen(128)
print('等待')
while True:
cli_socket,cli_addr = tcp_server.accept()
dump_data(cli_socket)
tcp_server.close()
if __name__ == "__main__":
main()