Django基礎一之web框架的本質
django第一天css
本節目錄html
咱們能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端,基於請求作出響應,客戶都先請求,服務端作出對應的響應,按照http協議的請求協議發送請求,服務端按照http協議的響應協議來響應請求,這樣的網絡通訊,咱們就能夠本身實現Web框架了。前端
經過對socket的學習,咱們知道網絡通訊,咱們徹底能夠本身寫了,由於socket就是作網絡通訊用的,下面咱們就基於socket來本身實現一個web框架,寫一個web服務端,讓瀏覽器來請求,並經過本身的服務端把頁面返回給瀏覽器,瀏覽器渲染出咱們想要的效果。在後面的學習中,你們提早準備一些文件:python
html文件內容以下,名稱爲test.html: mysql
<!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: web
h1{ background-color: green; color: white; }
js文件內容以下,名稱爲test.js:sql
alert('這是咱們第一個網頁');
再準備一個圖片,名稱爲meinv.jpg,再準備一個ico文件,名稱爲wechat.ico,其實就是個圖片文件,微信官網打開以後,在瀏覽器最上面可以看到,把它保存下來shell
上面的文件都準備好以後,你用pycharm新建一個項目,把文件都放到一個文件夾裏面去,留着備用,像下面這個樣子:數據庫
而後開始寫咱們的web框架,咱們分這麼幾步來寫:django
1、簡單的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
2、返回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的請求路徑咱們知道是什麼,靜態文件不是也這樣請求的嗎,好,咱們針對不一樣的路徑給它返回不一樣的文件,很是好!咱們來嘗試一下!
3、返回靜態文件的高級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()
8、返回動態頁面的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
9、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', [('Content-Type', 'text/html'),('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數據庫沒有問題。
10、起飛版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要學的內容。
捋一下框架的整個流程吧~~~
上面的代碼實現了一個簡單的動態頁面(字符串替換),我徹底能夠從數據庫中查詢數據,而後去替換我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
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
|
pip3 install django==1.11.9
|
二、建立一個django project
1
|
django
-
admin startproject mysite 建立了一個名爲"mysite"的Django 項目:
|
當前目錄下會生成mysite的工程,目錄結構以下:(你們注意昂,pip下載下來的django你就理解成一個模塊,而不是django項目,這個模塊能夠幫咱們建立django項目)
- manage.py ----- Django項目裏面的工具,經過它能夠調用django shell和數據庫,啓動關閉項目與項目交互等,無論你將框架分了幾個文件,必然有一個啓動文件,其實他們自己就是一個文件。
- 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應該放到應用裏面,好比說咱們的微信,剛纔說了幾個應用,這幾個應用的邏輯能放到一塊兒嗎,放到一塊兒是否是就亂套啦,也很差管理和維護,因此這些應用的邏輯都分開來放,它就幫咱們提煉出來了,提煉出來一個叫作應用的東西,因此咱們須要來建立這個應用。
三、在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相關的視圖函數的
四、啓動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來建立,怎麼建立呢?看圖:
看項目目錄:
如今實現一個用戶輸入一個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裏面來吧!