Web框架本質
我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣我們就可以自己實現Web框架了。
在學之前我們要復習前面的知識
"""
s = "Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3\r\n\r\n自行車"
# 問題1:如何取到["Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3", "自行車"]
s1=(s.split("\r\n\r\n"))
print(s1)
# 問題2:如何在上面結果基礎上拿到["Alex", "SB", "哈哈"]?
s2=s1[0].split('\r\n')[0].split(' ')
print(s2)
# 問題3:如何在上面結果基礎上拿到"SB"?
print(s2[1])
# ------------------------------------------------------------------------------------------
# 有一個列表,他的內部是一些元祖,元祖的第一個元素是姓名,第二個元素是愛好。
# 現在我給你一個姓名,如"Egon",如果有這個姓名,就打印出他的愛好,沒有就打印查無此人。
list1 = [
("Alex", "燙頭"),
("Egon", "街舞"),
("Yuan", "喝茶")
]
for i in list1:
if "Egon"==i[0]:
print(i[1])
break
else:
print('查無此人')
# ------------------------------------------------------------------------------------------
# 我有一個HTML文件"login.html"
# 問題1:我如何讀取它的內容保存到變量html_s?
# with open('login.html',r,encoding='utf8')as f:
# html_s=f.read()
# 問題2:我如何讀取它的二進制內容保存到變量html_b?
# with open('login.html',rb)as f:
# html_b=f.read()
# ------------------------------------------------------------------------------------------
s2 = "Alex 花了一百萬買了輛電動車,真@@xx@@。"
# 問題1:如何把上面的s2轉變成"Alex 花了一百萬買了輛電動車,真SB。"
# print(s2.replace("真@@xx@@","真SB"))
半成品自定義web框架

import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b"OK") conn.close()
可以說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。
用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什么數據?怎么發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩么?
所以,必須有一個統一的規則,讓大家發送消息、接收消息的時候有個格式依據,不能隨便寫。
這個規則就是HTTP協議,以后瀏覽器發送請求信息也好,服務器回復響應信息也罷,都要按照這個規則來。
HTTP協議主要規定了客戶端和服務器之間的通信格式,那HTTP協議是怎么規定消息格式的呢?
讓我們首先打印下我們在服務端接收到的消息是什么。
然后我們再看一下我們訪問博客園官網時瀏覽器收到的響應信息是什么。
響應相關信息可以在瀏覽器調試窗口的network標簽頁中看到。
點擊view source之后顯示如下圖:
我們發現收發的消息需要按照一定的格式來,這里就需要了解一下HTTP協議了。
HTTP協議對收發消息的格式要求
每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type表明響應的內容格式。如 text/html表示HTML網頁。
HTTP GET請求的格式:
HTTP響應的格式:
經過上面的補充學習,我們知道了要想讓我們自己寫的web server端正經起來,必須要讓我們的Web server在給客戶端回復消息的時候按照HTTP協議的規則加上響應狀態行,這樣我們就實現了一個正經的Web框架了。

import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 print(data) conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>O98K</h1>') # 回復消息 conn.close()
根據不同的路徑返回不同的內容

""" 根據瀏覽器訪問的路徑的不同,返回不同的內容 """ import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 從瀏覽器發送消息中,拿到用戶訪問的路徑 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) if url == "/home/": msg = b'<h1>home page</h1>' elif url == "/index/": msg = b'<h1>index page</h1>' else: msg = b'<h1>404</h1>' conn.send(b'HTTP/1.1 200 OK\r\n\r\n') conn.send(msg) conn.close()
根據瀏覽器訪問的路徑的不同,返回不同的內容

""" 根據瀏覽器訪問的路徑的不同,返回不同的內容 將不同頁面的處理代碼封裝到函數中 """ import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() def home(url): return b'<h1>home page</h1>' def index(url): return b'<h1>index page</h1>' while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 從瀏覽器發送消息中,拿到用戶訪問的路徑 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) if url == "/home/": msg = home(url) elif url == "/index/": msg = index(url) else: msg = b'<h1>404</h1>' conn.send(b'HTTP/1.1 200 OK\r\n\r\n') conn.send(msg) conn.close()
根據瀏覽器訪問的路徑的不同,返回不同的內容在升級

""" 根據瀏覽器訪問的路徑的不同,返回不同的內容 將不同頁面的處理代碼封裝到函數中 優化頻繁的if判斷 """ import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): return b'hehe' while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 從瀏覽器發送消息中,拿到用戶訪問的路徑 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ] func = None for i in url2func: if url == i[0]: func = i[1] # 拿到將要執行的函數 break else: func = None if func: msg = func(url) # 執行對應的函數 else: msg = b'<h1>404</h1>' # 找不到要執行的函數就返回404 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') conn.send(msg) conn.close()
返回具體的HTML文件

""" 根據瀏覽器訪問的路徑的不同,返回不同的內容 將不同頁面的處理代碼封裝到函數中 優化頻繁的if判斷 返回具體的HTML文件 """ import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): return b'hehe' def login(url): with open("login.html", "rb") as f: return f.read() # url和將要執行的函數的對應關系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 從瀏覽器發送消息中,拿到用戶訪問的路徑 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) func = None for i in url2func: if url == i[0]: func = i[1] # 拿到將要執行的函數 break else: func = None if func: msg = func(url) # 執行對應的函數 else: msg = b'<h1>404</h1>' # 找不到要執行的函數就返回404 # 按照HTTP協議的格式要求 回復消息 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 發送狀態行 conn.send(msg) # 發送響應體 conn.close()
返回動態的HTML文件

""" 根據瀏覽器訪問的路徑的不同,返回不同的內容 將不同頁面的處理代碼封裝到函數中 優化頻繁的if判斷 返回具體的HTML文件 實現不同的用戶得到不同的HTML頁面 """ import socket import time server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 不同的用戶得到的頁面上顯示不同的時間 c_time = str(time.time()) with open("user.html", "r") as f: data_s = f.read() data_s = data_s.replace("@@xx@@", c_time) # 替換后的字符串數據 return bytes(data_s, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和將要執行的函數的對應關系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 從瀏覽器發送消息中,拿到用戶訪問的路徑 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) func = None for i in url2func: if url == i[0]: func = i[1] # 拿到將要執行的函數 break else: func = None if func: msg = func(url) # 執行對應的函數 else: msg = b'<h1>404</h1>' # 找不到要執行的函數就返回404 # 按照HTTP協議的格式要求 回復消息 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 發送狀態行 conn.send(msg) # 發送響應體 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
我們利用wsgiref模塊來替換我們自己寫的web框架的socket server部分:
"""
根據瀏覽器訪問的路徑的不同,返回不同的內容
將不同頁面的處理代碼封裝到函數中
優化頻繁的if判斷
返回具體的HTML文件
實現不同的用戶得到不同的HTML頁面

""" import time from wsgiref.simple_server import make_server def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 不同的用戶得到的頁面上顯示不同的時間 c_time = str(time.time()) with open("user.html", "r") as f: data_s = f.read() data_s = data_s.replace("@@xx@@", c_time) # 替換后的字符串數據 return bytes(data_s, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和將要執行的函數的對應關系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] # 按照wsgiref的要求定義一個run_server函數 def run_server(environ, start_response): """ :param environ: 跟請求相關的參數 :param start_response: :return: """ # 設置HTTP響應的狀態碼和頭信息 start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) url = environ['PATH_INFO'] # 取到用戶輸入的url func = None for i in url2func: if url == i[0]: func = i[1] # 拿到將要執行的函數 break else: func = None if func: msg = func(url) # 執行對應的函數 else: msg = b'<h1>404</h1>' # 找不到要執行的函數就返回404 return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
jinja2
上面的代碼實現了一個簡單的動態,我完全可以從數據庫中查詢數據,然后去替換我html中的對應內容,然后再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這里用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2
下載jinja2:
pip install jinja2
"""
根據瀏覽器訪問的路徑的不同,返回不同的內容
將不同頁面的處理代碼封裝到函數中
優化頻繁的if判斷
返回具體的HTML文件
實現不同的用戶得到不同的HTML頁面

""" import time from wsgiref.simple_server import make_server from jinja2 import Template import pymysql def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 從數據庫里面去到所有的用戶信息, conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123', database='day61', charset='utf8' ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute('select * from user') ret = cursor.fetchall() print(ret) # 在頁面上顯示出來 with open("user.html", "r", encoding="utf8") as f: data_s = f.read() template = Template(data_s) # 生成一個模板文件實例 msg = template.render({"user_list": ret}) # 把數據填充到模板里面 return bytes(msg, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和將要執行的函數的對應關系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] # 按照wsgiref的要求定義一個run_server函數 def run_server(environ, start_response): """ :param environ: 跟請求相關的參數 :param start_response: :return: """ # 設置HTTP響應的狀態碼和頭信息 start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) url = environ['PATH_INFO'] # 取到用戶輸入的url func = None for i in url2func: if url == i[0]: func = i[1] # 拿到將要執行的函數 break else: func = None if func: msg = func(url) # 執行對應的函數 else: msg = b'<h1>404</h1>' # 找不到要執行的函數就返回404 return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
創建一個測試的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的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
Django
安裝(安裝最新LTS版):
pip3 install django==1.11.9
創建一個django項目:
下面的命令創建了一個名為"mysite"的Django 項目:
django-admin startproject mysite
目錄介紹:
mysite/
├── manage.py # 管理文件
└── mysite # 項目目錄
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函數的對應關系
└── wsgi.py # runserver命令就使用wsgiref模塊做簡單的web server
運行Django項目:
python manage.py runserver 127.0.0.1:8000
模板文件配置:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "template")], # template文件夾位置
'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',
],
},
},
]
靜態文件配置:
STATIC_URL = '/static/' # HTML中使用的靜態文件夾前綴
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), # 靜態文件存放位置
]
看不明白?有圖有真相:
剛開始學習時可在配置文件中暫時禁用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基礎必備三件套:
from django.shortcuts import HttpResponse, render, redirect
HttpResponse
內部傳入一個字符串參數,返回給瀏覽器。
例如:
def index(request):
# 業務邏輯代碼
return HttpResponse("OK")
render
除request參數外還接受一個待渲染的模板文件和一個保存具體數據的字典參數。
將數據填充進模板文件,最后把結果返回給瀏覽器。(類似於我們上面用到的jinja2)
例如:
def index(request):
# 業務邏輯代碼
return render(request, "index.html", {"name": "alex", "hobby": ["燙頭", "泡吧"]})
redirect
接受一個URL參數,表示跳轉到指定的URL。
例如:
def index(request):
# 業務邏輯代碼
return redirect("/home/")
重定向是怎么回事?
課后練習:
Django版登錄
啟動Django報錯:
Django 啟動時報錯 UnicodeEncodeError ...
報這個錯誤通常是因為計算機名為中文,改成英文的計算機名重啟下電腦就可以了。