Python Web框架本質——Python Web開發系列一

  前言:瞭解一件事情本質的那一瞬間總能讓我得到巨大的愉悅感,但願這篇文章也能幫助到您。html

  目的:本文主要簡單介紹Web開發中三大基本功能:Socket實現、路由系統、模板引擎渲染。python

  進入正題。編程

  

 一. 基礎知識

    • Http/Https協議:簡單的對象訪問協議,對應於應用層。Http協議是基於TCP連接的。特色是無狀態、短鏈接。
    • Socket:Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,是介於傳輸層和應用層之間的一個協議,是一組接口。全部支持網絡編程的語言都會有對Socket的實現,而幾乎全部的Web開發框架底層都是由Socket實現的。在Socket編程中,主動發起鏈接的叫客戶端,被動響應鏈接的叫服務器,如在瀏覽網頁時瀏覽器本質上就是一個Socket客戶端,網站服務器就是一個Socket服務端。
    • Web框架:「Web應用框架(Web application framework)是一種開發框架,用來支持動態網站、網絡應用程序及網絡服務的開發。」 (引用自百度百科),簡而言之是用於開發Web應用的,Python常見Web框架有Flask、Django、Tornado等。

    Web開發中最基礎的三大功能分別是:瀏覽器

    • Socket服務端實現
    • 路由系統
    • 模板引擎渲染

    下面將對這三部分一一說明。服務器

 

 二. Socket編程

  既然幾乎全部的Web開發框架底層都是由Socket實現的,咱們就從Socket編程開始,用Socket實現一個服務端和瀏覽器進行通訊(細想一下,這就是Web服務最基本的需求了吧)。網絡

# 例1
import socket
# 生成一個socket對象
server = socket.socket()
# 綁定機器的ip端口
server.bind(("127.0.0.1", 8001))
# 配置最多隻能有五個請求在等待鏈接
server.listen(5)

while True:
    # 阻塞,等待接受請求
    conn, addr = server.accept()
    # 創建鏈接後接受數據,規定一次數據大小爲8096字節
    data = conn.recv(8096)
print(data) # 在該鏈接通道中發送數據,注意要是字節形式 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") conn.send(b"Hello World!") # 關閉鏈接 conn.close()

 

  

  這段代碼實現了一個Socket服務端,server.accept()會讓服務器阻塞等待客戶端的鏈接,當接收到鏈接請求,就返回數據。下面用瀏覽器發送鏈接請求:app

 

 

   

  能夠看到,瀏覽器已經成功接收到了Socket服務端發來"Hello World",成功解析並顯示在了網頁上,一個最基本的Web服務就順利完成啦!框架

  

 三. Socket編程中的HTTP/HTTPS協議

  Http/Https協議,簡單的對象訪問協議,對應於應用層,簡單而言就是一個你們都遵循的格式、規範,根據這個規範咱們能夠獲取本身所需的信息。Http協議是基於TCP連接的。但與TCP一直保存鏈接不主動斷開相比,HTTP/HTTPS的鏈接在一次傳輸後就會斷開,而且不會保存鏈接信息,下次再鏈接時沒有上次鏈接的狀態,因此說特色是無狀態、短鏈接。socket

  先來看看上面代碼中在服務端接收到鏈接請求的內容(具體對應代碼:data = conn.recv(8096))函數

b'GET / HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8001\r\nConnection: Keep-Alive\r\n\r\n'

解釋:
 'method url protocol\r\nheaders-param1\r\nheaders-param2\r\nheaders-param3...headers-paramn\r\n\r\n'

 

  由此咱們能夠窺見一點HTTP/HTTPS協議內容,它規定GET請求的格式就如上面所示,那麼咱們拿到請求的數據後,經過簡單的字符串處理就能拿到這個請求的method,url,protocol,headers,例如url = data.split('\r\n')[0].split(' ')[1]。

  此外,由於這個請求只是一個簡單的GET請求,請求信息到\r\n\r\n就結束了,事實上\r\n\r\n也是一個分割符,分割的是請求頭和請求體,當一個請求是POST請求時,POST的參數就在請求體中,也就是說post_params  = data.split("\r\n\r\n")[1]。而在例1中,也用"\r\n\r\n"分割開了響應頭和響應體:conn.send(b"HTTP/1.1 200 OK\r\n\r\n"),conn.send(b"Hello World!")。

 

 四. 路由系統

  路由系統要完成的功能是:根據不一樣的請求信息作不一樣的數據處理,返回不一樣的數據響應。例如分別訪問「http://www.javashuo.com/article/p-zuajxdaq-kn.html」、「https://i-beta.cnblogs.com/posts/edit」,請求都會發送到博客園的服務器,根據url的不一樣,第一個請求會響應對應文章內容,而第二個請求會響應編輯後臺。

  接回上面的話題,當接收到一個遵循HTTP/HTTPS協議的請求時,咱們能夠經過字符串處理獲取到請求的url,而後根據不一樣的url調用不一樣功能的模塊或者函數來處理該請求,生成不一樣的數據來響應請求。如今來改寫咱們的Socket服務端,加入路由系統:

# 例2
import socket

def index():
    return b'Hello World!'

def func1():
    return b"I'm not hungry yet!"

def func2():
    return b"Cheers!"

# 路由表
routers = [
    ('/', index),
    ('/eat', func1),
    ('/drank', func2),
]


server = socket.socket()
server.bind(("127.0.0.1", 8001))
server.listen(5)

while True:

    conn, addr = server.accept()
    data = str(conn.recv(8096), encoding='utf-8')
    headers, bodys = data.split('\r\n\r\n') # 分割出請求頭,請求體
    temp_list = headers.split('\r\n')
    method, url, protocal = temp_list[0].split(' ') # 分割出請求方法,url,協議
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    
    func_name = None
    for item in routers: # 路由匹配,根據url獲取相應的處理函數
        if url == item[0]:
            func_name = item[1]
            break
    
    if func_name:
        response = func_name()
    else:
        response = b'404 not found'  # 假如url不在路由系統中,模擬返回404
    conn.send(response)
    conn.close()

 

 

  在例2中,咱們加入了路由表,經過字符串處理分割出請求url,並根據url去匹配路由表,找到合適的處理函數,若是沒有則返回404,再看看瀏覽器訪問結果:

 

 

  能夠看到,例2的Socket服務器已經可以根據請求url的不一樣調用合適的處理函數來處理返回正確的數據了,這就是Web開發框架中路由系統的本質!

 

 五. 模板引擎渲染

  由上面的例子中咱們能夠發現,Socket編程中的數據傳輸的數據都是字節,而咱們獲取請求信息和響應信息構造其實都是字符串的處理。前面的例子咱們響應的數據是簡單的字節串,此外咱們響應的數據還能夠是HTML代碼,這些代碼傳輸到瀏覽器等客戶端時會被渲染成咱們經常看到的網頁。看下個例子:

# 例3
# server.py
import socket

def index():
    with open('index.html', 'r') as f: # 讀取html文件內容做爲響應體數據
        response = f.read()
    return response.encode('utf-8')

# 路由表
routers = [
    ('/', index),
]

server = socket.socket()
server.bind(("127.0.0.1", 8001))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = str(conn.recv(8096), encoding='utf-8')
    headers, bodys = data.split('\r\n\r\n')  
    temp_list = headers.split('\r\n')
    method, url, protocal = temp_list[0].split(' ')  
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    
    func_name = None
    for item in routers:  
        if url == item[0]:
            func_name = item[1]
            break
    
    if func_name:
        response = func_name()
    else:
        response = b'404 not found'  
    conn.send(response)
    conn.close()

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>"HELLO WORLD"</title>
</head>
<body>
    <table border="1">
        <tr>
            <th>Month</th>
            <th>Savings</th>
        </tr>
        <tr>
            <td>January</td>
            <td>$100</td>
        </tr>
    </table>
</body>
</html>

 

 

 

  在這個例子中,咱們修改了index(),在例2中的直接返回字節串做爲響應體數據,而例3中咱們讀取了index.html文件內容做爲響應體數據返回,在瀏覽器中已被渲染成一個帶表格的網頁了。在Web開發中,這個html文件就稱之爲模板,這種一成不變的網頁,稱之爲靜態網頁。

  

  在本例中咱們已經實現渲染靜態頁面了,可是目前大部分的網頁並非靜態的,存在着大量的動態數據,動態網頁的渲染須要咱們獲取最新的數據,拼接到模板合適的位置,而後做爲響應體數據返回。動態拼接的功能咱們能夠經過字符串替換來實現,在模板中合適的位置用特殊字符來作佔位符,當要響應數據時候,拿到最新的數據替換掉模板中的佔位符,便可作到用最新的數據做爲返回結果了。看下個例子:

# 例4
# server.py
import socket
import datetime

def index():
    with open('index2.html', 'r') as f:  # 讀取html文件內容做爲響應體數據
        response = f.read()
    response = response.replace('@temp@', str(datetime.datetime.now())) # 獲取當前時間,替換html文件內容中的佔位符
    return response.encode('utf-8')


# 路由表
routers = [
    ('/', index),
]

server = socket.socket()
server.bind(("127.0.0.1", 8001))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = str(conn.recv(8096), encoding='utf-8')
    headers, bodys = data.split('\r\n\r\n')  
    temp_list = headers.split('\r\n')
    method, url, protocal = temp_list[0].split(' ') 
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    
    func_name = None
    for item in routers:  
        if url == item[0]:
            func_name = item[1]
            break
    
    if func_name:
        response = func_name()
    else:
        response = b'404 not found'  
    conn.send(response)
    conn.close()

# index2.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>"HELLO WORLD"</title> </head> <body> <table border="1"> <tr> <th>Month</th> <th>Savings</th> </tr> <tr> <td>January</td> <td>$100</td> </tr> <tr> <td>time</td> <!--用特殊符號做爲佔位符--> <td>@temp@</td> </tr> </table> </body> </html>

 

 

 

   在這個例子中,咱們修改了index.html,添加了一個佔位符,並在server.py中對這個佔位符用當前時間替換,因而在新的訪問結果中,能夠看到網頁已經顯示了最新的時間,用簡單的字符串替換咱們就實現了動態網頁的模板渲染啦!這其實就是模板渲染的本質,用於模板渲染的模塊稱之爲模板引擎,固然咱們不須要本身實現,經常使用的python模板引擎有:jinjia2。

 

 六. 結語

  介紹完基本Web框架功能的本質後,咱們來簡單聊聊Python Web框架。若是按照上面介紹的Web開發基本功能來分類,能夠分爲三類:

    • 框架包含Socket實現、路由匹配、模板引擎渲染功能:Tornado
    • 框架包含路由匹配、模板引擎渲染功能:Django(其Socket實現經過引用wsgiref模塊實現)
    • 框架包含路由匹配:Flask(其Socket實現經過引用werkzeug模塊實現,模板引擎渲染實現經過引用jinjia2模塊實現)

  固然Web框架除了以上介紹的基礎功能外還實現了不少其餘的功能,正是有了這些利器,咱們的Web開發才能駕輕就熟。

相關文章
相關標籤/搜索