前言:瞭解一件事情本質的那一瞬間總能讓我得到巨大的愉悅感,但願這篇文章也能幫助到您。html
目的:本文主要簡單介紹Web開發中三大基本功能:Socket實現、路由系統、模板引擎渲染。python
進入正題。編程
Web開發中最基礎的三大功能分別是:瀏覽器
下面將對這三部分一一說明。服務器
既然幾乎全部的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服務就順利完成啦!框架
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開發基本功能來分類,能夠分爲三類:
固然Web框架除了以上介紹的基礎功能外還實現了不少其餘的功能,正是有了這些利器,咱們的Web開發才能駕輕就熟。