Django之web框架原理

Web框架原理

咱們能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣咱們就能夠本身實現Web框架了。css

先寫一個html

原始的web框架

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    print(data)  # 打印瀏覽器發過來的消息並分析
    conn.send(b'ok')
    conn.close()

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

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

因此,必須有一個統一的規則,讓你們發送消息、接收消息的時候有個格式依據,不能隨便寫。mysql

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

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

運行上面的代碼,在瀏覽器輸入服務器的地址和端口sql

獲得瀏覽器發過來的data的打印結果:chrome

# data結果
'''
1.請求首行:
b'GET / HTTP/1.1\r\n
2.請求體:一大堆K:V鍵值對
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 chrome-extension\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Sec-Fetch-Site: cross-site\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7\r\n
\r\n
3.請求體:
    這裏是請求數據,get請求沒有,post請求才有
'''

咱們發現收發的消息須要按照必定的格式來,數據庫

1.數據格式
get請求格式
請求首行(請求方式,協議版本。。。)
請求頭(一大堆k:v鍵值對)
\r\n
請求體(真正的數據 發post請求的時候纔有 若是是get請求不會有)
響應格式
響應首行
響應頭
\r\n
響應體

HTTP GET請求的格式:

img

HTTP響應的格式:

img

這裏就須要瞭解一下HTTP協議了。

HTTP協議介紹

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

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

HTTP協議特色:
超文本傳輸協議

1.四大特性
1.基於TCP/IP之上做用於應用層
2.基於請求響應
3.無狀態 cookie session token...
4.無鏈接

2.響應狀態碼
用特定的數字表示一些意思
1XX:服務端已經成功接收到了你的數據 正在處理 你能夠繼續提交其餘數據
2XX:服務端成功響應(200請求成功)
3XX:重定向
4XX:請求錯誤(404 請求資源不存在 403 拒絕訪問)
5XX:服務器內部錯誤(500 )

自定義web框架完整版

若是咱們想要本身寫的web server服務端真正運行起來,達到一種請求與響應的對應關係,咱們必需要在sercer服務端給客戶端回覆消息的時候,按照HTTP協議的規則加上響應狀態行 ,就是 協議版本+狀態碼+狀態描述符:b'HTTP/1.1 200 OK\r\n\r\n'

以下例子:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    print(data)

    # 須要向客服端發送響應頭,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(b'hello world')
    conn.close()

根據不一樣的路徑返回不一樣的內容的Web服務端

若是咱們在瀏覽器客戶端輸入:http://127.0.0.1:8080/home,瀏覽器的頁面顯示就爲home,那麼能夠這樣作:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽

while True:
    # 等待鏈接
    conn, addr = sk.accept()
    # 接收客戶端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 請求首行的進行切割拿到url url是咱們從瀏覽器發來的消息分離出來的訪問路徑
    url=data1.split(' ')[1]
    # print(url)

    # 必須遵循HTTP協議,須要向客服端發送狀態行,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

    # 根據不一樣的路徑返回不一樣內容
    if url == '/index':
        response = b'index'
    elif url == '/home':
        response = b'home'
    else:
        response = b'404 not found!!!'


    conn.send(response)
    conn.close()

不一樣路徑不一樣內容-函數版

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽

# 將返回不一樣的內容部分封裝成函數
def index(url):
    res = f'這是{url}頁面|'
    return bytes(res,encoding='gbk')
    # return res.encode('utf-8')

def home(url):
    res = f'這是{url}頁面|'
    return bytes(res,encoding='gbk')
    # return res.encode('utf-8')

while True:
    # 等待鏈接
    conn, addr = sk.accept()
    # 接收客戶端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 請求首行的進行切割拿到url url是咱們從瀏覽器發來的消息分離出來的訪問路徑
    url=data1.split(' ')[1]
    # print(url)



    # 根據不一樣的路徑返回不一樣內容
    if url == '/index':
        response = index(url)
    elif url == '/home':
        response = home(url)
    else:
        response = b'404 not found!!!'

    # 必須遵循HTTP協議,須要向客服端發送狀態行,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

不一樣路徑不一樣內容-函數進階版

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽

# 定義一個url和實際要執行的函數的對應關係

def home(url):
    res = bytes(url, encoding='utf8')
    return res

def index(url):
    res = bytes(url, encoding='utf8')
    return res


# 定義一個url和要執行函數對應關係的字典
dt = {
    '/index':index,
    '/home':home
}

while True:
    # 等待鏈接
    conn, addr = sk.accept()
    # 接收客戶端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 請求首行的進行切割拿到url url是咱們從瀏覽器發來的消息分離出來的訪問路徑
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根據不一樣的路徑返回不一樣內容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必須遵循HTTP協議,須要向客服端發送狀態行,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

返回具體的HTML頁面

首先建立咱們須要的html頁面,而後把在代碼裏面以rb模式讀取出來,發送到瀏覽器

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽

def home(url):
    with open('home頁面.html','rb') as fr:
        res = fr.read()
    return res

def index(url):
    with open('index頁面.html', 'rb') as fr:
        res = fr.read()
    return res


# 定義一個url和實際要執行的函數的對應關係
dt = {
    '/index':'index',
    '/home':'home'
}

while True:
    # 等待鏈接
    conn, addr = sk.accept()
    # 接收客戶端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 請求首行的進行切割拿到url url是咱們從瀏覽器發來的消息分離出來的訪問路徑
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根據不一樣的路徑返回不一樣內容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必須遵循HTTP協議,須要向客服端發送狀態行,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

返回動態的網頁

上面的網頁是不會變化的,如何實現獲得一個動態的網站呢?下面作個例子:每次刷新都在獲取新的時間,模擬動態的數據

import socket
import datetime

sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 綁定IP和端口
sk.listen(5) # 監聽

def home(url):
    with open('get_time.html', 'r',encoding='utf8') as fr:
        res = fr.read()
        now = datetime.datetime.now().strftime("%Y-%m-%d %X")
        # 在網頁中定義好特殊符號,用動態的數據替換特殊字符
        res = res.replace('*time*',now).encode('utf8')
    return res

def index(url):
    with open('index頁面.html', 'rb') as fr:
        res = fr.read()
    return res


# 定義一個url和實際要執行的函數的對應關係
dt = {
    '/index': index,
    '/home': home
}

while True:
    # 等待鏈接
    conn, addr = sk.accept()
    # 接收客戶端返回的信息
    data = conn.recv(1024)
    # print(data)

    # 從data中取到路徑 並將收到的字節類型的數據轉換成字符串
    # data = str(data,encoding='utf8')
    data = data.decode('utf8')

    # 按\r\n分割
    data1 = data.split('\r\n')[0]
    # print(data1)
    # 請求首行的進行切割拿到url url是咱們從瀏覽器發來的消息分離出來的訪問路徑
    url=data1.split(' ')[1]
    # print(url)

    func = None
    # 根據不一樣的路徑返回不一樣內容
    for k,v in dt.items():
        print(k,v)
        if url == k:
            func = v
            break
    if func:
        response = func(url)
    else:
        response = b'404 not found!!!'

    # 必須遵循HTTP協議,須要向客服端發送狀態行,客戶端才能正常顯示信息
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(response)
    conn.close()

什麼是服務器程序和應用程序?

對於真實開發中的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模塊建立web服務器

from wsgiref.simple_server import make_server

def run(environ,response):
    # 當客戶發送請求過來時,會先調用wsgi內部接口,而後調用run函數,而且攜帶了兩個參數給run函數
    # environ:一個包含全部HTTP請求信息的dict對象;
    # response:一個發送HTTP響應的函數。

    # 向客戶端發送的狀態碼和頭信息
    response('200 OK',[('content-type','text/html; charset=utf-8'),])

    # 返回的是一個列表,內容是發送給客戶端展現的內容
    return ['hello world'.encode('utf-8')]


if __name__ == '__main__':
    # 至關於socket綁定ip和端口
    server = make_server('127.0.0.1',8080,run)
    # 實時監聽地址,等待客戶端鏈接,有鏈接來了就調用run函數
    server.serve_forever()

wsgiref模塊實現上全部述功能的服務端

from wsgiref.simple_server import make_server
import datetime

def index(url):
    with open('index頁面.html', 'rb') as fr:
        data = fr.read()
    return data

def home(url):
    with open('home頁面.html', 'rb') as fr:
        data = fr.read()
    return data

def get_time(url):
    
    now = datetime.datetime.now().strftime('%Y-%m-%d %X')
    with open('get_time.html','r',encoding='utf-8') as fr:
        data = fr.read()
    data = data.replace('*time*',now)
    return data.encode('utf-8')

dic={
    '/index':index,
    '/home':home,
    '/get_time':get_time
}

def run(env,response):
    # 發送狀態碼和頭信息到客戶端
    response('200 ok',[('content-type','text/html;charset=utf-8'),])

    # print(env)
    # 由於env就是客戶端發過來的請求信息(k:v鍵值對形式),
    # 經過打印信息得出PATH_INFO就是請求的url,
    url = env.get('PATH_INFO')
    print(url)

    func = None
    if url in dic:
        func =  dic.get(url)

    if func:
        res = func(url)
    else:
        res = b'404 not found!!!'

    return [res]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

jinja2模塊

jinja2模塊,跟上面的用特殊符號去替換須要展現的內容的原理是同樣的,jinja2他將html頁面封裝成一個能夠渲染數據的模板,而後獲得咱們真正想要返回給瀏覽器的html頁面。

例子:從數據庫中獲取數據展現到瀏覽器。

1.建立一張user表:

2.建立html文件

<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">用戶列表</h1>
            <table class="table table-bordered table-striped table-hover">
                <thead>
                    <tr>
                        <th>id</th>
                        <th>name</th>
                        <th>pwd</th>
                    </tr>
                </thead>
                <tbody>   
                                    <!--user_list是渲染的數據 -->
                    {% for user_dict in user_list %}   
                    <tr>
                        <td>{{ user_dict.id }}</td>
                        <td>{{ user_dict.name }}</td>
                        <td>{{ user_dict.hobby}}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
</html>

3.使用jinja2渲染html文件。

from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql


# 從數據庫中獲取,並使用jinja2將數據渲染到html
def get_db(url):
    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='tomjoy',
        password='123456',
        database='user_info',
        charset='utf8',
        autocommit=True
    )
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    sql = "select * from user"
    cursor.execute(sql)

    # 1.從數據庫中獲取數據
    res = cursor.fetchall()
    with open('get_db.html','r',encoding='utf8') as fr:
        data = fr.read()

    # 2.生成html渲染模板對象
    temp = Template(data)

    # 3.將數據庫中獲取回來的數據,傳到html模板對象進行渲染,
    # 返回一個咱們真正想要展現的html頁面
    ret = temp.render(user_list=res)
    return ret.encode('utf8')



dic = {
    '/get_db' : get_db
}

def run(env,response):
    response('200 ok',[('content-type','text/html;charset=utf-8'),])
    func = None
    url = env.get('PATH_INFO')
    if url in dic:
        func = dic.get(url)
    if func:
        res = func(url)
    else:
        res = b'404 not found!!!'

    return [res]



if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

jinja2模板語法(極其貼近python後端語法)

<p>{{ user }}</p>
        <p>{{ user.name }}</p>
        <p>{{ user['pwd'] }}</p>
        <p>{{ user.get('hobby') }}</p>
        
        
        {% for user_dict in user_list %}
            <tr>
                <td>{{ user_dict.id }}</td>
                <td>{{ user_dict.name }}</td>
                <td>{{ user_dict.pwd }}</td>
            </tr>
        {% endfor %}

模板渲染:利用模板語法 實現後端傳遞數據給前端html頁面

模板語法書寫格式:
變量相關:{{}}
邏輯相關:{%%}
注意:Django的模板語法因爲是本身封裝好的,只支持 點.取值

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

效果以下:

Django

1.安裝django

pip3 install django==1.11.11

2.建立django項目
在cmd命令行下建立一個名爲mysite的Django項目

django-admin startproject mysite

3.目錄介紹

mysite
├── manage.py  # Django入口管理文件
└── templates  # 存放html文件
└── mysite  # 項目目錄
    ├── __init__.py
    ├── settings.py  # 配置
    ├── urls.py  # 路由 --> URL和函數的對應關係
    └── wsgi.py  # runserver命令就使用wsgiref模塊作簡單的web server

4.模板文件配置
使用命令行建立django項目 不會自動幫你建立templates文件夾, 只能本身建立
在.settings文件中 須要你手動在TEMPLATES的DIRS寫配置
[os.path.join(BASE_DIR, 'templates')]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # templates 文件夾位置
        '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',
            ],
        },
    },
]

5.啓動django項目

python manage.py runserver

6.建立應用app01

python manage.py startapp app01

7.app應用目錄:

└── app01  # 項目目錄
    ├── migrations文件夾  # 存放數據庫遷移記錄
    ├── __init__.py
    ├── admin.py    # django後臺管理
    └── apps.py     # 註冊相關
    └── models.py   # 模型類 
    └── tests.py    # 測試文件 
    └── views.py    # 存放視圖函數

注意:若是是在命令行下建立app後,須要你去settings配置文件中註冊添加app名字。這樣django項目才能識別到你這個app

8.靜態文件配置:

靜態文件配置官方文檔

什麼是靜態文件?

靜態文件就是在打開網頁時所用到的 圖片、 js、css以及第三方的框架bootstrap、fontawesome、sweetalert

一般狀況下 網站所用到的靜態文件資源 統一都放在static文件夾下,爲了方便識別

STATIC_URL = '/static/'  # 是訪問靜態資源的接口前綴,並非存放靜態文件的文件夾
"""只要你想訪問靜態資源 你就必須以static開頭"""


# 手動在settings最底下添加配置靜態文件訪問資源
# 下面都是存放靜態文件的文件夾的路徑
# 從上往下找靜態文件,找不到就報錯
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static'), 
    os.path.join(BASE_DIR,'static1'), 
    os.path.join(BASE_DIR,'static2'),
]

圖解:img

9.禁用中間件:

前期爲了方便表單提交測試。在settings配置文件中暫時禁用csrf中間件

10.重定向:

​ 重定向的意思就是,我訪問的連接不是我剛剛輸入的那個連接,而是我一輸入他就跳轉到了另一個連接,這就是重定向

最後注意事項:
1.計算機的名稱不能有中文
2.一個pycharm窗口就是一個項目
3.項目名裏面儘可能不要用中文

django版本問題
1.X 2.X 如今市面上用的比較多的仍是1.X
推薦使用1.11.9~1.11.13

django安裝
pip3 install django==1.11.11

如何驗證django是否安裝成功
命令行直接敲django-admin

一個django項目就相似因而一所大學,而app就相似於大學裏面的學院
django其實就是用來一個個應用的
一個app就至關於一塊獨立的功能
用戶功能
管理功能
.........

​ django支持任意多個app

相關文章
相關標籤/搜索