­

python實現簡單http伺服器

  • 2019 年 10 月 6 日
  • 筆記
這實現http伺服器之前,需要給大家補充一點知識,http協議。

首先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()