Web框架本質
框架,即framework,特指爲解決一個開放性問題而設計的具備必定約束性的支撐結構,使用框架能夠幫你快速開發特定的系統,簡單地說,就是你用別人搭建好的舞臺來作表演。html
對於全部的Web應用,本質上其實就是一個socket服務端,用戶的瀏覽器其實就是一個socket客戶端前端
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) client.send("<h1 style='color:red'>Hello, yuan</h1>".encode("utf8")) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8001)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
能夠說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。python
用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什麼數據?怎麼發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩麼?mysql
因此,必須有一個統一的規則,讓你們發送消息、接收消息的時候有個格式依據,不能隨便寫。jquery
這個規則就是HTTP協議,之後瀏覽器發送請求信息也好,服務器回覆響應信息也罷,都要按照這個規則來。web
HTTP協議主要規定了客戶端和服務器之間的通訊格式,那HTTP協議是怎麼規定消息格式的呢?sql
讓咱們首先看下咱們在服務端接收到的消息是什麼。shell
而後再看下咱們瀏覽器收到的響應信息是什麼。數據庫
響應頭在瀏覽器的network窗口能夠看到,咱們看到的HTML頁面內容就是響應體。本質上仍是字符串,由於瀏覽器認識HTML,因此纔會渲染出頁面。django
每一個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type
代表響應的內容格式。如 text/html
表示HTML網頁
HTTP GET請求的格式:
GET /path HTTP/1.1 header1:v1\r\n header2:v2\r\n
使用 \r\n
分隔多個header
HTTP POST請求格式:
POST /path HTTP/1.1 header1:v1\r\n header2:v2\r\n \r\n\r\n 請求體...
當遇到連續兩個 \r\n\r\n
時,表示Header部分結束了,後面的數據是Body。
HTTP響應的格式:
200 OK Header1:v1\r\n Header2:v2\r\n \r\n\r\n 響應體...
上述經過socket來實現了其本質。
對於真實開發中的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開發環境用的就是這個模塊來作服務器
最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。
若是要動態生成HTML,就須要把上述步驟本身來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,若是咱們本身來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規範。
正確的作法是底層代碼由專門的服務器軟件實現,咱們用Python專一於生成HTML文檔。由於咱們不但願接觸到TCP鏈接、HTTP原始請求和響應格式,因此,須要一個統一的接口,讓咱們專心用Python編寫Web業務。
這個接口就是WSGI:Web Server Gateway Interface
from wsgiref.simple_server import make_server def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8')]) # 設置HTTP響應的狀態碼和頭信息 return [bytes("<h1>Hello world!</h1>", encoding="utf8"),] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
這樣就結束了嗎? 如何讓咱們的Web服務根據用戶請求的URL不一樣而返回不一樣的內容呢?
小事一樁,咱們能夠從請求相關數據裏面拿到請求的URL,而後作一個判斷
from wsgiref.simple_server import make_server def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url if url == "/index/": return [bytes("<h1>這是index頁面</h1>", encoding="utf8"), ] elif url == "/home/": return [bytes("<h1>這是home頁面</h1>", encoding="utf8"), ] else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
解決了不一樣URL返回不一樣內容的需求。 可是問題又來了,若是有不少不少頁面怎麼辦?難道要挨個判斷? 固然不用,咱們有更聰明的辦法
from wsgiref.simple_server import make_server def index(): return [bytes("<h1>這是index頁面</h1>", encoding="utf8"), ] def home(): return [bytes("<h1>這是home頁面</h1>", encoding="utf8"), ] # 定義一個url和函數的對應關係 URL_LIST = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None # 將要執行的函數 for i in URL_LIST: if i[0] == url: func = i[1] # 去以前定義好的url列表裏找url應該執行的函數 break if func: # 若是能找到要執行的函數 return func() # 返回函數的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
完美解決了不一樣URL返回不一樣內容的問題。 可是我不想僅僅返回幾個字符串,我想給瀏覽器返回完整的HTML內容,這又該怎麼辦呢?
沒問題,不論是什麼內容,最後都是轉換成字節數據發送出去的。 我能夠打開HTML文件,讀取出它內部的二進制數據,而後發送給瀏覽器
from wsgiref.simple_server import make_server def index(): with open("index.html", "rb") as f: data = f.read() return [data, ] def home(): with open("home.html", "rb") as f: data = f.read() return [data, ] # 定義一個url和函數的對應關係 URL_LIST = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None # 將要執行的函數 for i in URL_LIST: if i[0] == url: func = i[1] # 去以前定義好的url列表裏找url應該執行的函數 break if func: # 若是能找到要執行的函數 return func() # 返回函數的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
這網頁可以顯示出來了,可是都是靜態的啊。頁面的內容都不會變化的,我想要的是動態網站。
沒問題,我也有辦法解決。我選擇使用字符串替換來實現這個需求
from wsgiref.simple_server import make_server def index(): with open("index.html", "rb") as f: data = f.read() import time time_str = str(time.time()) data_str = str(data, encoding="utf8") data_str = data_str.replace("@@a@@", time_str) return [bytes(data_str, encoding="utf8"), ] def home(): with open("home.html", "rb") as f: data = f.read() return [data, ] # 定義一個url和函數的對應關係 URL_LIST = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None # 將要執行的函數 for i in URL_LIST: if i[0] == url: func = i[1] # 去以前定義好的url列表裏找url應該執行的函數 break if func: # 若是能找到要執行的函數 return func() # 返回函數的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
這是一個簡單的動態,我徹底能夠從數據庫中查詢數據,而後去替換我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>
使用jinja2渲染index2.html文件:
from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("index2.html", "r") as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render({"name": "Alex", "hobby_list": ["燙頭", "泡吧"]}) # 把數據填充到模板裏面 return [bytes(ret, encoding="utf8"), ] def home(): with open("home.html", "rb") as f: data = f.read() return [data, ] # 定義一個url和函數的對應關係 URL_LIST = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None # 將要執行的函數 for i in URL_LIST: if i[0] == url: func = i[1] # 去以前定義好的url列表裏找url應該執行的函數 break if func: # 若是能找到要執行的函數 return func() # 返回函數的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") 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()
模板的原理就是字符串替換,咱們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。
框架總結
本身定義的web框架
a. 本身寫socket處理請求相關的數據(web服務端) wsgiref uWSGI gunicorn
WSGI協議:
Python Web服務端和Python 應用程序之間通信的一個標準
b. URL -> 函數 --> 函數裏面寫業務邏輯(取到數據)
c. 把數據填充到HTML頁面(字符串替換)
分類:
1. a、b、c都是用本身的 --> Tornado
2. a用別人的,b和c用本身的 --> Django
3. a和c都用別人的,b用本身的 --> Flask
另一個維度的分類:
1. Django (大而全)
2. 其餘 (小而精)
Django
安裝(安裝最新LTS版)
pip3 install django==1.11.9
MVC和MTV模式
Django的MTV模式本質是各組件之間爲了保持鬆耦合關係,Django的MTV分別表明:
Model(模型):負責業務對象與數據庫的對象(ORM)
Template(模版):負責如何把頁面展現給用戶
View(視圖):負責業務邏輯,並在適當的時候調用Model和Template
此外,Django還有一個url分發器,它的做用是將一個個URL的頁面請求分發給不一樣的view處理,view再調用相應的Model和Template
Django基本命令
建立一個django project
django-admin.py startproject mysite
當前目錄下會生成mysite的工程,目錄結構以下:
manage.py ----- Django項目裏面的工具,經過它能夠調用django shell和數據庫等。
settings.py ---- 包含了項目的默認設置,包括數據庫信息,調試標誌以及其餘一些工做的變量。
urls.py ----- 負責把URL模式映射到應用程序。
PyCharm方式:
File -> new project -> 選Django -> 起名字 -> 點右下角create
在mysite目錄下建立應用,好比blog
python manage.py startapp blog
啓動django項目
python manage.py runserver 8080
這樣咱們的django就啓動起來了!當咱們訪問:http://127.0.0.1:8080/時就能夠看到:
setting文件配置
模板文件配置
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', ], }, }, ]
靜態文件配置
# 在HTML頁面上引用靜態文件時寫的名字 STATIC_URL = '/static/' # HTML中使用的靜態文件夾前綴 # 靜態文件(CSS文件和JS文件)存放的實際位置 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), # 靜態文件存放位置 ]
#三、STATIC文件還能夠配置STATICFILES_DIRS,指定額外的靜態文件存儲位置。 # STATIC_URL的含義與MEDIA_URL相似。 # ---------------------------------------------------------------------------- #注意1: #爲了後端的更改不會影響前端的引入,避免形成前端大量修改 STATIC_URL = '/static/' #引用名 STATICFILES_DIRS = ( os.path.join(BASE_DIR,"statics") #實際名 ,即實際文件夾的名字 ) #django對引用名和實際名進行映射,引用時,只能按照引用名來,不能按實際名去找 #<script src="/statics/jquery-3.1.1.js"></script> #------error-----不能直接用,必須用STATIC_URL = '/static/': #<script src="/static/jquery-3.1.1.js"></script> #注意2(statics文件夾寫在不一樣的app下,靜態文件的調用): STATIC_URL = '/static/' STATICFILES_DIRS=( ('hello',os.path.join(BASE_DIR,"app01","statics")) , ) #<script src="/static/hello/jquery-1.8.2.min.js"></script> #注意3: STATIC_URL = '/static/' {% load staticfiles %} # <script src={% static "jquery-1.8.2.min.js" %}></script>
剛開始學習時可在配置文件中暫時禁用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": ["燙頭", "泡吧"]})
---------------render(request, template_name[, context]) 結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的 HttpResponse 對象。 參數: request: 用於生成響應的請求對象。 template_name:要使用的模板的完整名稱,可選的參數 context:添加到模板上下文的一個字典。默認是一個空字典。若是字典中的某個值是可調用的,視圖將在渲染模板以前調用它。 content_type:生成的文檔要使用的MIME類型。默認爲DEFAULT_CONTENT_TYPE 設置的值。 status:響應的狀態碼。默認爲200。
redirect
接受一個URL參數,表示跳轉到指定的URL。
例如:
def index(request): # 業務邏輯代碼 return redirect("/home/")
-----------------------------------url.py url(r"login", views.login), url(r"yuan_back", views.yuan_back), -----------------------------------views.py def login(req): if req.method=="POST": if 1: # return redirect("/yuan_back/") name="yuanhao" return render(req,"my backend.html",locals()) return render(req,"login.html",locals()) def yuan_back(req): name="苑昊" return render(req,"my backend.html",locals()) -----------------------------------login.html <form action="/login/" method="post"> <p>姓名<input type="text" name="username"></p> <p>性別<input type="text" name="sex"></p> <p>郵箱<input type="text" name="email"></p> <p><input type="submit" value="submit"></p> </form> -----------------------------------my backend.html <h1>用戶{{ name }}你好</h1> #總結: render和redirect的區別: # 1 if render的頁面須要模板語言渲染,須要的將數據庫的數據加載到html,那麼全部的這一部分 # 除了寫在yuan_back的視圖函數中,必須還要寫在login中,代碼重複,沒有解耦. # 2 the most important: url沒有跳轉到/yuan_back/,而是還在/login/,因此當刷新後 # 又得從新登陸.