Web框架原理
我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣我們就可以自己實現Web框架了。
先寫一個
原始的web框架
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
while True:
conn, addr = sk.accept()
data = conn.recv(1024)
print(data) # 打印瀏覽器發過來的消息並分析
conn.send(b'ok')
conn.close()
可以說Web服務本質上都是在這幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。
用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什么數據?怎么發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩么?
所以,必須有一個統一的規則,讓大家發送消息、接收消息的時候有個格式依據,不能隨便寫。
這個規則就是HTTP協議,以后瀏覽器發送請求信息也好,服務器回復響應信息也罷,都要按照這個規則來。
HTTP協議主要規定了客戶端和服務器之間的通信格式,那HTTP協議是怎么規定消息格式的呢?
運行上面的代碼,在瀏覽器輸入服務器的地址和端口
得到瀏覽器發過來的data的打印結果:
# data結果
'''
1.請求首行:
b'GET / HTTP/1.1\r\n
2.請求體:一大堆K:V鍵值對
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 chrome-extension\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Sec-Fetch-Site: cross-site\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7\r\n
\r\n
3.請求體:
這里是請求數據,get請求沒有,post請求才有
'''
我們發現收發的消息需要按照一定的格式來,
1.數據格式
get請求格式
請求首行(請求方式,協議版本。。。)
請求頭(一大堆k:v鍵值對)
\r\n
請求體(真正的數據 發post請求的時候才有 如果是get請求不會有)
響應格式
響應首行
響應頭
\r\n
響應體
HTTP GET請求的格式:
HTTP響應的格式:
這里就需要了解一下HTTP協議了。
HTTP協議對收發消息的格式要求
每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type
表明響應的內容格式。如 text/html
表示HTML網頁。
HTTP協議特點:
超文本傳輸協議
1.四大特性
1.基於TCP/IP之上作用於應用層
2.基於請求響應
3.無狀態 cookie session token...
4.無連接
2.響應狀態碼
用特定的數字表示一些意思
1XX:服務端已經成功接收到了你的數據 正在處理 你可以繼續提交其他數據
2XX:服務端成功響應(200請求成功)
3XX:重定向
4XX:請求錯誤(404 請求資源不存在 403 拒絕訪問)
5XX:服務器內部錯誤(500 )
自定義web框架完整版
如果我們想要自己寫的web server服務端真正運行起來,達到一種請求與響應的對應關系,我們必須要在sercer服務端給客戶端回復消息的時候,按照HTTP協議的規則加上響應狀態行 ,就是 協議版本+狀態碼+狀態描述符:b'HTTP/1.1 200 OK\r\n\r\n'
如下例子:
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
while True:
conn, addr = sk.accept()
data = conn.recv(1024)
print(data)
# 需要向客服端發送響應頭,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(b'hello world')
conn.close()
根據不同的路徑返回不同的內容的Web服務端
如果我們在瀏覽器客戶端輸入:http://127.0.0.1:8080/home
,瀏覽器的頁面顯示就為home,那么可以這樣做:
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽
while True:
# 等待連接
conn, addr = sk.accept()
# 接收客戶端返回的信息
data = conn.recv(1024)
# print(data)
# 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 請求首行的進行切割拿到url url是我們從瀏覽器發來的消息分離出來的訪問路徑
url=data1.split(' ')[1]
# print(url)
# 必須遵循HTTP協議,需要向客服端發送狀態行,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 根據不同的路徑返回不同內容
if url == '/index':
response = b'index'
elif url == '/home':
response = b'home'
else:
response = b'404 not found!!!'
conn.send(response)
conn.close()
不同路徑不同內容-函數版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽
# 將返回不同的內容部分封裝成函數
def index(url):
res = f'這是{url}頁面|'
return bytes(res,encoding='gbk')
# return res.encode('utf-8')
def home(url):
res = f'這是{url}頁面|'
return bytes(res,encoding='gbk')
# return res.encode('utf-8')
while True:
# 等待連接
conn, addr = sk.accept()
# 接收客戶端返回的信息
data = conn.recv(1024)
# print(data)
# 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 請求首行的進行切割拿到url url是我們從瀏覽器發來的消息分離出來的訪問路徑
url=data1.split(' ')[1]
# print(url)
# 根據不同的路徑返回不同內容
if url == '/index':
response = index(url)
elif url == '/home':
response = home(url)
else:
response = b'404 not found!!!'
# 必須遵循HTTP協議,需要向客服端發送狀態行,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
不同路徑不同內容-函數進階版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽
# 定義一個url和實際要執行的函數的對應關系
def home(url):
res = bytes(url, encoding='utf8')
return res
def index(url):
res = bytes(url, encoding='utf8')
return res
# 定義一個url和要執行函數對應關系的字典
dt = {
'/index':index,
'/home':home
}
while True:
# 等待連接
conn, addr = sk.accept()
# 接收客戶端返回的信息
data = conn.recv(1024)
# print(data)
# 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 請求首行的進行切割拿到url url是我們從瀏覽器發來的消息分離出來的訪問路徑
url=data1.split(' ')[1]
# print(url)
func = None
# 根據不同的路徑返回不同內容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必須遵循HTTP協議,需要向客服端發送狀態行,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
返回具體的HTML頁面
首先創建我們需要的html頁面,然后把在代碼里面以rb模式讀取出來,發送到瀏覽器
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽
def home(url):
with open('home頁面.html','rb') as fr:
res = fr.read()
return res
def index(url):
with open('index頁面.html', 'rb') as fr:
res = fr.read()
return res
# 定義一個url和實際要執行的函數的對應關系
dt = {
'/index':'index',
'/home':'home'
}
while True:
# 等待連接
conn, addr = sk.accept()
# 接收客戶端返回的信息
data = conn.recv(1024)
# print(data)
# 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 請求首行的進行切割拿到url url是我們從瀏覽器發來的消息分離出來的訪問路徑
url=data1.split(' ')[1]
# print(url)
func = None
# 根據不同的路徑返回不同內容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必須遵循HTTP協議,需要向客服端發送狀態行,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
返回動態的網頁
上面的網頁是不會變化的,如何實現得到一個動態的網站呢?下面做個例子:每次刷新都在獲取新的時間,模擬動態的數據
import socket
import datetime
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽
def home(url):
with open('get_time.html', 'r',encoding='utf8') as fr:
res = fr.read()
now = datetime.datetime.now().strftime("%Y-%m-%d %X")
# 在網頁中定義好特殊符號,用動態的數據替換特殊字符
res = res.replace('*time*',now).encode('utf8')
return res
def index(url):
with open('index頁面.html', 'rb') as fr:
res = fr.read()
return res
# 定義一個url和實際要執行的函數的對應關系
dt = {
'/index': index,
'/home': home
}
while True:
# 等待連接
conn, addr = sk.accept()
# 接收客戶端返回的信息
data = conn.recv(1024)
# print(data)
# 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 請求首行的進行切割拿到url url是我們從瀏覽器發來的消息分離出來的訪問路徑
url=data1.split(' ')[1]
# print(url)
func = None
# 根據不同的路徑返回不同內容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必須遵循HTTP協議,需要向客服端發送狀態行,客戶端才能正常顯示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
什么是服務器程序和應用程序?
對於真實開發中的python web程序來說,一般會分為兩部分:服務器程序和應用程序。
服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各種數據進行整理
應用程序則負責具體的邏輯處理。為了方便應用程序的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。
為了統一規范,設立了一個標准,服務器和框架都支持這個標准。這樣不同的服務器就能適應不同的開發框架,不同的開發框架也就能適應不同的服務器。
WSGI(Web Server Gateway Interface)就是一種規范,它定義了使用Python編寫的web應用程序與web服務器程序之間的接口格式,實現web應用程序與web服務器程序間的解耦。
常用的WSGI服務器有uwsgi、Gunicorn。而Python標准庫提供的獨立WSGI服務器叫wsgiref,Django開發環境用的就是這個模塊來做服務器
利用wsgiref模塊創建web服務器
from wsgiref.simple_server import make_server
def run(environ,response):
# 當客戶發送請求過來時,會先調用wsgi內部接口,然后調用run函數,並且攜帶了兩個參數給run函數
# environ:一個包含所有HTTP請求信息的dict對象;
# response:一個發送HTTP響應的函數。
# 向客戶端發送的狀態碼和頭信息
response('200 OK',[('content-type','text/html; charset=utf-8'),])
# 返回的是一個列表,內容是發送給客戶端展示的內容
return ['hello world'.encode('utf-8')]
if __name__ == '__main__':
# 相當於socket綁定ip和端口
server = make_server('127.0.0.1',8080,run)
# 實時監聽地址,等待客戶端連接,有連接來了就調用run函數
server.serve_forever()
wsgiref模塊實現上所有述功能的服務端
from wsgiref.simple_server import make_server
import datetime
def index(url):
with open('index頁面.html', 'rb') as fr:
data = fr.read()
return data
def home(url):
with open('home頁面.html', 'rb') as fr:
data = fr.read()
return data
def get_time(url):
now = datetime.datetime.now().strftime('%Y-%m-%d %X')
with open('get_time.html','r',encoding='utf-8') as fr:
data = fr.read()
data = data.replace('*time*',now)
return data.encode('utf-8')
dic={
'/index':index,
'/home':home,
'/get_time':get_time
}
def run(env,response):
# 發送狀態碼和頭信息到客戶端
response('200 ok',[('content-type','text/html;charset=utf-8'),])
# print(env)
# 因為env就是客戶端發過來的請求信息(k:v鍵值對形式),
# 通過打印信息得出PATH_INFO就是請求的url,
url = env.get('PATH_INFO')
print(url)
func = None
if url in dic:
func = dic.get(url)
if func:
res = func(url)
else:
res = b'404 not found!!!'
return [res]
if __name__ == '__main__':
server = make_server('127.0.0.1',8080,run)
server.serve_forever()
jinja2模塊
jinja2模塊,跟上面的用特殊符號去替換需要展示的內容的原理是一樣的,jinja2他將html頁面封裝成一個可以渲染數據的模板,然后得到我們真正想要返回給瀏覽器的html頁面。
例子:從數據庫中獲取數據展示到瀏覽器。
1.創建一張user表:
2.創建html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">用戶列表</h1>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>pwd</th>
</tr>
</thead>
<tbody>
<!--user_list是渲染的數據 -->
{% for user_dict in user_list %}
<tr>
<td>{{ user_dict.id }}</td>
<td>{{ user_dict.name }}</td>
<td>{{ user_dict.hobby}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
3.使用jinja2渲染html文件。
from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql
# 從數據庫中獲取,並使用jinja2將數據渲染到html
def get_db(url):
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='tomjoy',
password='123456',
database='user_info',
charset='utf8',
autocommit=True
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "select * from user"
cursor.execute(sql)
# 1.從數據庫中獲取數據
res = cursor.fetchall()
with open('get_db.html','r',encoding='utf8') as fr:
data = fr.read()
# 2.生成html渲染模板對象
temp = Template(data)
# 3.將數據庫中獲取回來的數據,傳到html模板對象進行渲染,
# 返回一個我們真正想要展示的html頁面
ret = temp.render(user_list=res)
return ret.encode('utf8')
dic = {
'/get_db' : get_db
}
def run(env,response):
response('200 ok',[('content-type','text/html;charset=utf-8'),])
func = None
url = env.get('PATH_INFO')
if url in dic:
func = dic.get(url)
if func:
res = func(url)
else:
res = b'404 not found!!!'
return [res]
if __name__ == '__main__':
server = make_server('127.0.0.1',8080,run)
server.serve_forever()
jinja2模板語法(極其貼近python后端語法)
<p>{{ user }}</p>
<p>{{ user.name }}</p>
<p>{{ user['pwd'] }}</p>
<p>{{ user.get('hobby') }}</p>
{% for user_dict in user_list %}
<tr>
<td>{{ user_dict.id }}</td>
<td>{{ user_dict.name }}</td>
<td>{{ user_dict.pwd }}</td>
</tr>
{% endfor %}
模板渲染:利用模板語法 實現后端傳遞數據給前端html頁面
模板語法書寫格式:
變量相關:{{}}
邏輯相關:{%%}
注意:Django的模板語法由於是自己封裝好的,只支持 點.取值
注:模板渲染的原理就是字符串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
效果如下:
Django
1.安裝django
pip3 install django==1.11.11
2.創建django項目
在cmd命令行下創建一個名為mysite的Django項目
django-admin startproject mysite
3.目錄介紹
mysite
├── manage.py # Django入口管理文件
└── templates # 存放html文件
└── mysite # 項目目錄
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函數的對應關系
└── wsgi.py # runserver命令就使用wsgiref模塊做簡單的web server
4.模板文件配置
使用命令行創建django項目 不會自動幫你創建templates文件夾, 只能自己創建
在.settings文件中 需要你手動在TEMPLATES的DIRS寫配置
[os.path.join(BASE_DIR, 'templates')]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # templates 文件夾位置
'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',
],
},
},
]
5.啟動django項目
python manage.py runserver
6.創建應用app01
python manage.py startapp app01
7.app應用目錄:
└── app01 # 項目目錄
├── migrations文件夾 # 存放數據庫遷移記錄
├── __init__.py
├── admin.py # django后台管理
└── apps.py # 注冊相關
└── models.py # 模型類
└── tests.py # 測試文件
└── views.py # 存放視圖函數
注意:如果是在命令行下創建app后,需要你去settings配置文件中注冊添加app名字。這樣django項目才能識別到你這個app
8.靜態文件配置:
什么是靜態文件?
靜態文件就是在打開網頁時所用到的 圖片、 js、css以及第三方的框架bootstrap、fontawesome、sweetalert
通常情況下 網站所用到的靜態文件資源 統一都放在static文件夾下,為了方便識別
STATIC_URL = '/static/' # 是訪問靜態資源的接口前綴,並不是存放靜態文件的文件夾
"""只要你想訪問靜態資源 你就必須以static開頭"""
# 手動在settings最底下添加配置靜態文件訪問資源
# 下面都是存放靜態文件的文件夾的路徑
# 從上往下找靜態文件,找不到就報錯
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
]
圖解:
9.禁用中間件:
前期為了方便表單提交測試。在settings配置文件中暫時禁用csrf中間件
10.重定向:
重定向的意思就是,我訪問的鏈接不是我剛剛輸入的那個鏈接,而是我一輸入他就跳轉到了另外一個鏈接,這就是重定向
最后注意事項:
1.計算機的名稱不能有中文
2.一個pycharm窗口就是一個項目
3.項目名里面盡量不要用中文
django版本問題
1.X 2.X 現在市面上用的比較多的還是1.X
推薦使用1.11.9~1.11.13
django安裝
pip3 install django==1.11.11
如何驗證django是否安裝成功
命令行直接敲django-admin
一個django項目就類似於是一所大學,而app就類似於大學里面的學院
django其實就是用來一個個應用的
一個app就相當於一塊獨立的功能
用戶功能
管理功能
.........
django支持任意多個app