HTTP協議是基於TCP和IP協議的。HTTP協議是一種文本協議。php
每一個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。html
HTTP請求格式:前端
GET:python
GET /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3
POST:web
POST /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3 body data goes here...
Header部分每行用\r\n
換行,每行裏鍵名和鍵值之間以:
分割,注意冒號後有個空格。數據庫
當遇到\r\n\r\n
時,Header部分結束,後面的數據所有是Body。json
HTTP響應格式:flask
200 OK Header1: Value1 Header2: Value2 Header3: Value3 body data goes here...
HTTP響應若是包含body,也是經過\r\n\r\n
來分隔的。後端
請再次注意,Body的數據類型由
Content-Type
頭來肯定,若是是網頁,Body就是文本,若是是圖片,Body就是圖片的二進制數據。瀏覽器
Body數據是能夠被壓縮的,若是看到Content-Encoding
,說明網站使用了壓縮。最多見的壓縮方式是gzip。
瞭解了HTTP協議的格式後,咱們能夠理解一個Web應用的本質:
一、瀏覽器發送HTTP請求給服務器;
二、服務器接收請求後,生成HTML;
三、服務器把生成的HTML做爲HTTP響應的body返回給瀏覽器;
四、瀏覽器接收到HTTP響應後,解析HTTP裏body並顯示。
接受HTTP請求、解析HTTP請求、發送HTTP響應實現起來比較複雜,有專門的服務器軟件來實現,例如Nginx,Apache。咱們要作的就是專一於生成HTML文檔。
Python裏也提供了一個比較底層的WSGI
(Web Server Gateway Interface)接口來實現TCP鏈接、HTTP原始請求和響應格式。實現了該接口定義的內容,就能夠實現相似Nginx、Apache等服務器的功能。
WSGI
接口定義要求Web開發者實現一個函數,就能夠響應HTTP請求,示例:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>']
這是一個簡單的文本版本的Hello, web!
。
上面的application()
函數就是符合WSGI
標準的一個HTTP處理函數,它接收兩個參數:
environ:一個包含全部HTTP請求信息的dict對象; start_response:一個發送HTTP響應的函數。
有了WSGI,咱們關心的就是如何從environ
這個dict對象拿到HTTP請求信息,而後構造HTML,經過start_response()
發送Header,最後返回Body。
整個application()
函數自己沒有涉及到任何解析HTTP的部分,即底層代碼不須要本身編寫,只負責在更高層次上考慮如何響應請求就能夠了。
可是,application()
函數由誰來調用呢?由於這裏的參數environ
、start_response
咱們無法提供,返回的bytes也無法發給瀏覽器。
application()
函數必須由WSGI
服務器來調用。
有不少符合WSGI規範的服務器,Python提供了一個最簡單的WSGI
服務器,能夠把咱們的Web應用程序跑起來。這個模塊叫wsgiref
,它是用純Python編寫的WSGI
服務器的參考實現。所謂「參考實現」是指該實現徹底符合WSGI
標準,可是不考慮任何運行效率,僅供開發和測試使用。
有了wsgiref
,咱們能夠很是快的實現一個簡單的web服務器:
# coding: utf-8 from wsgiref.simple_server import make_server def application(environ, start_response): print(environ) start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello web!</h1>'] print('HTTP server is running on http://127.0.0.1:9999') # 建立一個服務器,IP地址能夠爲空,端口是9999,處理函數是application: httpd = make_server('', 9999, application) httpd.serve_forever()
運行後訪問http://127.0.0.1:9999/
,會看到:
Hello web!
擴展知識:
make_server()
裏第一個參數若是爲空,實際等效於0.0.0.0
,表示監聽本地全部ip地址(包括127.0.0.1
)。
經過Chrome瀏覽器的控制檯,咱們能夠查看到瀏覽器請求和服務器響應信息:
# 請求信息: GET / HTTP/1.1 Host: 127.0.0.1:9999 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: _ga=GA1.1.948200530.1463673425 # 響應信息: HTTP/1.0 200 OK Date: Sun, 12 Feb 2017 05:20:31 GMT Server: WSGIServer/0.2 CPython/3.4.3 Content-Type: text/html Content-Length: 19 <h1>Hello web!</h1>
咱們再看終端的輸出信息:
$ python user_wsgiref_server.py HTTP server is running on http://127.0.0.1:9999 127.0.0.1 - - [12/Feb/2017 13:18:38] "GET / HTTP/1.1" 200 19 127.0.0.1 - - [12/Feb/2017 13:18:39] "GET /favicon.ico HTTP/1.1" 200 19
若是咱們打印environ
參數信息,會看到以下值:
{ "SERVER_SOFTWARE": "WSGIServer/0.1 Python/2.7.5", "SCRIPT_NAME": "", "REQUEST_METHOD": "GET", "SERVER_PROTOCOL": "HTTP/1.1", "HOME": "/root", "LANG": "en_US.UTF-8", "SHELL": "/bin/bash", "SERVER_PORT": "9999", "HTTP_HOST": "dev.banyar.cn:9999", "HTTP_UPGRADE_INSECURE_REQUESTS": "1", "XDG_SESSION_ID": "64266", "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "wsgi.version": "0", "wsgi.errors": "", "HOSTNAME": "localhost", "HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.8,en;q=0.6", "PATH_INFO": "/", "USER": "root", "QUERY_STRING": "", "PATH": "/usr/local/php/bin:/usr/local/php/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin", "HTTP_USER_AGENT": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", "HTTP_CONNECTION": "keep-alive", "SERVER_NAME": "localhost", "REMOTE_ADDR": "192.168.0.101", "wsgi.url_scheme": "http", "CONTENT_LENGTH": "", "GATEWAY_INTERFACE": "CGI/1.1", "CONTENT_TYPE": "text/plain", "REMOTE_HOST": "", "HTTP_ACCEPT_ENCODING": "gzip, deflate, sdch" }
爲顯示方便,已精簡部分信息。有了環境變量信息,咱們能夠對程序作些修改,能夠動態顯示內容:
def application(environ, start_response): print(environ['PATH_INFO']) start_response('200 OK', [('Content-Type', 'text/html')]) body = '<h1>Hello %s!</h1>' % (environ['PATH_INFO'][1:] or 'web' ) return [body.encode('utf-8')]
以上使用了environ
裏的PATH_INFO
的值。咱們在瀏覽器輸入http://127.0.0.1:9999/python
,瀏覽器會顯示:
Hello python!
終端的輸出信息:
$ python user_wsgiref_server.py HTTP server is running on http://127.0.0.1:9999 /python 127.0.0.1 - - [12/Feb/2017 13:54:57] "GET /python HTTP/1.1" 200 22 /favicon.ico 127.0.0.1 - - [12/Feb/2017 13:54:58] "GET /favicon.ico HTTP/1.1" 200 27
實際項目開發中,咱們不可能使用swgiref
來實現服務器,由於WSGI提供的接口雖然比HTTP接口高級了很多,但和Web App的處理邏輯比,仍是比較低級。咱們須要使用成熟的web框架。
因爲用Python開發一個Web框架十分容易,因此Python有上百個開源的Web框架。部分流行框架:
Flask:輕量級Web應用框架; Django:全能型Web框架; web.py:一個小巧的Web框架; Bottle:和Flask相似的Web框架; Tornado:Facebook的開源異步Web框架
Flask是一個使用 Python 編寫的輕量級 Web 應用框架。其 WSGI 工具箱採用 Werkzeug ,模板引擎則使用 Jinja2 。
安裝很是簡單:
pip install flask
控制檯輸出:
Collecting flask Downloading Flask-0.12-py2.py3-none-any.whl (82kB) 100% |████████████████████████████████| 92kB 163kB/s Collecting itsdangerous>=0.21 (from flask) Downloading itsdangerous-0.24.tar.gz (46kB) 100% |████████████████████████████████| 51kB 365kB/s Collecting click>=2.0 (from flask) Downloading click-6.7-py2.py3-none-any.whl (71kB) 100% |████████████████████████████████| 71kB 349kB/s Collecting Jinja2>=2.4 (from flask) Downloading Jinja2-2.9.5-py2.py3-none-any.whl (340kB) 100% |████████████████████████████████| 348kB 342kB/s Collecting Werkzeug>=0.7 (from flask) Downloading Werkzeug-0.11.15-py2.py3-none-any.whl (307kB) 100% |████████████████████████████████| 317kB 194kB/s Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask) Downloading MarkupSafe-0.23.tar.gz Building wheels for collected packages: itsdangerous, MarkupSafe Running setup.py bdist_wheel for itsdangerous ... done Successfully built itsdangerous MarkupSafe Installing collected packages: itsdangerous, click, MarkupSafe, Jinja2, Werkzeug, flask Successfully installed Jinja2-2.9.5 MarkupSafe-0.23 Werkzeug-0.11.15 click-6.7 flask-0.12 itsdangerous-0.24
安裝完flask會同時安裝依賴模塊:itsdangerous
, click
, MarkupSafe
, Jinja2
, Werkzeug
。
如今咱們來寫個簡單的登陸功能,主要是三個頁面:
home
字樣;/login
,有登陸表單;那麼一共有3個URL:
user_flask_app.py
# coding: utf-8 from flask import Flask from flask import request app = Flask(__name__) # 首頁 @app.route('/', methods=['GET', 'POST']) def home(): return '<h1>Home</h1><p><a href="/login">去登陸</a></p>' # 登陸頁 @app.route('/login', methods=['get']) def login(): return '''<form action="/login" method="post"> <p>用戶名:<input name="username"></p> <p>密碼:<input name="password" type="password"></p> <p><button type="submit">登陸</button></p> </form>''' # 登陸頁處理 @app.route('/login', methods=['post']) def do_login(): # 從request對象讀取表單內容: param = request.form if(param['username'] == 'yjc' and param['password'] == 'yjc'): return '歡迎您 %s !' % param['username'] else: return '用戶名或密碼不正確。' pass if __name__ == '__main__': # run()方法參數能夠都爲空,使用默認值 app.run('', 5000)
咱們能夠打開:http://localhost:5000/ 看效果。實際的Web App應該拿到用戶名和口令後,去數據庫查詢再比對,來判斷用戶是否能登陸成功。
經過代碼咱們能夠發現,Flask經過Python的裝飾器在內部自動地把URL和函數給關聯起來。
注意代碼裏同一個URL/login
分別有GET
和POST
兩種請求,能夠映射到兩個處理函數中。
Web框架讓咱們從編寫底層WSGI接口拯救出來了,極大的提升了咱們編寫程序的效率。
但代碼裏嵌套太多的html讓整個代碼易讀性變差,使程序變得複雜。咱們須要將後端代碼邏輯與前端html分離出來。這就是傳說中的MVC
:Model-View-Controller,中文名「模型-視圖-控制器」。
Controlle
r負責業務邏輯,好比檢查用戶名是否存在,取出用戶信息等等;
View
負責顯示邏輯,經過簡單地替換一些變量,View最終輸出的就是用戶看到的HTML。
'Model'負責數據的獲取,如從數據庫查詢用戶信息等。Model簡單能夠理解爲數據。
那麼就是:Model
獲取數據,Controlle
處理業務邏輯,View
顯示數據。
如今,咱們把上次直接輸出字符串做爲HTML的例子用MVC模式改寫一下:
# coding: utf-8 from flask import Flask,request,render_template app = Flask(__name__) # 首頁 @app.route('/', methods=['GET', 'POST']) def home(): return render_template('home.html') # 登陸頁 @app.route('/login', methods=['get']) def login(): return render_template('login.html', param = []) # 登陸頁處理 @app.route('/login', methods=['post']) def do_login(): param = request.form if(param['username'] == 'yjc' and param['password'] == 'yjc'): return render_template('welcome.html', username = param['username']) else: return render_template('login.html', msg = '用戶名或密碼不正確。', param = param) pass if __name__ == '__main__': app.run('', 5000)
Flask經過render_template()
函數來實現模板的渲染。和Web框架相似,Python的模板也有不少種。Flask默認支持的模板是jinja2
。
模板頁面:
home.html
<h1>Home</h1><p><a href="/login">去登陸</a></p>
login.html
{% if msg %} <p style="color:red;">{{ msg }}</p> {% endif %} <form action="/login" method="post"> <p>用戶名:<input name="username" value="{{ param.username }}"></p> <p>密碼:<input name="password" type="password"></p> <p><button type="submit">登陸</button></p> </form>
welcome.html
<p>歡迎您, {{ username }} !</p>
項目目錄:
user_flask_app |-- templates |-- home.html |-- login.html |-- welcome.html |-- user_flask_app.py
render_template()
函數第一個參數是模板名,默認是templates
目錄下。後面的參數是傳給模板的變量。變量的值能夠是數字、字符串、列表等等。
在Jinja2模板中,咱們用{{ name }}
表示一個須要替換的變量。不少時候,還須要循環、條件判斷等指令語句,在Jinja2中,用{% ... %}
表示指令。
好比循環輸出頁碼:
{% for i in page_list %} <a href="/page/{{ i }}">{{ i }}</a> {% endfor %}
除了Jinja2
,常見的模板還有:
Mako:用<% ... %>和${xxx}的一個模板; Cheetah:也是用<% ... %>和${xxx}的一個模板; Django:Django是一站式框架,內置一個用{% ... %}和{{ xxx }}的模板。