首頁  >  文章  >  後端開發  >  如何使用Python寫一個簡單的HTTP伺服器?

如何使用Python寫一個簡單的HTTP伺服器?

王林
王林轉載
2023-05-07 22:25:061752瀏覽

什麼是http

http是一個應用層協議,準確的來說是基於TCP/IP4層網路協定中的傳輸層中的TCP應用層協定。

額,4層模型大概是這樣的:

如何使用Python寫一個簡單的HTTP伺服器?

在網路通訊中,使用者的資料是以封包來傳送的,但是在實際通訊中,每一層都會對包進行封裝,從而形成段、數數據報、幀,最後是以比特流(二進制)進行傳輸,到了目標主機後,會對每一層又進行拆解,從而得到最後的報文。

http就在最上層,就是應用層那裡。

http到底離我們多近呢?甚至於你現在看到的文章,使用的就是http協議,它還有一個名字,叫做超文本傳輸協議,為什麼叫超文本呢?因為最開始的是時候,http是被用來傳輸Hypertext文檔的,所以被叫做超文本協議,當然現在可以傳輸其他類型的數據,如: 視頻、音頻、圖片等,但是它依然被稱之為超文本協議。

很難理解吧,沒關係,繼續往下看。

分析http請求報文和回應封包格式

透過上面的簡介,我們知道http是應用程式層協議,它在網路協定中,被稱為之為報文的,讓我們來看看http的請求封包和回應封包吧。

http封包由4部分組成,分別是起始行、首部行、空白行 以及 實體組成。以\r\n(也稱為CRLF)進行分割。

讓我們來看一下實際的報文呢。

linux中,我們可以使用curl -v 網址來列印詳細的請求信息,其中就包括了報文。

指令:

curl -v http://juejin.cn

請求訊息:

如何使用Python寫一個簡單的HTTP伺服器?

#其中輸出的結果中> 代表我們發出的報文,而代表伺服器發送給我們的回應訊息。下面我們將結合報文來看上面的數據資訊。

請求封包格式如下:

如何使用Python寫一個簡單的HTTP伺服器?

其中請求行會指定http的請求方法,如: GETPOSTHEAD等,URL則是要求的檔案路徑,協定版本需要指定http的版本,最後是以CRLF結束。

首部行可以有多個,以 (字段名: 值) 的方式出現,每一個首部行同樣以CRLF結束。

而後是空白行。空白行則代表http封包頭結束了,接下來該是發送的封包主體了,接下來,我們將上述請求http://juejin.cn的例子,填入表格來看下:

如何使用Python寫一個簡單的HTTP伺服器?

上述是我們使用curl工具請求的http://juejin.cn請求封包整體形式,我們可以看到,我們使用了GET方法,取得伺服器的/訊息,使用的協定是HTTP/1.1,而後攜帶了3個首部行,分別是User-AgentHost以及Accept

回應封包格式如下:

如何使用Python寫一個簡單的HTTP伺服器?

將回應訊息和請求訊息進行對比,我們不難發現,除了第一行以外,其他的格式都是一樣的,所以,我們只介紹響應行的信息,在響應報行中,第一個是協議的版本,這個是伺服器的協議版本,而後便是狀態碼,用於告知客戶端,伺服器回應的訊息,最後是短語,短語的作用是告知使用者,這個回傳訊息大概是什麼意思。

好了,我們將上述juejin.cn回應給我們的報文,我們填到表格中呢:

如何使用Python寫一個簡單的HTTP伺服器?

上述是我們使用curl請求http://juejin.cn/,伺服器傳回的訊息,我們逐行來看下,回應行,告知了我們http版本是HTTP/1.1,狀態碼是301,短語是連結被轉移了。

上述我們若僅透過狀態碼的話,是很難get到整個報文的意思的,不過有短語,就可以猜一下了。

首部行,告知了我们服务器 、时间 、 报文类型 以及 报文长度。还记得我们第一段落介绍过得,http现在除了发送超文本以外,还可以发送图片、视频等,就是通过首部行Content-Type来确定的。

接着是空白行,最后是报文主体,哎,有没有感觉奇怪呢?为什么请求报文主体是空的呢?这是因为报文主体长度是由首部行Content-Length来定义的,如上报文展示的是,我们报文主体有262个字符。

手写一个简单的http服务器

上述,我们介绍了,什么是http以及初略的看了一下 http的请求报文和响应报文,那么,我们如何构建一个http服务器呢?

我们知道,http是应用层协议,是基于传输层tcp来实现的,所以,我们若想构建一个http服务器,那么应该写一个socket程序出来吧。

import socket
import threading

def handle(client , addr):
    print("from " , addr)
    data = client.recv(1024)
    for k,v in enumerate(data.decode().split("\r\n")):
        print(k ,v)

def main():
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.bind(("127.0.0.1",8080))
    s.listen()
    
    while True:
        client , addr = s.accept() 
        t = threading.Thread(target=handle,args=(client,addr))
        t.start()

if __name__ == '__main__'
    main()

上述,我们写了一个tcp程序,它将监听本地回环地址的8080端口,若此时我们使用curl -v 127.0.0.1:8080请求一下该接口,我们将会得到请求报文了,如下:

如何使用Python寫一個簡單的HTTP伺服器?

我们得到请求报文后,可以构建一个响应报文发送回去,例如: Hello, Destined Person.,我们就可以这样来构建http

import socket
import threading

def handle(client , addr):
    print("from " , addr)
    data = client.recv(1024)
    
    #请求报文
    for k,v in enumerate(data. decode() .split("\r\n")):
        print(k ,v)
        
    bodyText = "He1lo,Destined Person."
    #响应报文
    #响应行
    client.send(b"HTTP/1.1 200 OK\r\n")
    #首部行
    client. send(b"Server: pdudo_web_sites\r\n") 
    client. send(b"Content-Type: text/html\r\n")
    client. send(("Content-Length: %s\r\n" % (len(bodyText) + 2)).encode())
    client. send(b"\r\n")
    client. send(("%s\r\n" %(bodyText)).encode())
    
def main():
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s .bind(("127.0.0.1"8080))
        s .listen()
        
        while True:
            client,addr = s.accept()
            t = threading.Thread(target=handle,args=(client,addr))
            t.start()
    finally:
        s.close()

if __name__ == '__main__':
    main()

最后我们使用curl再来测试一下,是可以得到消息的。

如何使用Python寫一個簡單的HTTP伺服器?

以上是如何使用Python寫一個簡單的HTTP伺服器?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除