web框架?
框架,即framework,特指為解決一個開放性問題而設計的具有一定約束性的支撐結構,使用框架可以幫你快速開發特定的系統,簡單地說,就是你用別人搭建好的舞台來做表演。
web應用 訪問請求流程
*瀏覽器發送一個HTTP請求;
*服務器收到請求,生成一個HTML文檔(待補充;是否是全部類型的訪問都需要生成文檔);
*服務器把HTML文檔作為HTTP響應的Body發送給瀏覽器;
*瀏覽器收到HTTP響應,從HTTP Body取出HTML文檔並解析顯示
對於所有的Web應用,本質上其實就是一個socket服務端,用戶的瀏覽器其實就是一個socket客戶端。
socket實例
# -*- coding: utf-8 -*-
# @Date : 2016/11/24
# @Author : Jesson
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:#1b7665'>Hello, Jesson</h1>".encode("utf8"))
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost',8001))
sock.listen(5)
whileTrue:
connection, address = sock.accept()
handle_request(connection)
connection.close()
if __name__ =='__main__':
main()</pre>
地址欄輸入訪問請求:
最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。
如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規范。
正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專注於生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應格式,所以,需要一個統一的接口,讓我們專心用Python編寫Web業務。
這個接口就是WSGI:Web Server Gateway Interface。
WSGI 簡單介紹
WSGI,全稱 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是為 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口。自從 WSGI 被開發出來以后,許多其它語言中也出現了類似接口。
WSGI 是作為 Web 服務器與 Web 應用程序或應用框架之間的一種低級別的,以提升可移植 Web 應用開發的共同點。WSGI 是基於現存的 CGI 標准而設計的。
很多框架都自帶了 WSGI server ,比如 Flask,webpy,Django、CherryPy等等。當然性能都不好,自帶的 web server 更多的是測試用途,發布時則使用生產環境的 WSGI server或者是聯合 nginx 做 uwsgi。
通俗來講,WSGI就像是一座橋梁,一邊連着web服務器,另一邊連着用戶的應用。但是呢,這個橋的功能很弱,有時候還需要別的橋來幫忙才能進行處理。
WSGI的作用
WSGI有兩方:“服務器”或“網關”一方,以及“應用程序”或“應用框架”一方。服務方調用應用方,提供環境信息,以及一個回調函數(提供給應用程序用來將消息頭傳遞給服務器方),並接收Web內容作為返回值。
所謂的 WSGI中間件同時實現了API的兩方,因此可以在WSGI服務和WSGI應用之間起調解作用:從WSGI服務器的角度來說,中間件扮演應用程序,而從應用程序的角度來說,中間件扮演服務器。“中間件”組件可以執行以下功能:
- 重寫環境變量后,根據目標URL,將請求消息路由到不同的應用對象。
- 允許在一個進程中同時運行多個應用程序或應用框架。
- 負載均衡和遠程處理,通過在網絡上轉發請求和響應消息。
- 進行內容后處理,例如應用XSLT樣式表。
WSGI 的設計確實參考了 Java 的 servlet。http://www.python.org/dev/peps/pep-0333/ 有這么一段話:
By contrast, although Java has just as many web application frameworks available, Java's "servlet" API makes it possible for applications written with any Java web application framework to run in any web server that supports the servlet API.
另外,需要提及的一點是:其它基於python的web框架,如tornado、flask、webpy都是在這個范圍內進行增刪裁剪的。例如tornado用的是自己的異步非阻塞“wsgi”,flask則只提供了最精簡和基本的框架。Django則是直接使用了WSGI,並實現了大部分功能。
WSGI實例一
# -*- coding: utf-8 -*-
from wsgiref.simple_server import make_server
def application(environ, start_response):
start_response('200 OK',[('Content-Type','text/html')])
return[b'<h1 >Hello, web!</h1>']
#''中間為空,表示的是本地地址
httpd = make_server('',8080, application)
print('Serving HTTP on port 8000...')
# 開始監聽HTTP請求:
httpd.serve_forever()</pre>
整個application()函數本身沒有涉及到任何解析HTTP的部分,也就是說,底層代碼不需要我們自己編寫,
我們只負責在更高層次上考慮如何響應請求就可以了。
application()函數必須由WSGI服務器來調用。有很多符合WSGI規范的服務器,我們可以挑選一個來用。
Python內置了一個WSGI服務器,這個模塊叫wsgiref
application()函數就是符合WSGI標准的一個HTTP處理函數,它接收兩個參數://environ:一個包含所有HTTP請求信息的dict對象; //start_response:一個發送HTTP響應的函數。
在application()函數中,調用:
start_response('200 OK',[('Content-Type','text/html')])
就發送了HTTP響應的Header,注意Header只能發送一次,也就是只能調用一次start_response()函數。
start_response()函數接收兩個參數,一個是HTTP響應碼,一個是一組list表示的HTTP Header,每
個Header用一個包含兩個str的tuple表示。
通常情況下,都應該把Content-Type頭發送給瀏覽器。其他很多常用的HTTP Header也應該發送。
然后,函數的返回值b'<h1>Hello, web!</h1>'將作為HTTP響應的Body發送給瀏覽器。
有了WSGI,我們關心的就是如何從environ這個dict對象拿到HTTP請求信息,然后構造HTML,
通過start_response()發送Header,最后返回Body。</pre>
WSGI實例二
from wsgiref.simple_server import make_server
def f1():
f1=open("jd_index1.html","rb")
data1=f1.read()
return[data1]
def f2():
f2=open("tb_index.html","rb")
data2=f2.read()
return[data2]
def application(environ, start_response):
print(environ['PATH_INFO'])
path=environ['PATH_INFO']
start_response('200 OK',[('Content-Type','text/html')])
# 如果URL路徑為京東,執行函數1,返回京東主頁
if path=="/jingdong":
return f1()
# 如果URL路徑為淘寶,執行函數2,返回淘寶主頁
elif path=="/taobao":
return f2()
else:
return["<h1>404</h1>".encode("utf8")]
httpd = make_server('',8810, application)
print('Serving HTTP on port 8810...')
# 開始監聽HTTP請求:
httpd.serve_forever()
打開瀏覽器訪問相應的路徑
這里我們WSGI每次修改完數據后,都需要重新啟動該服務。
WSGI實例3 打印當前時間
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2016/11/24
# @Author : Jesson
import time
from wsgiref.simple_server import make_server
def f1(req):
print(req)
print(req["QUERY_STRING"])
f1=open("jd_index1.html","rb")
data1=f1.read()
return[data1]
def f2(req):
f2=open("tb_index.html","rb")
data2=f2.read()
return[data2]
def f3(req):#模版以及數據庫
f3=open("current_time.html","rb")
data3=f3.read()
times=time.strftime("%Y-%m-%d %X", time.localtime())
# 在前端相應的頁面 設置自定義模版語言'! !'
data3=str(data3,"utf8").replace("!time!",str(times))
return[data3.encode("utf8")]
def routers():
urlpatterns =(
('/jingdong',f1),
('/taobao',f2),
("/cur_time",f3)
)
return urlpatterns
def application(environ, start_response):
print(environ['PATH_INFO'])
path=environ['PATH_INFO']
start_response('200 OK',[('Content-Type','text/html')])
urlpatterns = routers()
func =None
for item in urlpatterns:
if item[0]== path:
func = item[1]
break
if func:
return func(environ)
else:
return["<h1>404</h1>".encode("utf8")]
httpd = make_server('',8828, application)
print('Serving HTTP on port 8828...')
# 開始監聽HTTP請求:
httpd.serve_forever()</pre>
其實,上邊幾個實例就相當於一個簡單的web框架。
MVC和MTV設計模式
MVC/MTV介紹
- MVC百度百科:全名Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典范,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯聚集到一個部件里面,在改進和個性化定制界面及用戶交互的同時,不需要重新編寫業務邏輯。
- 通俗解釋:一種文件的組織和管理形式!不要被縮寫嚇到了,這其實就是把不同類型的文件放到不同的目錄下的一種方法,然后取了個高大上的名字。當然,它帶來的好處有很多,比如前后端分離,松耦合等等,就不詳細說明了。
模型(model):定義數據庫相關的內容,一般放在models.py文件中。
視圖(view):定義HTML等靜態網頁文件相關,也就是那些html、css、js等前端的東西。
控制器(controller):定義業務邏輯相關,就是你的主要代碼。
- MTV: 有些WEB框架覺得MVC的字面意思很別扭,就給它改了一下。view不再是HTML相關,而是主業務邏輯了,相當於控制器。html被放在Templates中,稱作模板,於是MVC就變成了MTV。這其實就是一個文字游戲,和MVC本質上是一樣的,換了個名字和叫法而已,換湯不換葯。
Django的MTV模型組織
在web開發的項目文件中,相關目錄分開存放,必須要有機制將他們在內里進行耦合。在Django中,urls、orm、static、settings等起着重要的作用。
一個典型的業務流程是如下圖所示:
那么我們學Django學的是什么?
1. 目錄結構規范
2. urls路由方式
3. settings配置
4. ORM操作
5. 模板渲染
6. 其它
Django工作流程和命令行工具
基本流程
# Django
# 安裝: pip3 install django
添加環境變量
#1 創建project工程
django-admin startproject mysite
---mysite
---settings.py
---url.py
---wsgi.py
---manage.py(啟動文件)
#2 創建APP應用
python mannage.py startapp app01
#3 settings配置
TEMPLATES
STATICFILES_DIRS=(
os.path.join(BASE_DIR,"statics_自定義"),
)
STATIC_URL ='/static/'
# 我們只能用 STATIC_URL,但STATIC_URL會按着你的STATICFILES_DIRS去找
<script src="/statics_自定義/jquery-3.1.1.js"></script>
------這么寫可以實現,但是不規范-----不能直接用,必須用STATIC_URL ='/static/'
<script src="/static/jquery-3.1.1.js"></script>
采用第二種方式,后端的更改不會影響前端的引入,避免造成前端大量修改。
#4 根據需求設計代碼
url.py
view.py
#5 使用模版
render(req,"index.html")
#6 啟動項目
python manage.py runserver 127.0.0.1:8090
#7 連接數據庫,操作數據
model.py
django的命令行工具
django-admin.py 是Django的一個用於管理任務的命令行工具,manage.py是對django-admin.py的簡單包裝,每一個Django Project里都會有一個mannage.py。
- 創建一個django工程 : django-admin.py startproject mysite
當前目錄下會生成mysite的工程,目錄結構如下:
- manage.py ----- Django項目里面的工具,通過它可以調用django shell和數據庫等。
- settings.py ---- 包含了項目的默認設置,包括數據庫信息,調試標志以及其他一些工作的變量。
- urls.py ----- 負責把URL模式映射到應用程序。
- 在mysite目錄下創建blog應用: python manage.py startapp blog
-
啟動django項目:python manage.py runserver 8080
這樣我們的django就啟動起來了!當我們訪問:http://127.0.0.1:8080/時就可以看到: -
生成同步數據庫的腳本文件:python manage.py makemigrations
同步數據庫: python manage.py migrate
注意:
在開發過程中,數據庫同步誤操作之后,難免會遇到后面不能同步成功的情況,解決這個問題的一個簡單粗暴方法是把migrations目錄下的腳本(除init.py之外)全部刪掉,再把數據庫刪掉之后創建一個新的數據庫,數據庫同步操作再重新做一遍。
-
當我們訪問http://127.0.0.1:8080/admin/時,會出現:
所以我們需要為進入這個項目的后台創建超級管理員:python manage.py createsuperuser 終端執行上述命令,設置好用戶名和密碼后便可登錄啦!
設置用戶名的時候,郵箱email可以不用輸入,密碼有復雜度校驗,設置的復雜點。
另外,還會提示你,密碼會明文顯示。Warning: Password input may be echoed.
OK~ 如此優雅的后台界面! -
清空數據庫: python manage.py flush
- 查詢某個命令的詳細信息: django-admin.py help startapp
admin 是Django 自帶的一個后台數據庫管理系統。 - 啟動交互界面 :python manage.py shell
這個命令和 直接運行 python 進入 shell 的區別是:你可以在這個 shell 里面調用當前項目的 models.py 中的 API,對於操作數據,還有一些小測試非常方便。
######思考題補充:
(1) 如何更改正在運行的開發服務器端口
#進入django命令行,執行:
python manage.py runserver 加上新的端口號8080
Django URL (路由系統)
URL配置(URLconf)就像Django 所支撐網站的目錄。
它的本質是URL模式以及要為該URL模式調用的視圖函數之間的映射表;你就是以這種方式告訴Django,對於這個URL調用這段代碼,對於那個URL調用那段代碼。
`urlpatterns =[`
`url(正則表達式, views視圖函數,參數,別名),`
`]`
參數說明:
- 一個正則表達式字符串
- 一個可調用對象,通常為一個視圖函數或一個指定視圖函數路徑的字符串
- 可選的要傳遞給視圖函數的默認參數(字典形式)
- 一個可選的name參數
URL conf實例
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns =[
url(r'^articles/2003/$', views.special_case_2003),
#url(r'^articles/[0-9]{4}/$
', views.year_archive),
url(r'^articles/([0-9]{4})/$', views.year_archive), #no_named group
url(r'^articles/([0-9]{4})/([0-9]{2})/$
', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
Named groups
使用name組 來配置groups
python 測試代碼
import re
ret=re.search('(?P<id>\d{3})/(?P<name>\w{3})','weeew34ttt123/ooo')
print(ret.group())
print(ret.group('id'))
print(ret.group('name'))
輸出結果:
123/ooo
123
ooo
projects下的urls.py 配置練習:
from django.conf.urls import url
from.import views
urlpatterns =[
# 正則無名函數
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$
', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$
', views.article_detail),
]
url起別名,功能對比static名稱
urls.py配置
########################
urlpatterns =[
# 起別名
url(r'^index',views.index,name='bieming'),
url(r'^admin/', admin.site.urls),
]
# views.py文件中 添加index自定義方法
def index(req):
if req.method=='POST':
username=req.POST.get('username')
password=req.POST.get('password')
# 實際中,是需要從數據庫中讀取數據,這里為了演示效果,直接寫成固定的了。
if username=='jesson'and password=='123':
returnHttpResponse("登陸成功")
return render(req,'index.html')
# index.html
#####################
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
# 對應urls.py中的name='bieming'
<form action="{% url 'bieming' %}" method="post">
用戶名:<input type="text" name="username">
密碼:<input type="password" name="password">
<input type="submit" value="submit">
</form>
</body>
</html>
#######################
Django Views(視圖函數)
http請求中產生兩個核心對象:
http請求:HttpRequest對象
http響應:HttpResponse對象
所在位置:django.http
之前我們用到的參數request就是HttpRequest
檢測方法:isinstance(request,HttpRequest)
HttpRequest對象的屬性和方法:
path: 請求頁面的全路徑,不包括域名
method: 請求中使用的HTTP方法的字符串表示。全大寫表示。例如
if req.method=="GET":
do_something()
elseif req.method=="POST":
do_something_else()
GET: 包含所有HTTP GET參數的類字典對象
POST: 包含所有HTTP POST參數的類字典對象
服務器收到空的POST請求的情況也是可能發生的,也就是說,表單form通過HTTP POST方法提交請求,但是表單中可能沒有數據,因此不能使用if req.POST來判斷是否使用了HTTP POST 方法;應該使用 if req.method=="POST"
COOKIES: 包含所有cookies的標准Python字典對象;keys和values都是字符串。
FILES: 包含所有上傳文件的類字典對象;
FILES中的每一個Key都是
<input type="file" name="" />
標簽中name屬性的值,FILES中的每一個value同時也是一個標准的python字典對象,包含下面三個Keys:
filename: 上傳文件名,用字符串表示
content_type: 上傳文件的Content Type
content: 上傳文件的原始內容
user:
是一個django.contrib.auth.models.User對象,代表當前登陸的用戶。如果 訪問用戶當前沒有登陸,user將被初始化為django.contrib.auth.models.AnonymousUser的實例。
你可以通過user的is_authenticated()方法來辨別用戶是否登陸:
if req.user.is_authenticated();
只有激活Django中的AuthenticationMiddleware 時該屬性才可用session: 唯一可讀寫的屬性,代表當前會話的字典對象;自己有激活Django中的session支持時該屬性才可用。
方法
get_full_path(),用來返回包含查詢字符串的請求路徑。
比如:http://127.0.0.1:8000/index33/?name=123 ,req.get_full_path()得到的結果就是/index33/?name=123
req.path:/index33
HttpResponse對象:
對於HttpRequest對象來說,是由django自動創建的,但是,HttpResponse對象就必須我們自己創建。每個view請求處理方法必須返回一個HttpResponse對象。
HttpResponse類在django.http.HttpResponse
在HttpResponse對象上擴展的常用方法:
頁面渲染: render()#推薦使用
render_to_response()#不推薦使用

頁面跳轉: redirect("路徑")
locals():可以直接將函數本地作用域中所有的變量傳給模板,
補充render和redirect的區別:
--------url.py
url(r"login", views.login),
url(r"jesson_back", views.jesson_back),
-------views.py
def login(req):
if req.method=="POST":
if1:
# return redirect("/jesson_back/")
name="jesson"
return render(req,"my backend.html",locals())
return render(req,"login.html",locals())
def jesson_back(req):
name="jesson"
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的區別:
- 如果 render的頁面需要模板語言渲染,需要的將數據庫的數據加載到html,那么所有的這一部分除了寫在yuan_back的視圖函數中,必須還要寫在login中,代碼重復,沒有解耦.
- the most important: url沒有跳轉到/jesson_back/,而是還在/login/,所以當刷新后又得重新登錄.
自己構造response
Template基礎
模板系統的介紹
你可能已經注意到我們在例子視圖中返回文本的方式有點特別。 也就是說,HTML被直接硬編碼在 Python代碼之中。