01-01 Web應用

一 Web應用的組成

 

接下來我們學習的目的是爲了開發一個Web應用程序,而Web應用程序是基於B/S架構的,其中B指的是瀏覽器,負責向S端發送請求信息,而S端會根據接收到的請求信息返回相應的數據給瀏覽器,需要強調的一點是:S端由server和application兩大部分構成,如圖所示:

上圖:Web應用組成

Web應用組成

二 開發一個Web應用

我們無需開發瀏覽器(本質即套接字客戶端),只需要開發S端即可,S端的本質就是用套接字實現的,如下

# S端
import socket

def make_server(ip, port, app):  # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        # 1、接收瀏覽器發來的請求信息
        recv_data = conn.recv(1024)
        # print(recv_data.decode('utf-8'))

        # 2、將請求信息直接轉交給application
        res = app(recv_data)

        # 3、向瀏覽器返回消息(此處並沒有按照http協議返回)
        conn.send(res)
        
        conn.close()

def app(environ):  # 代表application
    # 處理業務邏輯
    return b'hello world'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)  # 在客戶端瀏覽器輸入:http://127.0.0.1:8008 會報錯(注意:請使用谷歌瀏覽器)

目前S端已經可以正常接收瀏覽器發來的請求消息了,但是瀏覽器在接收到S端回覆的響應消息b'hello world'時卻無法正常解析 ,因爲瀏覽器與S端之間收發消息默認使用的應用層協議是HTTP,瀏覽器默認會按照HTTP協議規定的格式發消息,而S端也必須按照HTTP協議的格式回消息纔行,所以接下來我們詳細介紹HTTP協議

HTTP協議詳解鏈接地址:http://www.cnblogs.com/linhaifeng/articles/8243379.html

 

S端修訂版本:處理HTTP協議的請求消息,並按照HTTP協議的格式回覆消息

# S端
import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        # 1、接收並處理瀏覽器發來的請求信息
        # 1.1 接收瀏覽器發來的http協議的消息
        recv_data = conn.recv(1024)

        # 1.2 對http協議的消息加以處理,簡單示範如下
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        # 2:將請求信息處理後的結果environ交給application,這樣application便無需再關注請求信息的處理,可以更加專注於業務邏輯的處理
        res = app(environ)

        # 3:按照http協議向瀏覽器返回消息
        # 3.1 返回響應首行
        conn.send(b'HTTP/1.1 200 OK\r\n')
        # 3.2 返回響應頭(可以省略)
        conn.send(b'Content-Type: text/html\r\n\r\n')
        # 3.3 返回響應體
        conn.send(res)

        conn.close()

def app(environ): # 代表application
    # 處理業務邏輯
    return b'hello world'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app) 

此時,重啓S端後,再在客戶端瀏覽器輸入:http://127.0.0.1:8008 便可以看到正常結果hello world了。

 

我們不僅可以回覆hello world這樣的普通字符,還可以夾雜html標籤,瀏覽器在接收到消息後會對解析出的html標籤加以渲染

# S端
import socket

def make_server(ip, port, app): 
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()
        
        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 返回html標籤
    return b'<h1>hello web</h1><img src="https://www.baidu.com/img/bd_logo1.png"></img>'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

更進一步我們還可以返回一個文件,例如timer.html,內容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>{{ time }}</h2>
</body>
</html>

S端程序如下

# S端
import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 處理業務邏輯:打開文件,讀取文件內容並返回
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

上述S端爲瀏覽器返回的都是靜態頁面(內容都固定的),我們還可以返回動態頁面(內容是變化的)

# S端
import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 處理業務邏輯
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()

    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    data = data.replace('{{ time }}', now)  # 字符串替換
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app) # 在瀏覽器輸入http://127.0.0.1:8008,每次刷新都會看到不同的時間

 

三 Web框架的由來

綜上案例我們可以發現一個規律,在開發S端時,server的功能是複雜且固定的(處理socket消息的收發和http協議的處理),而app中的業務邏輯卻各不相同(不同的軟件就應該有不同的業務邏輯),重複開發複雜且固定的server是毫無意義的,有一個wsgiref模塊幫我們寫好了server的功能,這樣我們便只需要專注於app功能的編寫即可

 

# wsgiref實現了server,即make_server
from wsgiref.simple_server import make_server 

def app(environ, start_response): # 代表application 
    # 1、返回http協議的響應首行和響應頭信息
    start_response('200 OK', [('Content-Type', 'text/html')])
    
    # 2、處理業務邏輯:根據請求url的不同返回不同的頁面內容
    if environ.get('PATH_INFO') == '/index':
        with open('index.html','r', encoding='utf-8') as f:
            data=f.read()
    elif environ.get('PATH_INFO') == '/timer':
        with open('timer.html', 'r', encoding='utf-8') as f:
            data = f.read()
        import time
        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        data = data.replace('{{ time }}', now)  # 字符串替換
    else:
        data='<h1>Hello, web!</h1>'
    
    # 3、返回http響應體信息,必須是bytes類型,必須放在列表中
    return [data.encode('utf-8')]

if __name__ == '__main__':
    # 當接收到請求時,wsgiref模塊會對該請求加以處理,然後後調用app函數,自動傳入兩個參數:
    # 1 environ是一個字典,存放了http的請求信息
    # 2 start_response是一個功能,用於返回http協議的響應首行和響應頭信息
    s = make_server('', 8011, app) # 代表server
    print('監聽8011')
    s.serve_forever() # 在瀏覽器輸入http://127.0.0.1:8011/index和http://127.0.0.1:8011/timer會看到不同的頁面內容

timer.html已經存在了,新增的index.html頁面內容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>主頁</h1>
</body>
</html>

上述案例中app在處理業務邏輯時需要根據不同的url地址返回不同的頁面內容,當url地址越來越多,需要寫一堆if判斷,代碼不夠清晰,耦合程度高,所以我們做出以下優化

# 處理業務邏輯的函數
def index(environ):
    with open('index.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')


def timer(environ):
    import datetime
    now = datetime.datetime.now().strftime('%y-%m-%d %X')
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
    data = data.replace('{{ time }}', now)
    return data.encode('utf-8')


# 路徑跟函數的映射關係
url_patterns = [
    ('/index', index),
    ('/timer', timer),
]

from wsgiref.simple_server import make_server


def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])

    # 拿到請求的url並根據映射關係url_patters執行相應的函數
    reuqest_url = environ.get('PATH_INFO')
    for url in url_patterns:
        if url[0] == reuqest_url:
            data = url[1](environ)
            break
    else:
        data = b'404'

    return [data]


if __name__ == '__main__':
    s = make_server('', 8011, app)
    print('監聽8011')
    s.serve_forever()

隨着業務邏輯複雜度的增加,處理業務邏輯的函數以及url_patterns中的映射關係都會不斷地增多,此時仍然把所有代碼都放到一個文件中,程序的可讀性和可擴展性都會變得非常差,所以我們應該將現有的代碼拆分到不同文件中

插圖:

project

mysite # 文件夾
    ├── app01 # 文件夾
    │   └── views.py
    ├── mysite # 文件夾
    │   └── urls.py
    └── templates # 文件夾
    │   ├── index.html
    │   └── timer.html
    ├── main.py

views.py 內容如下:

# 處理業務邏輯的函數
def index(environ):
    with open('templates/index.html', 'r',encoding='utf-8') as f: # 注意文件路徑
        data = f.read()
    return data.encode('utf-8')

def timer(environ):
    import datetime
    now = datetime.datetime.now().strftime('%y-%m-%d %X')
    with open('templates/timer.html', 'r',encoding='utf-8') as f: # 注意文件路徑
        data = f.read()
    data=data.replace('{{ time }}',now)
    return data.encode('utf-8')

urls.py內容如下:

# 路徑跟函數的映射關係
from app01.views import * # 需要導入views中的函數

url_patterns = [
    ('/index', index),
    ('/timer', timer),
]

main.py 內容如下:

from wsgiref.simple_server import make_server
from mysite.urls import url_patterns  # 需要導入urls中的url_patterns


def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])

    # 拿到請求的url並根據映射關係url_patters執行相應的函數
    reuqest_url = environ.get('PATH_INFO')
    for url in url_patterns:
        if url[0] == reuqest_url:
            data = url[1](environ)
            break
    else:
        data = b'404'

    return [data]


if __name__ == '__main__':
    s = make_server('', 8011, app)
    print('監聽8011')
    s.serve_forever()

至此,我們就針對application的開發自定義了一個框架,所以說框架的本質就是一系列功能的集合體、不同的功能放到不同的文件中。有了該框架,可以讓我們專注於業務邏輯的編寫,極大的提高了開發web應用的效率(開發web應用的框架可以簡稱爲web框架),比如我們新增一個業務邏輯,要求爲:瀏覽器輸入http://127.0.0.1:8011/home 就能訪問到home.html頁面,在框架的基礎上具體開發步驟如下:

步驟一:在templates文件夾下新增home.html

步驟二:在urls.py的url_patterns中新增一條映射關係

url_patterns = [
    ('/index', index),
    ('/timer', timer),
    ('/home', home), # 新增的映射關係
]

步驟三:在views.py中新增一個名爲home的函數

def home(environ):
    with open('templates/home.html', 'r',encoding='utf-8') as f: 
        data = f.read()
    return data.encode('utf-8')

我們自定義的框架功能有限,在Python中我們可以使用別人開發的、功能更強大的Django框架

 

四 Django框架的安裝與使用

在使用Django框架開發web應用程序時,開發階段同樣依賴wsgiref模塊來實現Server的功能,我們使用Django框架是爲了快速地開發application

 

4.1 安裝

目前在企業開發中Django框架使用的主流版本爲1.11.x版本,最新版本爲2.x,我們主要講解1.11版本,同時會涉及2.x的新特性

pip3 install django==1.11.18 # 在命令行執行該命令

4.2 使用

4.2.1 快速創建並啓動Django項目

 

如果使用的是我們自定義的框架來開發web應用,需要事先生成框架包含的一系列基礎文件,然後在此基礎上進行開發。

如果使用的是Django框架來開發web應用,同樣需要事先生成Django框架包含的一系列基礎文件,然後在此基礎上進行開發。

但Django框架更爲方便的地方在於它已經爲我們提供了一系列命令來幫我們快速地生成這一系列基礎文件

# 在命令行執行以下指令,會在當前目錄生成一個名爲mysite的文件夾,該文件夾中包含Django框架的一系列基礎文件
django-admin startproject mysite

創建功能模塊

cd mysite # 切換到mysite目錄下,執行以下命令
python manage.py startapp app01 # 創建功能模塊app01,此處的startapp代表創建application下的一個功能模塊。例如我們要開發application是京東商城,京東商城這個大項目下有一個訂單管理模塊,我們可以將其命名爲app01

運行

python manage.py runserver 8001 # 在瀏覽器輸入:http://127.0.0.1:8001 會看到Django的歡迎頁面。

4.2.2 Django項目目錄結構

 

截目錄樹的圖(按照下述目錄截圖)

mysite # 文件夾
    ├── app01 # 文件夾
    │   └── migrations # 文件夾
    │   └── admin.py
    │   └── apps.py
    │   └── models.py
    │   └── tests.py
    │   └── views.py
    ├── mysite # 文件夾
    │   └── settings.py
    │   └── urls.py
    │   └── wsgi.py
    └── templates # 文件夾
    ├── manage.py

關鍵文件介紹

-manage.py---項目入口,執行一些命令
-項目名
    -settings.py  全局配置信息
    -urls.py      總路由,請求地址跟視圖函數的映射關係
-app名字
    -migrations   數據庫遷移的記錄
    -models.py    數據庫表模型
    -views.py     處理業務邏輯的函數,簡稱視圖函數

4.2.3 基於Pycharm創建Django項目

基於pycharm創建django項目

4.2.4 基於Django實現的一個簡單示例

(1)url.py
from django.contrib import admin
from django.conf.urls import url
#導入views模塊
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # r'^index/$' 會正則匹配url地址的路徑部分
    url(r'^index/$',views.index), # 新增地址http://127.0.0.1:8001/index/與index函數的映射關係
]
(2)視圖
from django.shortcuts import render

# 必須定義一個request形參,request相當於我們自定義框架時的environ參數
def index(request):
    import datetime
    now=datetime.datetime.now()
    ctime=now.strftime("%Y-%m-%d %X")

    return render(request,"index.html",{"ctime":ctime}) # render會讀取templates目錄下的index.html文件的內容並且用字典中的ctime的值替換模版中的{{ ctime }}
(3)模版

在templates目錄下新建文件index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h4>當前時間:{{ ctime }}</h4>

</body>
</html>

測試:

python manage.py runserver 8001 # 在瀏覽器輸入:http://127.0.0.1:8001/index/ 會看到當前時間。

4.2.5 Django框架的分層與請求生命週期

綜上,我們使用Django框架就是爲了開發application,而application的工作過程本質就是根據不同的請求返回不同的數據,Django框架將這個工作過程細分爲如下四層去實現

1、路由層(根據不同的地址執行不同的視圖函數,詳見urls.py)

2、視圖層(定義處理業務邏輯的視圖函數,詳見views.py)

3、模型層 (跟數據庫打交道的,詳解models.py)

4、模板層(待返回給瀏覽器的html文件,詳見templates)

django請求生命週期

django請求生命週期

這體現了一種解耦合的思想,下面我們開始詳細介紹每一層

 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章