1. 程式人生 > >web框架和第一個Django實例

web框架和第一個Django實例

提交 標簽頁 error 我們 gate 網址 inf 工具 都是

Web框架本質

我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣我們就可以自己實現Web框架了。

在學之前我們要復習前面的知識

"""

s = "Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3\r\n\r\n自行車"

# 問題1:如何取到["Alex SB 哈哈\r\nx:1\r\ny:2\r\nz:3", "自行車"]

s1=(s.split("\r\n\r\n"))

print(s1)

# 問題2:如何在上面結果基礎上拿到["Alex", "SB", "哈哈"]?

s2=s1[0].split(‘\r\n‘)[0].split(‘ ‘)

print(s2)

# 問題3:如何在上面結果基礎上拿到"SB"?

print(s2[1])

# ------------------------------------------------------------------------------------------

# 有一個列表,他的內部是一些元祖,元祖的第一個元素是姓名,第二個元素是愛好。

# 現在我給你一個姓名,如"Egon",如果有這個姓名,就打印出他的愛好,沒有就打印查無此人。

list1 = [

("Alex", "燙頭"),

("Egon", "街舞"),

("Yuan", "喝茶")

]

for i in list1:

if "Egon"==i[0]:

print(i[1])

break

else:

print(‘查無此人‘)

# ------------------------------------------------------------------------------------------

# 我有一個HTML文件"login.html"

# 問題1:我如何讀取它的內容保存到變量html_s?

# with open(‘login.html‘,r,encoding=‘utf8‘)as f:

# html_s=f.read()

# 問題2:我如何讀取它的二進制內容保存到變量html_b?

# with open(‘login.html‘,rb)as f:

# html_b=f.read()

# ------------------------------------------------------------------------------------------

s2 = "Alex 花了一百萬買了輛電動車,真@@xx@@。"

# 問題1:如何把上面的s2轉變成"Alex 花了一百萬買了輛電動車,真SB。"

# print(s2.replace("真@@xx@@","真SB"))

半成品自定義web框架

技術分享圖片
import socket

sk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()

while True:
    conn, addr = sk.accept()
    data = conn.recv(8096)
    conn.send(b"OK")
    conn.close()
View Code

可以說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。

用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什麽數據?怎麽發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩麽?

所以,必須有一個統一的規則,讓大家發送消息、接收消息的時候有個格式依據,不能隨便寫。

這個規則就是HTTP協議,以後瀏覽器發送請求信息也好,服務器回復響應信息也罷,都要按照這個規則來。

HTTP協議主要規定了客戶端和服務器之間的通信格式,那HTTP協議是怎麽規定消息格式的呢?

讓我們首先打印下我們在服務端接收到的消息是什麽。

然後我們再看一下我們訪問博客園官網時瀏覽器收到的響應信息是什麽。

響應相關信息可以在瀏覽器調試窗口的network標簽頁中看到。

技術分享圖片

點擊view source之後顯示如下圖:

技術分享圖片

我們發現收發的消息需要按照一定的格式來,這裏就需要了解一下HTTP協議了。

HTTP協議介紹

HTTP協議對收發消息的格式要求

每個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type表明響應的內容格式。如 text/html表示HTML網頁。

HTTP GET請求的格式:

技術分享圖片

HTTP響應的格式:

技術分享圖片

經過上面的補充學習,我們知道了要想讓我們自己寫的web server端正經起來,必須要讓我們的Web server在給客戶端回復消息的時候按照HTTP協議的規則加上響應狀態行,這樣我們就實現了一個正經的Web框架了。

技術分享圖片
import socket

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    print(data)

    conn.send(bHTTP/1.1 200 OK\r\n\r\n<h1>O98K</h1>)  # 回復消息

    conn.close()

 
View Code

根據不同的路徑返回不同的內容

技術分享圖片
"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

"""

import socket

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    # print(data)

    # 從瀏覽器發送消息中,拿到用戶訪問的路徑

    data_str = str(data, encoding="utf8")

    # print(data_str)

    url = data_str.split("\r\n")[0].split(" ")[1]

    print(url)

 

    if url == "/home/":

       msg = b<h1>home page</h1>

    elif url == "/index/":

        msg = b<h1>index page</h1>

    else:

        msg = b<h1>404</h1>

 

    conn.send(bHTTP/1.1 200 OK\r\n\r\n)

    conn.send(msg)

    conn.close()
View Code

根據瀏覽器訪問的路徑的不同,返回不同的內容

技術分享圖片
"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

"""

import socket

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

def home(url):

 

 

    return b<h1>home page</h1>

 

 

def index(url):

    return b<h1>index page</h1>

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    # print(data)

    # 從瀏覽器發送消息中,拿到用戶訪問的路徑

    data_str = str(data, encoding="utf8")

    # print(data_str)

    url = data_str.split("\r\n")[0].split(" ")[1]

    print(url)

 

    if url == "/home/":

       msg = home(url)

    elif url == "/index/":

        msg = index(url)

    else:

        msg = b<h1>404</h1>

 

    conn.send(bHTTP/1.1 200 OK\r\n\r\n)

    conn.send(msg)

    conn.close()
View Code

根據瀏覽器訪問的路徑的不同,返回不同的內容在升級

技術分享圖片
"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

優化頻繁的if判斷

 

"""

import socket

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

def home(url):

    s = "this is {} page!".format(url)

    return bytes(s, encoding="utf8")

 

 

def index(url):

    return b<h1>index page</h1>

 

 

def user(url):

    return bhehe

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    # print(data)

    # 從瀏覽器發送消息中,拿到用戶訪問的路徑

    data_str = str(data, encoding="utf8")

    # print(data_str)

    url = data_str.split("\r\n")[0].split(" ")[1]

    print(url)

 

    url2func = [

        ("/index/", index),

        ("/home/", home),

        ("/user/", user),

    ]

    func = None

    for i in url2func:

        if url == i[0]:

            func = i[1]  # 拿到將要執行的函數

            break

    else:

        func = None

 

    if func:

        msg = func(url)  # 執行對應的函數

    else:

        msg = b<h1>404</h1>  # 找不到要執行的函數就返回404

 

 

    conn.send(bHTTP/1.1 200 OK\r\n\r\n)

    conn.send(msg)

    conn.close()
View Code

返回具體的HTML文件

技術分享圖片
"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

優化頻繁的if判斷

返回具體的HTML文件

 

"""

import socket

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

 

 

def home(url):

    s = "this is {} page!".format(url)

    return bytes(s, encoding="utf8")

 

 

def index(url):

    return b<h1>index page</h1>

 

 

def user(url):

    return bhehe

 

def login(url):

    with open("login.html", "rb") as f:

        return f.read()

 

# url和將要執行的函數的對應關系

url2func = [

    ("/index/", index),

    ("/home/", home),

    ("/user/", user),

    ("/login/", login),

]

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    # print(data)

    # 從瀏覽器發送消息中,拿到用戶訪問的路徑

    data_str = str(data, encoding="utf8")

    # print(data_str)

    url = data_str.split("\r\n")[0].split(" ")[1]

    print(url)

 

 

    func = None

    for i in url2func:

        if url == i[0]:

            func = i[1]  # 拿到將要執行的函數

            break

    else:

        func = None

 

    if func:

        msg = func(url)  # 執行對應的函數

    else:

        msg = b<h1>404</h1>  # 找不到要執行的函數就返回404

 

    # 按照HTTP協議的格式要求 回復消息

    conn.send(bHTTP/1.1 200 OK\r\n\r\n)  # 發送狀態行

    conn.send(msg)  # 發送響應體

    conn.close()
View Code

返回動態的HTML文件

技術分享圖片
"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

優化頻繁的if判斷

返回具體的HTML文件

實現不同的用戶得到不同的HTML頁面

 

"""

import socket

import time

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((127.0.0.1, 8888))

server.listen()

 

 

 

def home(url):

    s = "this is {} page!".format(url)

    return bytes(s, encoding="utf8")

 

 

def index(url):

    return b<h1>index page</h1>

 

 

def user(url):

    # 不同的用戶得到的頁面上顯示不同的時間

    c_time = str(time.time())

    with open("user.html", "r") as f:

        data_s = f.read()

        data_s = data_s.replace("@@xx@@", c_time)  # 替換後的字符串數據

        return bytes(data_s, encoding="utf8")

 

 

def login(url):

    with open("login.html", "rb") as f:

        return f.read()

 

# url和將要執行的函數的對應關系

url2func = [

    ("/index/", index),

    ("/home/", home),

    ("/user/", user),

    ("/login/", login),

]

 

while 1:

    conn, addr = server.accept()

    data = conn.recv(8096)  # 收消息

    # print(data)

    # 從瀏覽器發送消息中,拿到用戶訪問的路徑

    data_str = str(data, encoding="utf8")

    # print(data_str)

    url = data_str.split("\r\n")[0].split(" ")[1]

    print(url)

 

 

    func = None

    for i in url2func:

        if url == i[0]:

            func = i[1]  # 拿到將要執行的函數

            break

    else:

        func = None

 

    if func:

        msg = func(url)  # 執行對應的函數

    else:

        msg = b<h1>404</h1>  # 找不到要執行的函數就返回404

 

    # 按照HTTP協議的格式要求 回復消息

    conn.send(bHTTP/1.1 200 OK\r\n\r\n)  # 發送狀態行

    conn.send(msg)  # 發送響應體

    conn.close()
View Code

服務器程序和應用程序

對於真實開發中的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開發環境用的就是這個模塊來做服務器。

從這繼續...

wsgiref

我們利用wsgiref模塊來替換我們自己寫的web框架的socket server部分:

"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

優化頻繁的if判斷

返回具體的HTML文件

實現不同的用戶得到不同的HTML頁面

技術分享圖片
"""

 

import time

from wsgiref.simple_server import make_server

 

 

def home(url):

    s = "this is {} page!".format(url)

    return bytes(s, encoding="utf8")

 

 

def index(url):

    return b‘<h1>index page</h1>‘

 

 

def user(url):

    # 不同的用戶得到的頁面上顯示不同的時間

    c_time = str(time.time())

    with open("user.html", "r") as f:

        data_s = f.read()

        data_s = data_s.replace("@@xx@@", c_time)  # 替換後的字符串數據

        return bytes(data_s, encoding="utf8")

 

 

def login(url):

    with open("login.html", "rb") as f:

        return f.read()

 

# url和將要執行的函數的對應關系

url2func = [

    ("/index/", index),

    ("/home/", home),

    ("/user/", user),

    ("/login/", login),

]

 

# 按照wsgiref的要求定義一個run_server函數

def run_server(environ, start_response):

    """

 

    :param environ: 跟請求相關的參數

    :param start_response:

    :return:

    """

    # 設置HTTP響應的狀態碼和頭信息

    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])

    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url

 

    func = None

    for i in url2func:

        if url == i[0]:

            func = i[1]  # 拿到將要執行的函數

            break

    else:

        func = None

 

    if func:

        msg = func(url)  # 執行對應的函數

    else:

        msg = b‘<h1>404</h1>‘  # 找不到要執行的函數就返回404

 

    return [msg, ]

 

if __name__ == ‘__main__‘:

    httpd = make_server(‘127.0.0.1‘, 8090, run_server)

    print("我在8090等你哦...")

    httpd.serve_forever()

 
View Code

jinja2

上面的代碼實現了一個簡單的動態,我完全可以從數據庫中查詢數據,然後去替換我html中的對應內容,然後再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這裏用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2

下載jinja2:

pip install jinja2

"""

根據瀏覽器訪問的路徑的不同,返回不同的內容

將不同頁面的處理代碼封裝到函數中

優化頻繁的if判斷

返回具體的HTML文件

實現不同的用戶得到不同的HTML頁面

技術分享圖片
 

"""

 

import time

from wsgiref.simple_server import make_server

from jinja2 import Template

import pymysql

 

 

def home(url):

    s = "this is {} page!".format(url)

    return bytes(s, encoding="utf8")

 

 

def index(url):

    return b‘<h1>index page</h1>‘

 

 

def user(url):

    # 從數據庫裏面去到所有的用戶信息,

    conn = pymysql.connect(

        host=‘127.0.0.1‘,

        port=3306,

        user=‘root‘,

        password=‘123‘,

        database=‘day61‘,

        charset=‘utf8‘

    )

    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

    cursor.execute(‘select * from user‘)

    ret = cursor.fetchall()

    print(ret)

    # 在頁面上顯示出來

 

    with open("user.html", "r", encoding="utf8") as f:

        data_s = f.read()

 

        template = Template(data_s)  # 生成一個模板文件實例

 

        msg = template.render({"user_list": ret})  # 把數據填充到模板裏面

 

 

        return bytes(msg, encoding="utf8")

 

 

def login(url):

    with open("login.html", "rb") as f:

        return f.read()

 

# url和將要執行的函數的對應關系

url2func = [

    ("/index/", index),

    ("/home/", home),

    ("/user/", user),

    ("/login/", login),

]

 

# 按照wsgiref的要求定義一個run_server函數

def run_server(environ, start_response):

    """

 

    :param environ: 跟請求相關的參數

    :param start_response:

    :return:

    """

    # 設置HTTP響應的狀態碼和頭信息

    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ])

    url = environ[‘PATH_INFO‘]  # 取到用戶輸入的url

 

    func = None

    for i in url2func:

        if url == i[0]:

            func = i[1]  # 拿到將要執行的函數

            break

    else:

        func = None

 

    if func:

        msg = func(url)  # 執行對應的函數

    else:

        msg = b‘<h1>404</h1>‘  # 找不到要執行的函數就返回404

 

    return [msg, ]

 

if __name__ == ‘__main__‘:

    httpd = make_server(‘127.0.0.1‘, 8090, run_server)

    print("我在8090等你哦...")

    httpd.serve_forever()
View Code

創建一個測試的user表:

CREATE TABLE user(
id int auto_increment PRIMARY KEY,
name CHAR(10) NOT NULL,
hobby CHAR(20) NOT NULL
)engine=innodb DEFAULT charset=UTF8;

模板的原理就是字符串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。

Django

技術分享圖片

Django官網下載頁面

安裝(安裝最新LTS版):

pip3 install django==1.11.9

技術分享圖片

創建一個django項目:

下面的命令創建了一個名為"mysite"的Django 項目:

django-admin startproject mysite

技術分享圖片

技術分享圖片

技術分享圖片

目錄介紹:

mysite/
├── manage.py # 管理文件
└── mysite # 項目目錄
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函數的對應關系
└── wsgi.py # runserver命令就使用wsgiref模塊做簡單的web server

運行Django項目:

python manage.py runserver 127.0.0.1:8000

技術分享圖片

模板文件配置:

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‘,
],
},
},
]

靜態文件配置:

STATIC_URL = ‘/static/‘ # HTML中使用的靜態文件夾前綴
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), # 靜態文件存放位置
]

看不明白?有圖有真相:

技術分享圖片

剛開始學習時可在配置文件中暫時禁用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": ["燙頭", "泡吧"]})

redirect

接受一個URL參數,表示跳轉到指定的URL。

例如:

def index(request):
# 業務邏輯代碼
return redirect("/home/")

重定向是怎麽回事?

技術分享圖片

課後練習:

Django版登錄

啟動Django報錯:

Django 啟動時報錯 UnicodeEncodeError ...

報這個錯誤通常是因為計算機名為中文,改成英文的計算機名重啟下電腦就可以了。

web框架和第一個Django實例