Web框架本質
我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣我們就可以自己實現Web框架了。
socket服務端?
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協議是怎么規定消息格式的呢?
讓我們首先打印下我們在服務端接收到的消息是什么。
import?socket????????sk?=?socket.socket()????sk.bind(("127.0.0.1",?80))????sk.listen()????????????while?True:????????conn,?addr?=?sk.accept()????????data?=?conn.recv(8096)????????print(data)????????conn.send(b"OK")????????conn.close()???? 輸出:
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8\r\n\r\n'
我們將\r\n替換成換行看得更清晰點:
GET?/?HTTP/1.1??Host:?127.0.0.1:8080??Connection:?keep-alive??Cache-Control:?max-age=0??Upgrade-Insecure-Requests:?1??User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?WOW64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/66.0.3355.4?Safari/537.36??Accept:?text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8??Accept-Encoding:?gzip,?deflate,?br??Accept-Language:?zh-CN,zh;q=0.9??Cookie:?csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8?????? 然后我們再看一下我們訪問博客園官網時瀏覽器收到的響應信息是什么。
響應相關信息可以在瀏覽器調試窗口的Network標簽頁中看到。
點擊view source之后顯示如下圖:
我們發現收發的消息需要按照一定的格式來,這里就需要了解一下HTTP協議了。
HTTP協議介紹
HTTP協議對收發消息的格式要求
每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。
HTTP響應的Header中有一個 Content-Type表明響應的內容格式。它的值如text/html; charset=utf-8。
text/html則表示是網頁,charset=utf-8則表示編碼為utf-8。
HTTP GET請求的格式:
HTTP響應的格式:
?自定義web框架
經過上面的學習,那我們基于socket服務端的十幾行代碼寫一個我們自己的web框架。我們先不處理瀏覽器發送的請求,先讓瀏覽器能顯示我們web框架返回的信息,那我們就要按照HTTP協議的格式來發送響應。
import?socket????????sock?=?socket.socket(socket.AF_INET,?socket.SOCK_STREAM)????sock.bind(('127.0.0.1',?8000))????sock.listen()????????while?True:????????conn,?addr?=?sock.accept()????????data?=?conn.recv(8096)??????????????conn.send(b"HTTP/1.1?200?OK\r\n\r\n")????????conn.send(b"OK")????????conn.close()???? 我們通過十幾行代碼簡單地演示了web 框架的本質。
接下來就讓我們繼續完善我們的自定義web框架吧!
根據不同的路徑返回不同的內容
這樣就結束了嗎? 如何讓我們的Web服務根據用戶請求的URL不同而返回不同的內容呢??
小事一樁,我們可以從請求相關數據里面拿到請求URL的路徑,然后拿路徑做一個判斷...
??????import?socket????sk?=?socket.socket()??sk.bind(("127.0.0.1",?8080))????sk.listen()??????while?True:????????????conn,?add?=?sk.accept()??????data?=?conn.recv(8096)??????????????data?=?str(data,?encoding="utf8")??????????????data1?=?data.split("\r\n")[0]??????url?=?data1.split()[1]????????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()?? 根據不同的路徑返回不同的內容--函數版
上面的代碼解決了不同URL路徑返回不同內容的需求。
我們返回的內容是簡單的幾個字符,那如果我可以將返回的結果封裝成一個函數呢?
??????import?socket????sk?=?socket.socket()??sk.bind(("127.0.0.1",?8080))????sk.listen()??????????def?func(url):??????s?=?"這是{}頁面!".format(url)??????return?bytes(s,?encoding="utf8")??????while?True:????????????conn,?add?=?sk.accept()??????data?=?conn.recv(8096)??????????????data?=?str(data,?encoding="utf8")??????????????data1?=?data.split("\r\n")[0]??????url?=?data1.split()[1]????????conn.send(b'HTTP/1.1?200?OK\r\n\r\n')??????????????if?url?==?"/index/":??????????response?=?func(url)??????elif?url?==?"/home/":??????????response?=?func(url)??????else:??????????response?=?b"404?not?found!"????????conn.send(response)??????conn.close()?? 根據不同的路徑返回不同的內容--函數進階版
看起來上面的代碼寫了一個函數,那肯定可以寫多個函數,不同的路徑對應執行不同的函數拿到結果,但是我們要一個個判斷路徑,是不是很麻煩?我們有簡單的辦法來解決。
??????import?socket????sk?=?socket.socket()??sk.bind(("127.0.0.1",?8080))????sk.listen()??????????def?index(url):??????s?=?"這是{}頁面XX!".format(url)??????return?bytes(s,?encoding="utf8")??????def?home(url):??????s?=?"這是{}頁面。。!".format(url)??????return?bytes(s,?encoding="utf8")????????list1?=?[??????("/index/",?index),??????("/home/",?home),??]????while?True:????????????conn,?add?=?sk.accept()??????data?=?conn.recv(8096)??????????????data?=?str(data,?encoding="utf8")??????????????data1?=?data.split("\r\n")[0]??????url?=?data1.split()[1]????????conn.send(b'HTTP/1.1?200?OK\r\n\r\n')??????????????func?=?None????????for?item?in?list1:??????????if?item[0]?==?url:??????????????func?=?item[1]??????????????break??????if?func:??????????response?=?func(url)??????else:??????????response?=?b"404?not?found!"??????????????conn.send(response)??????conn.close()?? 返回具體的HTML文件
完美解決了不同URL返回不同內容的問題。 但是我不想僅僅返回幾個字符串,我想給瀏覽器返回完整的HTML內容,這又該怎么辦呢?
沒問題,不管是什么內容,最后都是轉換成字節數據發送出去的。 我們可以打開HTML文件,讀取出它內部的二進制數據,然后再發送給瀏覽器。
???????import?socket????sk?=?socket.socket()??sk.bind(("127.0.0.1",?8080))????sk.listen()??????????def?index(url):????????????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")????????list1?=?[??????("/index/",?index),??????("/home/",?home),??]????while?True:????????????conn,?add?=?sk.accept()??????data?=?conn.recv(8096)??????????????data?=?str(data,?encoding="utf8")??????????????data1?=?data.split("\r\n")[0]??????url?=?data1.split()[1]????????conn.send(b'HTTP/1.1?200?OK\r\n\r\n')??????????????func?=?None????????for?item?in?list1:??????????if?item[0]?==?url:??????????????func?=?item[1]??????????????break??????if?func:??????????response?=?func(url)??????else:??????????response?=?b"404?not?found!"??????????????conn.send(response)??????conn.close()?? <!DOCTYPE html>
<html lang="en">
<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>index
</title>
</head>
<body>
<div>這是index頁面
</div>
</body>
</html> index.html <!DOCTYPE html>
<html lang="en">
<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>index
</title>
</head>
<body>
<div>這是home頁面
</div>
</body>
</html> home.html 讓網頁動態起來
這網頁能夠顯示出來了,但是都是靜態的啊。頁面的內容都不會變化的,我想要的是動態網站。
沒問題,我也有辦法解決。我選擇使用字符串替換來實現這個需求。(這里使用時間戳來模擬動態的數據)
???????import?socket????sk?=?socket.socket()??sk.bind(("127.0.0.1",?8080))????sk.listen()??????????def?index(url):????????????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")??????def?timer(url):??????import?time??????with?open("time.html",?"r",?encoding="utf8")?as?f:??????????s?=?f.read()??????????s?=?s.replace('@@time@@',?time.strftime("%Y-%m-%d?%H:%M:%S"))??????return?bytes(s,?encoding="utf8")????????list1?=?[??????("/index/",?index),??????("/home/",?home),??????("/time/",?timer),??]????while?True:????????????conn,?add?=?sk.accept()??????data?=?conn.recv(8096)??????????????data?=?str(data,?encoding="utf8")??????????????data1?=?data.split("\r\n")[0]??????url?=?data1.split()[1]????????conn.send(b'HTTP/1.1?200?OK\r\n\r\n')??????????????func?=?None????????for?item?in?list1:??????????if?item[0]?==?url:??????????????func?=?item[1]??????????????break??????if?func:??????????response?=?func(url)??????else:??????????response?=?b"404?not?found!"??????????????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
我們利用wsgiref模塊來替換我們自己寫的web框架的socket server部分:
?????????????from?wsgiref.simple_server?import?make_server???????????????def?index(url):?????????????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")?????????????def?timer(url):???????import?time???????with?open("time.html",?"r",?encoding="utf8")?as?f:???????????s?=?f.read()???????????s?=?s.replace('@@time@@',?time.strftime("%Y-%m-%d?%H:%M:%S"))???????return?bytes(s,?encoding="utf8")???????????????list1?=?[???????("/index/",?index),???????("/home/",?home),???????("/time/",?timer),???]?????????????def?run_server(environ,?start_response):???????start_response('200?OK',?[('Content-Type',?'text/html;charset=utf8'),?])????????url?=?environ['PATH_INFO']????????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!"???????return?[response,?]?????????????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
<!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> index2.html文件 使用jinja2渲染index2.html文件:
from?wsgiref.simple_server?import?make_server??from?jinja2?import?Template??????def?index(url):????????????with?open("index2.html",?"r",?encoding="utf8")?as?f:??????????data?=?f.read()??????????template?=?Template(data)?????????????ret?=?template.render({'name':?'alex',?'hobby_list':?['抽煙',?'喝酒',?'燙頭']})?????????return?bytes(ret,?encoding="utf8")??????def?home(url):??????with?open("home.html",?"r",?encoding="utf8")?as?f:??????????s?=?f.read()??????return?bytes(s,?encoding="utf8")????????list1?=?[??????("/index/",?index),??????("/home/",?home),??]??????def?run_server(environ,?start_response):??????start_response('200?OK',?[('Content-Type',?'text/html;charset=utf8'),?])????????url?=?environ['PATH_INFO']????????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!"??????return?[response,?]??????if?__name__?==?'__main__':??????httpd?=?make_server('127.0.0.1',?8090,?run_server)??????print("我在8090等你哦...")??????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的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
Django
Django官網下載頁面
安裝(安裝最新LTS版):
pip3
install django==1.11.15
創建一個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 啟動時報錯 UnicodeEncodeError ...
報這個錯誤通常是因為計算機名為中文,改成英文的計算機名重啟下電腦就可以了。
轉載于:https://www.cnblogs.com/jiadi321/p/9669646.html
總結
以上是生活随笔為你收集整理的Web框架的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。