Python學習--20 Web開發

HTTP格式

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。

WSGI接口

瞭解了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()函數由誰來調用呢?由於這裏的參數environstart_response咱們無法提供,返回的bytes也無法發給瀏覽器。

application()函數必須由WSGI服務器來調用。

有不少符合WSGI規範的服務器,Python提供了一個最簡單的WSGI服務器,能夠把咱們的Web應用程序跑起來。這個模塊叫wsgiref,它是用純Python編寫的WSGI服務器的參考實現。所謂「參考實現」是指該實現徹底符合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

web框架

實際項目開發中,咱們不可能使用swgiref來實現服務器,由於WSGI提供的接口雖然比HTTP接口高級了很多,但和Web App的處理邏輯比,仍是比較低級。咱們須要使用成熟的web框架。

因爲用Python開發一個Web框架十分容易,因此Python有上百個開源的Web框架。部分流行框架:

Flask:輕量級Web應用框架;
Django:全能型Web框架;
web.py:一個小巧的Web框架;
Bottle:和Flask相似的Web框架;
Tornado:Facebook的開源異步Web框架

Flask

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:

  • GET /:首頁,返回Home;
  • GET /login:登陸頁,顯示登陸表單;
  • POST /login:處理登陸表單,顯示登陸結果。

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分別有GETPOST兩種請求,能夠映射到兩個處理函數中。

使用模板

Web框架讓咱們從編寫底層WSGI接口拯救出來了,極大的提升了咱們編寫程序的效率。

但代碼裏嵌套太多的html讓整個代碼易讀性變差,使程序變得複雜。咱們須要將後端代碼邏輯與前端html分離出來。這就是傳說中的MVC:Model-View-Controller,中文名「模型-視圖-控制器」。

Controller負責業務邏輯,好比檢查用戶名是否存在,取出用戶信息等等;

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 }}的模板。
相關文章
相關標籤/搜索