web框架本質
Django基礎一之web框架的本質
django第一天
本節目錄
一 web框架的本質及自定義web框架
我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而使用者的瀏覽器就是一個socket客戶端,基於請求做出響應,客戶都先請求,服務端做出對應的響應,按照http協議的請求協議傳送請求,服務端按照http協議的響應協議來響應請求,這樣的網路通訊,我們就可以自己實現Web框架了。
通過對socket的學習,我們知道網路通訊,我們完全可以自己寫了,因為socket就是做網路通訊用的,下面我們就基於socket來自己實現一個web框架,寫一個web服務端,讓瀏覽器來請求,並通過自己的服務端把頁面返回給瀏覽器,瀏覽器渲染出我們想要的效果。在後面的學習中,大家提前準備一些檔案:
html檔案內容如下,名稱為test.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="test.css"> <link rel="icon" href="wechat.ico"> <!--直接寫在html頁面裡面的css樣式是直接可以在瀏覽器上顯示的--> <!--<style>--> <!--h1{--> <!----> <!--color: white;--> <!--}--> <!--</style>--> </head> <body> <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1> <!--直接寫在html頁面裡面的img標籤的src屬性值如果是別人網站的地址(網路地址)是直接可以在瀏覽器上顯示的--> <!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--如果都是網路地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取檔案,返回圖片檔案資訊的程式碼,因為別人的網站就做了這個事情了--> <img src="meinv.png" alt="" width="100" height="100"> <!--如果你是本地的圖片想要返回給頁面,你需要對頁面上的關於這個圖片的請求要自己做出響應,這個src就是來你本地請求這個圖片,你只要將圖片資訊讀取出來,返回給頁面,頁面拿到這個圖片的資料,就能夠渲染出來了,是不是很簡單--> <!--直接寫在html頁面裡面的js操作是直接可以在瀏覽器上顯示的--> <!--<script>--> <!--alert('這是我們第一個網頁')--> <!--</script>--> <script src="test.js"></script> </body> </html>
css檔案內容如下,名稱為test.css:
h1{ background-color: green; color: white; }
js檔案內容如下,名稱為test.js:
alert('這是我們第一個網頁');
再準備一個圖片,名稱為meinv.jpg,再準備一個ico檔案,名稱為wechat.ico,其實就是個圖片檔案,微信官網開啟之後,在瀏覽器最上面能夠看到,把它儲存下來
上面的檔案都準備好之後,你用pycharm新建一個專案,把檔案都放到一個資料夾裡面去,留著備用,像下面這個樣子:
然後開始寫我們的web框架,我們分這麼幾步來寫:
一、簡單的web框架
建立一個python檔案,內容如下,名稱為test.py:
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') #socket是應用層和傳輸層之間的抽象層,每次都有協議,協議就是訊息格式,那麼傳輸層的訊息格式我們不用管,因為socket幫我們搞定了,但是應用層的協議還是需要咱們自己遵守的,所以再給瀏覽器傳送訊息的時候,如果沒有按照應用層的訊息格式來寫,那麼你返回給瀏覽器的資訊,瀏覽器是沒法識別的。而應用層的協議就是我們的HTTP協議,所以我們按照HTTP協議規定的訊息格式來給瀏覽器返回訊息就沒有問題了,關於HTTP我們會細說,首先看一下直接寫conn.send(b'hello')的效果,然後執行程式碼,通過瀏覽器來訪問一下,然後再看這一句conn.send(b'HTTP/1.1 200 ok \r\n\r\nhello')的效果 #下面這句就是按照http協議來寫的 # conn.send(b'HTTP/1.1 200 ok \r\n\r\nhello') #上面這句還可以分成下面兩句來寫 conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(b'hello')
我們來瀏覽器上看一下瀏覽器傳送的請求:
目前我們還沒有寫如何返回一個html檔案給瀏覽器,所以這裡暫時不用管它,那麼我們點開這個127.0.0.1看看:
我們在python檔案中列印一下瀏覽器傳送過來的請求資訊是啥:
重啟我們的程式碼,然後在網址中輸入這個:
再重啟我們的程式碼,然後在網址中輸入這個:
瀏覽器發過來一堆的訊息,我們給瀏覽器回覆(響應)資訊的時候,也要按照一個訊息格式來寫,這些都是http協議規定的,那麼我們就來學習一下http協議,然後繼續完善我們的web框架:
HTTP協議:https://www.cnblogs.com/clschao/articles/9230431.html
二、返回HTML檔案的web框架
首先寫一個html檔案,內容如下,名稱為test.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title><link rel="stylesheet" href="test.css"> <!--直接寫在html頁面裡面的css樣式是直接可以在瀏覽器上顯示的--> <style> h1{ background-color: green; color: white; } </style> </head> <body> <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1> <!--直接寫在html頁面裡面的img標籤的src屬性值如果是別人網站的地址(網路地址)是直接可以在瀏覽器上顯示的--> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt=""> <!--如果都是網路地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取檔案,返回圖片檔案資訊的程式碼,因為別人的網站就做了這個事情了--> <!--直接寫在html頁面裡面的js操作是直接可以在瀏覽器上顯示的--> <script> alert('這是我們第一個網頁') </script> </body> </html>
準備我們的python程式碼,服務端程式,檔案內容如下,檔名稱為test.py:
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') print('瀏覽器請求資訊:',str_msg) # conn.send(b'HTTP/1.1 200 ok \r\ncontent-type:text/html;charset=utf-8;\r\n') conn.send(b'HTTP/1.1 200 ok \r\n\r\n') with open('test1.html','rb') as f: f_data = f.read() conn.send(f_data)
頁面上輸入網址看效果,css和js程式碼的效果也有,very good:
但是我們知道,我們的css和js基本都是寫在本地的檔案裡面的啊,而且我們的圖片基本也是我們自己本地的啊,怎麼辦,我們將上面我們提前準備好的js和css還有那個.ico結尾的圖片檔案都準備好,來我們在來一個升級版的web框架,其實css、js、圖片等檔案都叫做網站的靜態檔案。
首先我們先看一個效果,如果我們直接將我們寫好的css和js還有.ico和圖片檔案插入到我們的html頁面裡面,就是下面這個html檔案
名稱為test.html,內容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="test.css"> <!--加上下面這句,那麼我們看瀏覽器除錯視窗中的那個network裡面就沒有那個favicon.ico的請求了,其實這就是頁面title標籤文字左邊的那個頁面圖示,但是這個檔案是我們自己本地的,所以我們需要在後端程式碼裡面將這個檔案資料讀取出來返回給前端--> <link rel="icon" href="wechat.ico"> <!--直接寫在html頁面裡面的css樣式是直接可以在瀏覽器上顯示的--> <!--<style>--> <!--h1{--> <!----> <!--color: white;--> <!--}--> <!--</style>--> </head> <body> <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1> <!--直接寫在html頁面裡面的img標籤的src屬性值如果是別人網站的地址(網路地址)是直接可以在瀏覽器上顯示的--> <!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--如果都是網路地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取檔案,返回圖片檔案資訊的程式碼,因為別人的網站就做了這個事情了--> <img src="meinv.png" alt="" width="100" height="100"> <!--如果你是本地的圖片想要返回給頁面,你需要對頁面上的關於這個圖片的請求要自己做出響應,這個src就是來你本地請求這個圖片,你只要將圖片資訊讀取出來,返回給頁面,頁面拿到這個圖片的資料,就能夠渲染出來了,是不是很簡單--> <!--直接寫在html頁面裡面的js操作是直接可以在瀏覽器上顯示的--> <!--<script>--> <!--alert('這是我們第一個網頁')--> <!--</script>--> <script src="test.js"></script> </body> </html>
同樣使用我們之前的python程式,來看效果:
發現js和css的效果都沒有出來,並且我們看一下瀏覽器除錯視窗的那個network
在下來我們在network裡面點選那個test.css檔案,看看請求是什麼:
還有就是當我們直接在瀏覽器上儲存某個頁面的時候,隨便一個頁面,我們到頁面上點選右鍵另存為,然後存到本地的一個目錄下,你會發現這個頁面的html、css、js、圖片等檔案都跟著儲存下來了,我儲存了一下部落格園首頁的頁面,看,是一個資料夾和一個html檔案:
我們點開部落格園那個資料夾看看裡面都有什麼:
發現js、css還有圖片什麼的都被儲存了下來,說明什麼,說明這些檔案本身就存在瀏覽器上了,哦,原來就是將html頁面需要的css、js、圖片等檔案也傳送給瀏覽器就可以了,並且這些靜態檔案都是瀏覽器單獨過來請求的,其實和標籤的屬性有有關係,css檔案是link標籤的href屬性:<link rel="stylesheet" href="test.css">,js檔案是script標籤的src屬性:<script src="test.js"></script>,圖片檔案是img標籤的src屬性:<img src="meinv.png" alt="" width="100" height="100"> ,那個.ico檔案是link標籤的屬性:<link rel="icon" href="wechat.ico">,其實這些屬性都會在頁面載入的時候,單獨到自己對應的屬性值裡面取請求對應的檔案資料,而且我們如果在值裡面寫的都是自己本地的路徑,那麼都會來自己的本地路徑來找,如果我們寫的是相對路徑,就會到我們自己的網址+檔名稱,這個路徑來找它需要的檔案,所以我們只需要將這些請求做一些響應,將對應的檔案資料相應給瀏覽器就可以了!並且我們通過前面的檢視,能夠發現,瀏覽器url的請求路徑我們知道是什麼,靜態檔案不是也這樣請求的嗎,好,我們針對不同的路徑給它返回不同的檔案,非常好!我們來嘗試一下!
三、返回靜態檔案的高階web框架
還是用第二個web框架裡面的那個html檔案,我們只需要寫一些我們的服務端程式就可以了,同樣是test.py檔案,內容如下:
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() #首先瀏覽器相當於給我們傳送了多個請求,一個是請求我們的html檔案,而我們的html檔案裡面的引入檔案的標籤又給我們這個網站傳送了請求靜態檔案的請求,所以我們要將建立連線的過程迴圈起來,才能接受多個請求,沒毛病 while 1: conn,addr = sk.accept() # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') #通過http協議我們知道,瀏覽器請求的時候,有一個請求內容的路徑,通過對請求資訊的分析,這個路徑我們在請求的所有請求資訊中可以提煉出來,下面的path就是我們提煉出來的路徑 path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') #由於整個頁面需要html、css、js、圖片等一系列的檔案,所以我們都需要給人家瀏覽器傳送過去,瀏覽器才能有這些檔案,才能很好的渲染你的頁面 #根據不同的路徑來返回響應的內容 if path == '/': #返回html檔案 print(from_b_msg) with open('test.html','rb') as f: # with open('Python開發.html','rb') as f: data = f.read() conn.send(data) conn.close() elif path == '/meinv.png': #返回圖片 with open('meinv.png','rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() elif path == '/test.css': #返回css檔案 with open('test.css','rb') as f: css_data = f.read() conn.send(css_data) conn.close() elif path == '/wechat.ico':#返回頁面的ico圖示 with open('wechat.ico','rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() elif path == '/test.js': #返回js檔案 with open('test.js','rb') as f: js_data = f.read() conn.send(js_data) conn.close() #注意:上面每一個請求處理完之後,都有一個conn.close()是因為,HTTP協議是短連結的,一次請求對應一次響應,這個請求就結束了,所以我們需要寫上close,不然瀏覽器自己斷了,你自己寫的服務端沒有斷,就會出問題。
執行起來我們的py檔案,然後在瀏覽器訪問一下我們的服務端,看效果:
666666,完全搞定了,自己通過socket已經完全搞定了web專案,激動不,哈哈,我們再來完善一下
四:函式版高階web框架
html檔案和其他的靜態檔案還是我們上面使用的。
python程式碼如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Time : 2019/2/17 14:06 # @Author : wuchao # @Site : # @File : test.py # @Software: PyCharm import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() #處理頁面請求的函式 def func1(conn): with open('test.html', 'rb') as f: # with open('Python開發.html','rb') as f: data = f.read() conn.send(data) conn.close() #處理頁面img標籤src屬性值是本地路徑的時候的請求 def func2(conn): with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() #處理頁面link( <link rel="stylesheet" href="test.css">)標籤href屬性值是本地路徑的時候的請求 def func3(conn): with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() #處理頁面link(<link rel="icon" href="wechat.ico">)標籤href屬性值是本地路徑的時候的請求 def func4(conn): with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() #處理頁面script(<script src="test.js"></script>)標籤src屬性值是本地路徑的時候的請求 def func5(conn): with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() while 1: conn,addr = sk.accept() # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') print(from_b_msg) if path == '/': func1(conn) elif path == '/meinv.png': func2(conn) elif path == '/test.css': func3(conn) elif path == '/wechat.ico': func4(conn) elif path == '/test.js': func5(conn)
五 更高階版(多執行緒版)web框架
應用上我們併發程式設計的內容,反正html檔案和靜態檔案都直接給瀏覽器,那大家就一塊併發處理,html檔案和靜態檔案還是上面的。
python程式碼如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Time : 2019/2/17 14:06 # @Author : wuchao # @Site : # @File : test.py # @Software: PyCharm import socket from threading import Thread #注意一點,不開多執行緒完全是可以搞定的,在這裡只是教大家要有併發程式設計的思想,所以我使用了多執行緒 sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() def func1(conn): with open('test.html', 'rb') as f: # with open('Python開發.html','rb') as f: data = f.read() conn.send(data) conn.close() def func2(conn): with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() def func3(conn): with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() def func4(conn): with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5(conn): with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() while 1: conn,addr = sk.accept() # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') print(from_b_msg) if path == '/': # func1(conn) t = Thread(target=func1,args=(conn,)) t.start() elif path == '/meinv.png': # func2(conn) t = Thread(target=func2, args=(conn,)) t.start() elif path == '/test.css': # func3(conn) t = Thread(target=func3, args=(conn,)) t.start() elif path == '/wechat.ico': # func4(conn) t = Thread(target=func4, args=(conn,)) t.start() elif path == '/test.js': # func5(conn) t = Thread(target=func5, args=(conn,)) t.start()
六 更更高階版web框架
if判斷太多了,開執行緒的方式也比較噁心,有多少個if判斷,就寫多少次建立執行緒,簡化一下:
import socket from threading import Thread sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() def func1(conn): conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n') with open('test.html', 'rb') as f: # with open('Python開發.html','rb') as f: data = f.read() conn.send(data) conn.close() def func2(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() def func3(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() def func4(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() #定義一個路徑和執行函式的對應關係,不再寫一堆的if判斷了 l1 = [ ('/',func1), ('/meinv.png',func2), ('/test.css',func3), ('/wechat.ico',func4), ('/test.js',func5), ] #遍歷路徑和函式的對應關係列表,並開多執行緒高效的去執行路徑對應的函式, def fun(path,conn): for i in l1: if i[0] == path: t = Thread(target=i[1],args=(conn,)) t.start() # else: # conn.send(b'sorry') while 1: conn,addr = sk.accept() #看完這裡面的程式碼之後,你就可以思考一個問題了,很多人要同時訪問你的網站,你在請求這裡是不是可以開起併發程式設計的思想了,多程序+多執行緒+協程,妥妥的支援高併發,再配合伺服器叢集,這個網頁就支援大量的高併發了,有沒有很激動,哈哈,但是咱們寫的太low了,而且功能很差,容錯能力也很差,當然了,如果你有能力,你現在完全可以自己寫web框架了,寫一個nb的,如果現在沒有這個能力,那麼我們就來好好學學別人寫好的框架把,首先第一個就是咱們的django框架了,其實就是將這些功能封裝起來,並且容錯能力強,抗壓能力強,總之一個字:吊。 # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) # 注意:因為開啟的執行緒很快,可能導致你的檔案還沒有傳送過去,其他檔案的請求已經來了,導致你檔案資訊沒有被瀏覽器正確的認識,所以需要將傳送請求行和請求頭的部分寫道前面的每一個函式裡面去,並且防止出現瀏覽器可能不能識別你的html檔案的情況,需要在傳送html檔案的那個函式裡面的傳送請求行和請求頭的部分加上兩個請求頭content-type:text/html\r\ncharset:utf-8\r\n # conn.send(b'HTTP/1.1 200 ok\r\n\r\n') 不這樣寫了 # conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n') 不這樣寫了 print(from_b_msg) #執行這個fun函式並將路徑和conn管道都作為引數傳給他 fun(path,conn)
七 根據不同路徑返回不同頁面的web框架
既然知道了我們可以根據不同的請求路徑來返回不同的內容,那麼我們可不可以根據使用者訪問的不同路徑,返回不同的頁面啊,嗯,應該是可以的
自己建立兩個html檔案,寫幾個標籤在裡面,名為index.html和home.html,然後根據不同的路徑返回不同的頁面,我就給大家寫上python程式碼吧:
""" 根據URL中不同的路徑返回不同的內容 返回獨立的HTML頁面 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 繫結IP和埠 sk.listen() # 監聽 # 將返回不同的內容部分封裝成函式 def index(url): # 讀取index.html頁面的內容 with open("index.html", "r", encoding="utf8") as f: s = f.read() # 返回位元組資料 return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函式的對應關係 list1 = [ ("/index/", index), ("/home/", home), ] while 1: # 等待連線 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的訊息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的位元組型別的資料轉換成字串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是我們從瀏覽器發過來的訊息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因為要遵循HTTP協議,所以回覆的訊息也要加狀態行 # 根據不同的路徑返回不同內容 func = None # 定義一個儲存將要執行的函式名的變數 for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" # 返回具體的響應訊息 conn.send(response) conn.close()
八、返回動態頁面的web框架
這網頁能夠顯示出來了,但是都是靜態的啊。頁面的內容都不會變化的,我想要的是動態網站,動態網站的意思是裡面有動態變化的資料,而不是頁面裡面有動態效果,這個大家要注意啊。
沒問題,我也有辦法解決。我選擇使用字串替換來實現這個需求。(這裡使用時間戳來模擬動態的資料,還是隻給大家python程式碼吧)
""" 根據URL中不同的路徑返回不同的內容 返回HTML頁面 讓網頁動態起來 """ import socket import time sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 繫結IP和埠 sk.listen() # 監聽 # 將返回不同的內容部分封裝成函式 def index(url): with open("index.html", "r", encoding="utf8") as f: s = f.read() now = str(time.time()) s = s.replace("@@oo@@", now) # 在網頁中定義好特殊符號,用動態的資料去替換提前定義好的特殊符號 return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函式的對應關係 list1 = [ ("/index/", index), ("/home/", home), ] while 1: # 等待連線 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的訊息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的位元組型別的資料轉換成字串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是我們從瀏覽器發過來的訊息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因為要遵循HTTP協議,所以回覆的訊息也要加狀態行 # 根據不同的路徑返回不同內容 func = None # 定義一個儲存將要執行的函式名的變數 for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" # 返回具體的響應訊息 conn.send(response) conn.close()
這八個框架讓大家滿意了吧,這下子明白整個web框架的原理了吧,哈哈,但是我們寫的框架還是太low了,不夠強壯,那別人已經開發好了很多nb的框架了,如:Django、Flask、Tornado等等,我們學學怎麼用就可以啦,但是注意一個問題,我們在裡面獲取路徑的時候,我們是按照\r\n來分割然後再通過空格來分割獲取到的路徑,但是如果不是http協議的話,你自己要注意訊息格式了。
接下來我們看一個別人寫好的模組來搞的web框架,這個模組叫做wsgiref
九、wsgiref模組版web框架
wsgiref模組其實就是將整個請求資訊給封裝了起來,就不需要你自己處理了,假如它將所有請求資訊封裝成了一個叫做request的物件,那麼你直接request.path就能獲取到使用者這次請求的路徑,request.method就能獲取到本次使用者請求的請求方式(get還是post)等,那這個模組用起來,我們再寫web框架是不是就簡單了好多啊。
對於真實開發中的python web程式來說,一般會分為兩部分:伺服器程式和應用程式。
伺服器程式負責對socket伺服器進行封裝,並在請求到來時,對請求的各種資料進行整理。
應用程式則負責具體的邏輯處理。為了方便應用程式的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程式都要和伺服器程式配合,才能為使用者提供服務。
這樣,伺服器程式就需要為不同的框架提供不同的支援。這樣混亂的局面無論對於伺服器還是框架,都是不好的。對伺服器來說,需要支援各種不同框架,對框架來說,只有支援它的伺服器才能被開發出的應用使用。最簡單的Web應用就是先把HTML用檔案儲存好,用一個現成的HTTP伺服器軟體,接收使用者請求,從檔案中讀取HTML,返回。如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、傳送HTTP響應都是苦力活,如果我們自己來寫這些底層程式碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規範。
正確的做法是底層程式碼由專門的伺服器軟體實現,我們用Python專注於生成HTML文件。因為我們不希望接觸到TCP連線、HTTP原始請求和響應格式,所以,需要一個統一的介面協議來實現這樣的伺服器軟體,讓我們專心用Python編寫Web業務。
這時候,標準化就變得尤為重要。我們可以設立一個標準,只要伺服器程式支援這個標準,框架也支援這個標準,那麼他們就可以配合使用。一旦標準確定,雙方各自實現。這樣,伺服器可以支援更多支援標準的框架,框架也可以使用更多支援標準的伺服器。
WSGI(Web Server Gateway Interface)就是一種規範,它定義了使用Python編寫的web應用程式與web伺服器程式之間的介面格式,實現web應用程式與web伺服器程式間的解耦。
常用的WSGI伺服器有uwsgi、Gunicorn。而Python標準庫提供的獨立WSGI伺服器叫wsgiref,Django開發環境用的就是這個模組來做伺服器。
好,接下來我們就看一下(能理解就行,瞭解就可以了):先看看wsfiref怎麼使用
from wsgiref.simple_server import make_server # wsgiref本身就是個web框架,提供了一些固定的功能(請求和響應資訊的封裝,不需要我們自己寫原生的socket了也不需要咱們自己來完成請求資訊的提取了,提取起來很方便) #函式名字隨便起 def application(environ, start_response): ''' :param environ: 是全部加工好的請求資訊,加工成了一個字典,通過字典取值的方式就能拿到很多你想要拿到的資訊 :param start_response: 幫你封裝響應資訊的(響應行和響應頭),注意下面的引數 :return: ''' start_response('200 OK', [('k1','v1'),]) print(environ) print(environ['PATH_INFO']) #輸入地址127.0.0.1:8000,這個列印的是'/',輸入的是127.0.0.1:8000/index,列印結果是'/index' return [b'<h1>Hello, web!</h1>'] #和咱們學的socketserver那個模組很像啊 httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 開始監聽HTTP請求: httpd.serve_forever()
來一個完整的web專案,使用者登入認證的專案,我們需要連線資料庫了,所以先到mysql資料庫裡面準備一些表和資料
mysql> create database db1; Query OK, 1 row affected (0.00 sec) mysql> use db1; Database changed mysql> create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); Query OK, 0 rows affected (0.23 sec) mysql> insert into userinfo(username,password) values('chao','666'),('sb1','222'); Query OK, 2 rows affected (0.03 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from userinfo; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | chao | 666 | | 2 | sb1 | 222 | +----+----------+----------+ 2 rows in set (0.00 sec)
然後再建立這麼幾個檔案:
python檔名稱webmodel.py,內容如下:
#建立表,插入資料 def createtable(): import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='666', database='db1', charset='utf8' ) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = ''' -- 建立表 create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); -- 插入資料 insert into userinfo(username,password) values('chao','666'),('sb1','222'); ''' cursor.execute(sql) conn.commit() cursor.close() conn.close()
python的名為webauth檔案,內容如下:
#對使用者名稱和密碼進行驗證 def auth(username,password): import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123', database='db1', charset='utf8' ) print('userinfo',username,password) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = 'select * from userinfo where username=%s and password=%s;' res = cursor.execute(sql, [username, password]) if res: return True else: return False
使用者輸入使用者名稱和密碼的檔案,名為web.html,內容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--如果form表單裡面的action什麼值也沒給,預設是往當前頁面的url上提交你的資料,所以我們可以自己指定資料的提交路徑--> <!--<form action="http://127.0.0.1:8080/auth/" method="post">--> <form action="http://127.0.0.1:8080/auth/" method="get"> 使用者名稱<input type="text" name="username"> 密碼 <input type="password" name="password"> <input type="submit"> </form> <script> </script> </body> </html>
使用者驗證成功後跳轉的頁面,顯示成功,名為websuccess.html,內容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> h1{ color:red; } </style> </head> <body> <h1>寶貝兒,恭喜你登陸成功啦</h1> </body> </html>
python服務端程式碼(主邏輯程式碼),名為web_python.py:
from urllib.parse import parse_qs from wsgiref.simple_server import make_server import webauth def application(environ, start_response): # start_response('200 OK', [('Content-Type', 'text/html'),('k1','v1')]) # start_response('200 OK', [('Content-Type', 'text/html'),('charset','utf-8')]) start_response('200 OK', [('Content-Type', 'text/html')]) print(environ) print(environ['PATH_INFO']) path = environ['PATH_INFO'] #使用者獲取login頁面的請求路徑 if path == '/login': with open('web.html','rb') as f: data = f.read() #針對form表單提交的auth路徑,進行對應的邏輯處理 elif path == '/auth/': #登陸認證 #1.獲取使用者輸入的使用者名稱和密碼 #2.去資料庫做資料的校驗,檢視使用者提交的是否合法 # user_information = environ[''] if environ.get("REQUEST_METHOD") == "POST": #獲取請求體資料的長度,因為提交過來的資料需要用它來提取,注意POST請求和GET請求的獲取資料的方式不同 try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 #POST請求獲取資料的方式 request_data = environ['wsgi.input'].read(request_body_size) print('>>>>>',request_data) # >>>>> b'username=chao&password=123',是個bytes型別資料 print('?????',environ['QUERY_STRING']) #????? 空的,因為post請求只能按照上面這種方式取資料 #parse_qs可以幫我們解析資料 re_data = parse_qs(request_data) print('拆解後的資料',re_data) #拆解後的資料 {b'password': [b'123'], b'username': [b'chao']}
#post請求的返回資料我就不寫啦
pass
if environ.get("REQUEST_METHOD") == "GET": #GET請求獲取資料的方式,只能按照這種方式取 print('?????',environ['QUERY_STRING']) #????? username=chao&password=123,是個字串型別資料 request_data = environ['QUERY_STRING'] # parse_qs可以幫我們解析資料 re_data = parse_qs(request_data) print('拆解後的資料', re_data) #拆解後的資料 {'password': ['123'], 'username': ['chao']} username = re_data['username'][0] password = re_data['password'][0] print(username,password) #進行驗證: status = webauth.auth(username,password) if status: # 3.將相應內容返回 with open('websuccess.html','rb') as f: data = f.read() else: data = b'auth error' # 但是不管是post還是get請求都不能直接拿到資料,拿到的資料還需要我們來進行分解提取,所以我們引入urllib模組來幫我們分解 #注意昂,我們如果直接返回中文,沒有給瀏覽器指定編碼格式,預設是gbk,所以我們需要gbk來編碼一下,瀏覽器才能識別 # data='登陸成功!'.encode('gbk') else: data = b'sorry 404!,not found the page' return [data] #和咱們學的socketserver那個模組很像啊 httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 開始監聽HTTP請求: httpd.serve_forever()
把程式碼拷走,建立檔案,放到同一個目錄下,執行一下we_python.py檔案的程式碼就能看到效果,注意先輸入的網址是127.0.0.1:8080/login ,還要注意你的mysql資料庫沒有問題。
十、起飛版web框架
我們上一個web框架把所有的程式碼都寫在了一個py檔案中,我們拆到其他檔案裡面去,並且針對不用的路徑來進行分發請求的時候都用的if判斷,很多值得優化的地方,好,結合我們前面幾個版本的優勢我們來優化一下,分幾個檔案和資料夾
程式碼就不在部落格上都列出來了,我打包放到百度雲上了,大家去下載看看把:https://pan.baidu.com/s/1Ns5QHFpZGusGHuHzrCto3A
將來要說的MVC框架是什麼呢:
M:model.py 就是和資料庫打交道用的,建立表等操作
V:View 檢視(檢視函式,html檔案)
C:controller 控制器(其實就是我百度雲程式碼裡面那個urls檔案裡面的內容,url(路徑)分發與檢視函式的邏輯處理)
Django叫做MTV框架
M:model.py 就是和資料庫打交道用的,建立表等操作(和上面一樣)
T:templates 存放HTML檔案的
V:View 檢視函式(邏輯處理)
其實你會發現MTV比MVC少一個url分發的部分
所以我們學的django還要學一個叫做url控制器(路徑分發)的東西,MTV+url控制器就是我們django要學的內容。
捋一下框架的整個流程吧~~~
二 模板渲染JinJa2
上面的程式碼實現了一個簡單的動態頁面(字串替換),我完全可以從資料庫中查詢資料,然後去替換我html中的對應內容(專業名詞叫做模板渲染,你先渲染一下,再給瀏覽器進行渲染),然後再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染資料。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的資料。 我這裡用的特殊符號是我定義的,其實模板渲染有個現成的工具:jinja2
下載:
pip install jinja2
來一個html檔案,index2,html,內容如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h1>姓名:{{name}}</h1> <h1>愛好:</h1> <ul> {% for hobby in hobby_list %} <li>{{hobby}}</li> {% endfor %} </ul> </body> </html>
使用jinja2渲染index2.html檔案,建立一個python檔案,程式碼如下:
from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("index2.html", "r",encoding='utf-8') as f: data = f.read() template = Template(data) # 生成模板檔案 ret = template.render({"name": "于謙", "hobby_list": ["燙頭", "泡吧"]}) # 把資料填充到模板裡面 return [bytes(ret, encoding="utf8"), ] # 定義一個url和函式的對應關係 URL_LIST = [ ("/index/", index), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設定HTTP響應的狀態碼和頭資訊 url = environ['PATH_INFO'] # 取到使用者輸入的url func = None # 將要執行的函式 for i in URL_LIST: if i[0] == url: func = i[1] # 去之前定義好的url列表裡找url應該執行的函式 break if func: # 如果能找到要執行的函式 return func() # 返回函式的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
現在的資料是我們自己手寫的,那可不可以從資料庫中查詢資料,來填充頁面呢?
使用pymysql連線資料庫:
conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8") cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select name, age, department_id from userinfo") user_list = cursor.fetchall() cursor.close() conn.close()
建立一個測試的user表:
CREATE TABLE user( id int auto_increment PRIMARY KEY, name CHAR(10) NOT NULL, hobby CHAR(20) NOT NULL )engine=innodb DEFAULT charset=UTF8;
模板的原理就是字串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
三 MVC和MTV框架
MVC
Web伺服器開發領域裡著名的MVC模式,所謂MVC就是把Web應用分為模型(M),控制器(C)和檢視(V)三層,他們之間以一種外掛式的、鬆耦合的方式連線在一起,模型負責業務物件與資料庫的對映(ORM),檢視負責與使用者的互動(頁面),控制器接受使用者的輸入呼叫模型和檢視完成使用者的請求,其示意圖如下所示:
MTV
Django的MTV模式本質上和MVC是一樣的,也是為了各元件間保持鬆耦合關係,只是定義上有些許不同,Django的MTV分別是值:
- M 代表模型(Model):負責業務物件和資料庫的關係對映(ORM)。
- T 代表模板 (Template):負責如何把頁面展示給使用者(html)。
- V 代表檢視(View): 負責業務邏輯,並在適當時候呼叫Model和Template。
除了以上三層之外,還需要一個URL分發器,它的作用是將一個個URL的頁面請求分發給不同的View處理,View再呼叫相應的Model和Template,MTV的響應模式如下所示:
一般是使用者通過瀏覽器向我們的伺服器發起一個請求(request),這個請求回去訪問檢視函式,(如果不涉及到資料呼叫,那麼這個時候檢視函式返回一個模板也就是一個網頁給使用者),檢視函式呼叫模型,模型去資料庫查詢資料,然後逐級返回,檢視函式把返回的資料填充到模板中空格中,最後返回網頁給使用者。
四 Django下載安裝
1、下載Django:
1 |
pip3 install django==1.11.9
|
2、建立一個django project
1 |
django - admin startproject mysite 建立了一個名為"mysite"的Django 專案:
|
當前目錄下會生成mysite的工程,目錄結構如下:(大家注意昂,pip下載下來的django你就理解成一個模組,而不是django專案,這個模組可以幫我們建立django專案)
- manage.py-----Django專案裡面的工具,通過它可以呼叫djangoshell和資料庫,啟動關閉專案與專案互動等,不管你將框架分了幾個檔案,必然有一個啟動檔案,其實他們本身就是一個檔案。
- settings.py----包含了專案的預設設定,包括資料庫資訊,除錯標誌以及其他一些工作的變數。
- urls.py-----負責把URL模式對映到應用程式。
- wsgi.py ---- runserver命令就使用wsgiref模組做簡單的web server,後面會看到renserver命令,所有與socket相關的內容都在這個檔案裡面了,目前不需要關注它。
python manage.py runserver 127.0.0.1:8080 #此時已經可以啟動django專案了,只不過什麼邏輯也沒有呢
你會發現,上面沒有什麼view檢視函式的檔案啊,這裡我們說一個應用與專案的關係,上面我們只是建立了一個專案,並沒有建立應用,以微信來舉例,微信是不是一個大的專案,但是微信裡面是不是有很多個應用,支付應用、聊天應用、朋友圈、小程式等這些在一定程度上都是相互獨立的應用,也就是說一個大的專案裡面可以有多個應用,也就是說專案是包含應用的,它沒有將view放到這個專案目錄裡面是因為它覺得,一個專案裡面可以有多個應用,而每個應用都有自己這個應用的邏輯內容,所以他覺得這個view應該放到應用裡面,比如說我們的微信,剛才說了幾個應用,這幾個應用的邏輯能放到一起嗎,放到一起是不是就亂套啦,也不好管理和維護,所以這些應用的邏輯都分開來放,它就幫我們提煉出來了,提煉出來一個叫做應用的東西,所以我們需要來建立這個應用。
3、在mysite目錄下建立應用
1 |
python manage.py startapp blog #通過執行manage.py檔案來建立應用,執行這句話一定要注意,你應該在這個manage.py的檔案所在目錄下執行這句話,因為其他目錄裡面沒有這個檔案
python manage.py startapp blog2 #每個應用都有自己的目錄,每個應用的目錄下都有自己的views.py檢視函式和models.py資料庫操作相關的檔案
|
我們現在只需要看其中兩個檔案
models.py :之前我們寫的那個名為model的檔案就是建立表用的,這個檔案就是存放與該app(應用)相關的表結構的
views.py :存放與該app相關的檢視函式的
4、啟動django專案
1 |
python manage.py runserver 8080 # python manage.py runserver 127.0.0.1:8080,本機就不用寫ip地址了 如果連埠都沒寫,預設是本機的8000埠
|
這樣我們的django就啟動起來了!當我們訪問:http://127.0.0.1:8080/時就可以看到:
學習Django,我們就學上面的這些檔案,怎麼在MTV+url分發的功能下來使用。
最後我們說一下,其實我們將來建立django專案,很少用命令行了,就用pycharm來建立,怎麼建立呢?看圖:
看專案目錄:
五 基於Django實現一個簡單的示例
現在實現一個使用者輸入一個timer路徑,返回一個含有當前時間的頁面,想想怎麼做?使用者輸入網址-->路徑-->函式-->返回資料(檔案)
url控制器(第一步就找它)
django 1.11.9版本的url寫法:
from django.conf.urls import url
from django.contrib import admin
from crm import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
]
下面是django2.x版本的url寫法,不太一樣了,但是相容1.x的,不過我們現在還是主要說1.xx版本的,所以寫url的時候按照上的方式寫。
from django.contrib import admin from django.urls import path #找對應的函式,是哪個app裡面的函式 from app01 import views urlpatterns = [ path('admin/', admin.site.urls), #這個先不用管,後面會學 path('index/',views.index), ]
檢視
from django.shortcuts import render,HttpResponse # Create your views here. #邏輯和返回資料 def index(request): import datetime now=datetime.datetime.now() ctime=now.strftime("%Y-%m-%d %X") #return HttpResponse('哈哈,好玩嗎?') return render(request,"index.html",{"ctime":ctime}) #render,渲染html頁面檔案並返回給瀏覽器
模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h4>當前時間:{{ ctime }}</h4> </body> </html>
通過pycharm來執行專案:
看控制檯:
執行效果如下:
有同學說:我想自己配置啟動的埠怎麼搞啊:
還有一點說一下昂,在settings配置檔案裡面有關於templates(放html檔案的配置):
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] #有些版本的django沒有寫這個,自己寫一下,就是配置一個django找html檔案的路徑,render方法就來這裡找html檔案 , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
關於請求和響應的請求資訊和相應資訊的設定還需要你自己寫嗎?之前我們用wsgiref是不是還寫來著,現在都不需要寫了,簡單不。
還有一點:post請求的時候你會發現一個 Forbidden的錯誤:
現在只需要做一步,在settings配置檔案裡面將這一行註釋掉,這是django給你加的一個csrf的認證,現在不需要,我們會講的
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
還記得django寫檢視函式的時候,有一個引數是必須要給的嗎,叫做request,如果你是post請求,那麼就用request.POST,就能拿到post請求提交過來的所有資料(一個字典,然後再通過字典取值request.POST.get('username'),取出來的就是個字串,你在那個字典裡面看到的是{'username':['chao']},雖然看著是列表,但是request.POST.get('username')取出來的就是個字串),通過request.GET就能拿到提交過來的所有資料,而且記著,每一個檢視函式都要給人家返回一些內容,用render或者HttpResponse等,其實render裡面也是通過HttpResponse來返回內容,不然會報錯,錯誤是告訴你沒有返回任何內容:
django認識了,以後我們就按照下面的步驟來學:
1.django的url控制器
2.django的檢視
3.django的模板(template)
4.ORM(花的時間比較久)
作業好了,我們之前寫的那個登陸認證示例,加到django裡面來吧!
好文要頂關注我收藏該文 cls超
關注 - 2
粉絲 - 718 +加關注 11 0 posted @2019-02-17 17:12cls超 閱讀(6855) 評論(2)編輯收藏
評論 回覆引用 #1樓2019-05-15 18:09|江湖乄夜雨 讀過的最良心的web框架基礎講解的部落格了! 支援(1)反對(0) 回覆引用 #2樓2019-05-16 23:04|ryxiong728 太詳細了,給超哥打call 支援(1)反對(0) 重新整理評論重新整理頁面返回頂部 發表評論 編輯預覽
[Ctrl+Enter快捷鍵提交]
【推薦】News: 大型組態、工控、模擬、CADGIS 50萬行VC++原始碼免費下載【推薦】有你助力,更好為你——部落格園使用者消費觀調查,附帶小驚喜!
【推薦】了不起的開發者,擋不住的華為,園子裡的品牌專區
【福利】AWS攜手部落格園為開發者送免費套餐+50元京東E卡
【推薦】未知數的距離,毫秒間的傳遞,聲網與你實時互動
【推薦】部落格園x示說網,AI實戰系列公開課第三期 相關博文:
·web框架—django基礎
·django之Web框架原理
·Django之Web框架原理
·pythonweb開發Django連線mysql
·DjangoWeb開發學習筆記
»更多推薦... 最新 IT 新聞:
·這位“華為天才少年”,竟然要我用“充電寶”打《只狼》
·網傳年租金3千萬!威馬搬離北京新天地 恆大汽車入場
·劉強東豪擲7億美元瘋狂搶“菜”
·藝術家眼中的《賽博朋克2077》:這畫質比電影還夢幻
·傳多家供應商禁止給社群團購平臺供貨
»更多新聞... 暱稱:cls超
園齡:4年5個月
粉絲:718
關注:2 +加關注
搜尋
常用連結
我的標籤
- python 學習之旅(16)
- mysql學習(6)
- powerdesigner學習(3)
- 前端學習(3)
- 網路傳輸協議(1)
- linux運維學習(1)
- git學習(1)
- python-learning(1)
- python-teaching(1)
- redis學習(1)
積分與排名
- 積分 - 215253
- 排名 - 2932
最新評論
- 1. Re:Python之程序
-
八 程序池和mutiprocess.Poll-->multiprocessing.pool
- 2. Re:前端學習目錄
- @Word馬 收到,麼麼噠...
- 3. Re:前端學習目錄
-
回來看看chao sir(cls)感恩!
- 4. Re:前端CSS
-
部落格可以做個目錄呀,這樣比較合適呢
- 5. Re:Django學習目錄
- @muguangrui 陳老師是誰~~...
Powered by .NET 5.0.1-servicing.20575.16 on Kubernetes