咱們能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣咱們就能夠本身實現Web框架了。html
在學以前咱們要複習前面的知識python
"""mysql
s = "Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3\r\n\r\n自行車"web
# 問題1:如何取到["Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3", "自行車"]sql
s1=(s.split("\r\n\r\n"))數據庫
print(s1)django
# 問題2:如何在上面結果基礎上拿到["Alex", "SB", "哈哈"]?瀏覽器
s2=s1[0].split('\r\n')[0].split(' ')服務器
print(s2)session
# 問題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"))
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模塊來替換咱們本身寫的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()
上面的代碼實現了一個簡單的動態,我徹底能夠從數據庫中查詢數據,而後去替換我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的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
安裝(安裝最新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 ...
報這個錯誤一般是由於計算機名爲中文,改爲英文的計算機名重啓下電腦就能夠了。