Django基礎一之web框架的本質
一 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="icon" href="wechat.ico">
<!--直接寫在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="">--> <!--如果都是網絡地址,那么只要你的電腦有網,就可以看到,不需要自己在后端寫對應的讀取文件,返回圖片文件信息的代碼,因為別人的網站就做了這個事情了-->
<img src="meinv.png" alt="" width="100" height="100"> <!--如果你是本地的圖片想要返回給頁面,你需要對頁面上的關於這個圖片的請求要自己做出響應,這個src就是來你本地請求這個圖片,你只要將圖片信息讀取出來,返回給頁面,頁面拿到這個圖片的數據,就能夠渲染出來了,是不是很簡單-->
<!--直接寫在html頁面里面的js操作是直接可以在瀏覽器上顯示的-->
<script>
alert('這是我們第一個網頁')
</script>
<script src="test.js"></script>
</body>
</html>
<!--再准備一個圖片,名稱為meinv.jpg,再准備一個ico文件,名稱為wechat.ico,其實就是個圖片文件,微信官網打開之后,在瀏覽器最上面能夠看到-->
然后開始寫我們的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')
with open('test.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文件
<!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">
</head>
<body>
<h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>
<img src="meinv.png" alt="" width="100" height="100">
<script src="test.js"></script>
</body>
</html>
<!--css文件內容如下,名稱為test.css: -->
h1{
background-color: green;
color: white;
}
<!-- js文件內容如下,名稱為test.js:-->
alert('這是我們第一個網頁');
二、返回靜態文件的高級web框架
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文件,然后在瀏覽器訪問一下我們的服務端,看效果
完全搞定了,自己通過socket已經完全搞定了web項目,激動不,哈哈,我們再來完善一下
三、更高級版(函數+多線程版+動態獲取時間戳)web框架
# -*- coding: utf-8 -*-
# @Time : 2019/7/12 17:11
# @Author : AnWen
import time
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 9000))
server.listen()
def html(conn):
time_msg=str(time.time())
with open('test.html', 'r',encoding='utf-8') as f:
date = f.read()
# 在網頁中定義好特殊符號,用動態的數據去替換提前定義好的特殊符號
date=date.replace('%這是被替換字符串%',time_msg)
date=date.encode('utf-8')
conn.send(date)
conn.close()
def css(conn):
with open('test.css', 'rb') as f:
date = f.read()
conn.send(date)
conn.close()
def js(conn):
with open('test.js', 'rb') as f:
date = f.read()
conn.send(date)
conn.close()
def ico(conn):
with open('wechat.ico', 'rb') as f:
date = f.read()
conn.send(date)
conn.close()
def jpg(conn):
with open('window.jpg', 'rb') as f:
date = f.read()
conn.send(date)
conn.close()
#定義一個路徑和執行函數的對應關系,不再寫一堆的if判斷了
urlpatterns = [
('/', html),
('/test.css', css),
('/test.js', js),
('/wechat.ico', ico),
('/window.jpg', jpg)
]
while True:
conn, addr = server.accept()
request_str = conn.recv(1024).decode('utf-8')
path = request_str.split('\r\n')[0].split(' ')[1]
print(path)
# 因為要遵循HTTP協議,所以回復的消息也要加狀態行
conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
#遍歷路徑和函數的對應關系列表,並開多線程高效的去執行路徑對應的函數,
for item in urlpatterns:
if path==item[0]:
t=Thread(target=item[1],args=(conn,))
t.start()
四、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:8080,這個打印的是'/',輸入的是127.0.0.1:8080/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數據庫里面准備一些表和數據
# -*- coding: utf-8 -*-
# @Time : 2019/7/12 18:33
# @Author : AnWen
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='day53',
charset='utf8',
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
#創建表
sql = "create table userinfo(id int primary key auto_increment,name char(12),age int not null);"
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='day53',
charset='utf8',
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
#插入數據
sql = "insert into userinfo(name,age) values ('anwen',20);"
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
# -*- coding: utf-8 -*-
# @Time : 2019/7/12 18:55
# @Author : AnWen
import pymysql
def showdata():
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='day53',
charset='utf8',
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
#查詢數據
sql = 'select * from userinfo'
cursor.execute(sql)
data=cursor.fetchone()
conn.close()
conn.cursor()
return data
# showdata()
wsgiref模塊版web框架
# -*- coding: utf-8 -*-
# @Time : 2019/7/12 12:17
# @Author : AnWen
import time
from wsgiref.simple_server import make_server
from showdata import showdata
def html():
#獲取數據庫數據
userinfo_data=showdata()
# {'id': 1, 'name': 'anwen', 'age': 20}
with open('test.html', 'r', encoding='utf-8') as f:
date = f.read()
date = date.replace('%這是被替換字符串%', userinfo_data['name'])
date = date.encode('utf-8')
return date
def css():
with open('test.css', 'rb') as f:
date = f.read()
return date
def js():
with open('test.js', 'rb') as f:
date = f.read()
return date
def ico():
with open('wechat.ico', 'rb') as f:
date = f.read()
return date
def jpg():
with open('window.jpg', 'rb') as f:
date = f.read()
return date
urlpatterns = [
('/', html),
('/test.css', css),
('/test.js', js),
('/wechat.ico', ico),
('/window.jpg', jpg)
]
def application(environ, start_response):
'''
:param environ: 是全部加工好的請求信息,加工成了一個字典,通過字典取值的方式就能拿到很多你想要拿到的信息
:param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數
:return:
'''
# print(environ)
start_response('200 ok', [('k1', 'v1')])
path = environ['PATH_INFO']
for item in urlpatterns:
if path == item[0]:
ret = item[1]()
break
else:
ret = '404 not found!'
return [ret]
httpd = make_server('127.0.0.1', 9000, application)
print('Serving HTTP on port 9000...')
# 開始監聽HTTP請求:
httpd.serve_forever()
# wsgiref本身就是個web框架,提供了一些固定的功能(請求和響應信息的封裝,不需要我們自己寫原生的socket了也不需要咱們自己來完成請求信息的提取了,提取起來很方便)
模板渲染JinJa2
上面的代碼實現了一個簡單的動態頁面(字符串替換),我完全可以從數據庫中查詢數據,然后去替換我html中的對應內容(專業名詞叫做模板渲染,你先渲染一下,再給瀏覽器進行渲染),然后再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這里用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2
下載:pip install jinja2
使用jinja2渲染index2.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>第一個Web框架</title>
<link rel="stylesheet" href="test.css">
<link rel="icon" href="wechat.ico">
</head>
<body>
<h1>%這是被替換字符串%</h1>
<h1>{{userinfo}}</h1>
<!--<ul>-->
<!-- {% for k,v in userinfo.items() %}-->
<!-- <li>{{k}}--{{v}}</li>-->
<!-- {% endfor %}-->
<!--</ul>-->
<ul>
{% for k,v in userinfo.items() %}
<li>{{v}}</li>
{% endfor %}
</ul>
<h1>嘻嘻~~</h1>
<img src="window.jpg" alt="" width="100px" height="100px">
<script src="test.js"></script>
</body>
</html>
# -*- coding: utf-8 -*-
# @Time : 2019/7/12 12:17
# @Author : AnWen
import time
from wsgiref.simple_server import make_server
from showdata import showdata
from jinja2 import Template
def html():
userinfo_data=showdata()
with open('6jinja2和wsgiref動態框架.html', 'r', encoding='utf-8') as f:
date = f.read()
# print(date)
tem=Template(date) ## 生成模板文件
print(userinfo_data) #{'id': 1, 'name': 'anwen', 'age': 20}
date=tem.render({'userinfo':userinfo_data})
#模板的原理就是字符串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
date = date.encode('utf-8')
return date
def css():
with open('test.css', 'rb') as f:
date = f.read()
return date
def js():
with open('test.js', 'rb') as f:
date = f.read()
return date
def ico():
with open('wechat.ico', 'rb') as f:
date = f.read()
return date
def jpg():
with open('window.jpg', 'rb') as f:
date = f.read()
return date
urlpatterns = [
('/', html),
('/test.css', css),
('/test.js', js),
('/wechat.ico', ico),
('/window.jpg', jpg)
]
def application(environ, start_response):
'''
:param environ: 是全部加工好的請求信息,加工成了一個字典,通過字典取值的方式就能拿到很多你想要拿到的信息
:param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數
:return:
'''
# print(environ)
## 設置HTTP響應的狀態碼和頭信息
start_response('200 ok', [('k1', 'v1')])
path = environ['PATH_INFO']
for item in urlpatterns:
if path == item[0]:
ret = item[1]()
break
else:
ret = '404 not found!'
return [ret]
httpd = make_server('127.0.0.1', 9000, application)
print('Serving HTTP on port 9000...')
httpd.serve_forever()