1. 程式人生 > >html協議與靜態伺服器的python實現

html協議與靜態伺服器的python實現

html協議

瀏覽器請求

協議表示規定,意思就是必須按照這個來,不然你發出去的東西別人不認識

# 這裡寫的是請求方式,請求的內容 / 表示根目錄 HTTP/1.1表示版本
GET / HTTP/1.1
# 請求的域名
Host: www.baidu.com
# 請求的連線方式,使用的是長連線模式
Connection: keep-alive
Upgrade-Insecure-Requests: 1
# 表示通過什麼瀏覽器請求
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
# 表示可以接受的檔案型別
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://www.baidu.com/
# 表示接受的壓縮包格式
Accept-Encoding: gzip, deflate, br
# 表示接受的解析語言
Accept-Language: zh-CN,zh;q=0.9

超文字傳輸協議只是很多協議中的一種,還有https基於安全性的超文字傳輸協議

伺服器的響應

伺服器在收到瀏覽器的請求之後,根據請求內容和請求方式等等,呼叫對應的程式進行先關的響應,響應的內容也是必須符合規範,響應內容包裹響應頭和響應體

響應體就是返回回來的響應頁面,響應頭包含了響應的一些規範資訊,必須滿足http協議的寫法

響應頭部分

# 申明響應的協議版本 響應後的狀態碼(200表示成功) ok這個可以隨便寫
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xb533b01200030d20
Cache-Control: private
# 響應的連線方式,表示長連線
Connection: Keep-Alive
# 響應的內容壓縮格式
Content-Encoding: gzip
# 響應的內容解析格式和編碼方式
Content-Type: text/html;charset=utf-8
# 兩個時間
Date: Sat, 15 Dec 2018 05:03:20 GMT
Expires: Sat, 15 Dec 2018 05:03:20 GMT
# 響應的伺服器
Server: BWS/1.1
Strict-Transport-Security: max-age=172800
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

這裡還存在cookie的使用,這個後面再說

瀏覽器點選確定之後到頁面顯示完全

這中間到底經過了多少步驟

DNS伺服器

我們在輸入域名的時候實際上每個域名對應的是一個IP地址,計算機並不能進行解析鬱悶,只能解析的是ip地址,所以平時我們輸入的域名真實的是先在自己電腦上進行域名檢索,如果本機上有則直接返回對應域名的ip,如果沒有則進行連線外面進行訪

先通過閘道器進行訪問路由,路由進行連線最後到DNS網段進行搜尋找到對應的DNS伺服器,伺服器上進行解析提供的域名之後進行返回域名對應的ip.

拿到DNS解析的ip之後進行ip訪問,還是通過閘道器和路由找到對應的ip,此時建立與伺服器的三次握手,三次握手完成之後,客戶端傳送請求,服務端回覆請求,之後揮手

需要注意的是向DNS伺服器傳送請求的協議實際上是UDP協議

當域名輸入完成後本機與伺服器發生的連線次數

總共存在10次包的傳遞,當然最初我也以為是11次,實際上需要之後10次.

首先三次握手的時候就是3次,四次揮手的時候就是4次,這裡就有7次

在客戶端與服務端進行通訊的時候情況本身應該是:

客戶端----->服務端

這一次是客戶端請求,表示快把網頁程式碼給我

服務端----->客戶端

服務端收到之後給一個回覆:好的我收到你的請求了

服務端----->客戶端

服務端把客戶端請求的資料傳送給客戶端

分割線--------------------------------------------------------------------------------------------------------------------------------------------------

以下就是四次揮手

服務端----->客戶端

服務端已經把資料包傳送給客戶端了,現在再連線已經沒有必要,所以服務端此時斷開連線

客戶端----->服務端

客戶端收到斷開連線請求,表態,我已經收到了,同意斷開

客戶端----->服務端

客戶端處理完資料之後傳送確認資訊,我同意斷開

服務端----->客戶端

服務端收到客戶端的確認資訊之後進行返回資訊,表示已經確認資訊,等待2MSL時間後關閉

這裡之所以在四次揮手之前客戶端收到服務端的資料後沒有回覆確認資訊,實際上利用抓包來看真的沒有這個確認資訊,根據tcp這種安全性的協議不應該嘛.事實上是因為設計協議的時候就考慮到,伺服器發完之後會斷開做四次揮手的動作,你客戶端傳送的確認資訊已經沒有意義,不管你確不確認服務端都會直接傳送斷開請求,一旦服務端傳送斷開請求後,服務端將無法主動回覆,計算真的沒有收到也不會再次發包,因此這裡客戶端回覆一個確認包的意義就沒有了,所以總共實際上是10次包的傳遞

cookie和session的區別

cookie使用的是瀏覽器進行暫時儲存,cookie將資訊儲存在瀏覽器上,cookie通常使用在安全要求不高的使用場景,必去未登入狀態下的購物車資料,比如瀏覽某些網頁後,使用者的點選資訊被存在cookie中,當用戶跳轉到其他網站上去的時候,如果有這個網站的廣告塊,則cookie就會傳入資訊,系統就會智慧的推薦商品

session的儲存方式與cookie不同,session將資料儲存在伺服器,這就保證了資料的安全性,而且session是基於cookie的,session會返回一個加密後的省份識別碼給瀏覽器中的cookie進行儲存,別人計算破解後拿到的也只是一對加密後的程式碼,實際的資訊還是拿不到,這就確保了其安全性.因此session使用的場景就是要求安全性比較高的場景.比如使用者的登入

當如果禁用了cookie後,session還是能夠正常使用,只是session生成的身份識別碼不在cookie中,如果使用者自己儲存後並填寫在請求頭裡面,session依然會正常執行,不會受到影響,只是這樣非常不方便

實現一個靜態伺服器

雖然實際上不會讓我們去寫伺服器,但是這裡練習一下,瞭解整個過程,熟悉下各種頭

from socket import *
from multiprocessing import Process


def main():
    # 建立物件
    server = socket(AF_INET, SOCK_STREAM)
    # 配置斷開釋放埠功能
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 掛起伺服器
    server.bind(('', 8888))
    # 配置為監聽狀態
    server.listen()
    while True:
        # 客戶端連線上就建立新物件
        new_server, client_info = server.accept()
        # 建立程序
        p = Process(target=response_function, args=(new_server, client_info))
        # 開啟程序
        p.start()
        # 關閉外部的新物件
        new_server.close()


def response_function(new_server, client_info):
    data = new_server.recv(1024)
    if not data: # 客戶端斷開連線的時候會發送一個空,此時直接關閉
        new_server.close()
        return
    # print(f'來自{client_info}的請求:{data}')
    # 伺服器回覆
    # 先解析對應的請求資訊,拆分成為列表,並且進行取出第一行,轉碼後取出請求的檔名
    request_file_name = data.splitlines()[0].decode('utf-8').split(' ')[1]
    # 如果請求的頁面不存在則需要進行更改回復內容
    try:
        # 進行檔案開啟
        with open(SERVER_ROOT+request_file_name, 'rb') as f:
            # 讀取出檔案資訊
            response_body = f.read()
    except:
        # 如果報錯則返回404
        response_heads = b'HTTP/1.1 404 not find\r\n'
        # 配置解釋的型別
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        # 響應內容
        response_body = '沒得'.encode('utf-8')
    else:
        # 準備好響應頭資料
        response_heads = b'HTTP/1.1 200 OK\r\n'
        # 配置解釋的型別
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'

    print(request_file_name)
    # 傳送響應訊息
    new_server.send(response_heads+response_body)
    new_server.close()


# 在外面定義伺服器查詢的根目錄
SERVER_ROOT = './static'
if __name__ == '__main__':
    main()

當然還是可以利用面向物件的思想將其封裝稱為一個可呼叫的包

但是需要注意的是在windows環境下,凡是使用了多程序的,而且原來的包裡面寫了

if __name__ == '__main__':

這種需要注意,坑爹的windows裡面直接呼叫會進入死迴圈的,並且有時候是不會報錯的,死迴圈的原因在於程序開啟的那個地方,程序開啟後子程序複製一份父程序的記憶體空間(這樣說不準確,但是粗略理解就這樣了),那麼在這個py檔案被呼叫的時候執行到這裡時就會再次載入子程序,子程序裡面跟父程序一樣的還是有一個分裂程序,然後繼續載入,這樣就沒完了,程式就會卡在程序分裂那裡

解決方法很簡單,誰匯入,誰就要寫

if __name__ == '__main__':

當然在linux裡面就不會出現這種問題,隨便呼叫