Django基礎之:web框架本質


一 web框架的本質

簡單描述就是:瀏覽器通過你輸入的網址給你的socket服務端發送請求,服務端接受到請求給其回復一個對應的html頁面,這就是web項目。所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端,基於請求做出響應,客戶都先請求,服務端做出對應的響應,按照http協議的請求協議發送請求,服務端按照http協議的響應協議來響應請求,這樣的網絡通信,我們就可以自己實現Web框架了。

什么是web框架?這就好比建設房子,房子主體鋼結構等都為我們搭建好了,我們就是抹抹牆面,添加一些裝飾,修飾一下即可。Django框架就是已經為我們搭建好的主題鋼結構,剩下的根據不同的業務自定制即可。但是我們先不着急學習Django框架,今天我們先自己搭建一個web框架,自己搭建了web框架之后,你對Django框架就會比較好理解了。

1. 先構建socket服務端

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

while 1:
    conn, addr = server.accept()
    while 1:
        client_data = conn.recv(1024).decode('utf-8')
        print(client_data)
        # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
        conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
        conn.send('<h1>hello</h1>'.encode('utf-8'))
    conn.close()

我們通過瀏覽器請求服務端,服務端給我們返回一個hello標簽。客戶端請求過來之后,要想讓服務端給客戶端返回消息,必須基於一個協議,我們用http協議示例。那么服務端如果返回給瀏覽器一個頁面呢?

2. 構建一個html頁面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
        div {
            background-color: #7fb8ff;
            color: red;
        }
    </style>
</head>
<body>
<div>你好,世界</div>

</body>
</html>

此時你的服務端必須將html頁面返回給客戶端,怎么返回?就得通過發送bytes數據。

服務端:

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024).decode('utf-8')
    print(client_data)
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    with open('demo.html', mode='rb') as f1:
        conn.send(f1.read())
conn.close()

這么我們發現一個問題,當你的瀏覽器訪問服務端時,服務端會接受到這些信息:

GET / HTTP/1.1
Host: 127.0.0.1:8001
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

這些是什么信息?這是因為服務端與客戶端遵循http協議,這是協議做的事情,所以在我們研究web框架之前,必不可少的需要研究一下這個協議,協議到底做了什么事情。

http協議詳見:xxxxx.

3. http協議具體研究 

3.1 請求步驟

1. 建立鏈接:客戶端連接到Web服務器。
一個HTTP客戶端,通常是瀏覽器,與Web服務器的HTTP端口(默認為80)建立一個TCP套接字連接。例如,http://www.baidu.com。

2. 發送HTTP請求
通過TCP套接字,客戶端向Web服務器發送一個文本的請求報文,一個請求報文由請求行、請求頭部、空行和請求數據4部分組成。

這里分不同的請求,主要是get、post請求。

首先看get請求:

更改一下我們的html頁面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">


</head>
<body>

<form action="http://127.0.0.1:8002">
用戶名:<input type="text" name="username">
密碼:<input type="password" name="password">
驗證碼:<input type="text" name="code">
<input type="submit">
</form>
</body>
</html>

此時你發送的是get請求,服務器接受到的消息為:

GET /?username=taibai&password=123&code=2w3e HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://127.0.0.1:8002/?username=taibai&password=123&code=3er4
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

你的get請求的數據都是拼接到url后面的:

在來看post請求:

更改一下我們的html頁面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 101 Template</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">


</head>
<body>

<form action="http://127.0.0.1:8002" method="post">
用戶名:<input type="text" name="username">
密碼:<input type="password" name="password">
驗證碼:<input type="text" name="code">
<input type="submit">
</form>
</body>
</html>

此時你發送的是post請求,服務器接受到的消息為:

POST / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Content-Length: 38
Cache-Control: max-age=0
Origin: http://127.0.0.1:8002
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

username=taibai&password=123&code=2w3e

post請求,請求數據是在最后一行的。

正確的請求的格式為:

請求頭部有一些重要的鍵值對:

Host: 127.0.0.1:8002------> 主機(IP地址和端口)
Connection: keep-alive-----> 鏈接是否保留一段時間
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36            ------> 用戶的瀏覽器代理
Content-Length: 29    ------> 請求數據的長度

3. 服務器接受請求並返回HTTP響應
Web服務器解析請求,定位請求資源。服務器將資源復本寫到TCP套接字,由客戶端讀取。一個響應由狀態行、響應頭部、空行和響應數據4部分組成。

響應從哪里看呢?響應從服務器中獲取:

 

 

 

 標准的響應格式:

4. 釋放連接TCP連接
若connection 模式為close,則服務器主動關閉TCP連接,客戶端被動關閉連接,釋放TCP連接;若connection 模式為keepalive,則該連接會保持一段時間,在該時間內可以繼續接收請求;

5. 客戶端瀏覽器解析HTML內容
客戶端瀏覽器首先解析狀態行,查看表明請求是否成功的狀態代碼。然后解析每一個響應頭,響應頭告知以下為若干字節的HTML文檔和文檔的字符集。客戶端瀏覽器讀取響應數據HTML,根據HTML的語法對其進行格式化,並在瀏覽器窗口中顯示。

 3.2 重要知識點

URL、請求方式、http協議的特點,請求流程,請求格式,響應格式。響應狀態碼等。

post與get請求的區別:

GET提交的數據會放在URL之后,也就是請求行里面,以?分割URL和傳輸數據,參數之間以&相連,如EditBook?name=test1&id=123456.(請求頭里面那個content-type做的這種參數形式,后面講) 
POST方法是把提交的數據放在HTTP包的請求體中.

GET提交的數據大小有限制(因為瀏覽器對URL的長度有限制),而POST方法提交的數據沒有限制.

GET與POST請求在服務端獲取請求數據方式不同,就是我們自己在服務端取請求數據的時候的方式不同了,這句廢話昂。
常用的get請求:瀏覽器輸入網址,a標簽,form標簽默認get請求。
常用的post請求:一般就是用來提交數據,比如用戶名密碼登錄。

 二、構建web框架

2.1 簡單版web框架

之前通過pycharm打開html頁面,是pychram給你提供的服務端,我們應該自己提供服務端,通過瀏覽器與服務端交互。

html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
        div{
            background-color: #7fb8ff;
            font-size: 16px;
        }
    </style>

</head>
<body>
<div>歡迎訪問xx商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>

</body>
</html>

python:

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024).decode('utf-8')
    print(client_data)
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    with open('02 簡單版html.html', mode='rb') as f1:
        conn.send(f1.read())
    conn.close()

 

2.2 升級版web框架

首先我們應該創建不同的css、js文件,通過html引入。

div{
            background-color: #7fb8ff;
            font-size: 16px;
        }
css
alert('未滿18歲禁止入內');
js

 

 

 瀏覽器執行到link標簽時,href會發出一個請求,請求你當前根目錄下面的test.css文件,其實完整url也就是http://127.0.0.1:8002/test.css,你引入的js同理,此過程是異步的。html代碼不會等你引入完css之后,再執行下面代碼。

你的python服務端與簡單版本一致,我們啟動一下服務端,看瀏覽器發送了什么請求。

此時你的服務端接受到了如下幾個請求:

GET / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


GET /test.css HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9



GET /test.js HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

你的瀏覽器給服務端發送了4個請求,這4個請求分別是:對根目錄也就是html的請求、對css文件請求、對js文件請求、以及favincon也就是小圖標的請求,

雖然瀏覽器發送了四個請求,但是我們統一回復的就是一個html頁面,相當於我們重復回復了4次同一個html頁面,這也導致了我們的頁面沒有css,js的效果:

 所以我們應該怎么做?對不同的請求返回給不同的文件資源,比如他請求的css文件,我們就返回css文件資源,這樣頁面就有了更加豐富的樣式。那么如何區分他的請求呢?就是通過每次請求的第一行的請求路徑,所以我們應該把每次的請求路徑分割,根據不同的請求路徑,返回給其不同的文件資源。

最終的html、以及服務端代碼:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico"> 
    

</head>
<body>
<div>歡迎訪問和軟商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">
{#<img src="mv.jpeg" alt="">#}
<script src="test.js"></script>
</body>
</html>

# 上面我添加了兩個標簽,就是對圖片的請求。
View Code
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    if request_path == '/':
        with open('03 升級版html.html', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test.css':
        with open('test.css', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/mv.jpeg':
        with open('mv.jpeg', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test.js':
        with open('test.js', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/jd.ico':
        with open('jd.ico', mode='rb') as f1:
            conn.send(f1.read())
    conn.close()
View Code

favicon.ico 就是窗口小圖標,瀏覽器一直會向服務器發送請求。
圖片的索取與文件是一樣的,也就是通過路徑索取資源。

這樣升級版web框架我們就完成了。

2.3 函數版web框架

上一個版本用了多個if elif elif....... 這種方式比較low,並且代碼應該整合成函數而不能使用純面向過程方式,所以我們進行改版:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>
<div>歡迎訪問和軟商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>
html
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

def html(conn):
    with open('04 函數進階版html.html', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def css(conn):
    with open('test.css', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def js(conn):
    with open('test.js', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    for i in request_list:
        if request_path == i[0]:
            i[1](conn)
    conn.close()
python服務端

雖然這個這個版本感覺更加簡潔明了了,但是這個版本還是不完美,現在雖然是異步處理請求,但是上一階段我們學完並發,我們對於這些異步請求應該用並發處理更加合理。

2.4 並發版web框架

此時我們要加上多線程處理瀏覽器發送過來的請求。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>歡迎訪問河軟商城</div>
<div>{time_now}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>
html
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

def html(conn):
    with open('05 高級版html.html', mode='rb') as f1:
        data = f1.read()
    conn.send(data)
    conn.close()


def css(conn):
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    conn.send(data)
    conn.close()


def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    conn.send(data)
    conn.close()

def js(conn):
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    conn.send(data)
    conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    conn.send(data)
    conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    # 只要有一個線程處理conn管道,accept就會接受新的請求創建另一個管道
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1], args=(conn,))
            t.start()
    # conn.close()
socket服務端

此版本並不是完結版本,上面的版本我們寫的都是靜態頁面,沒有實現動態框架以及與數據庫交互等。所以我們繼續更新。

2.5 動態版web框架

一般情況下,我們的數據都是在數據庫中,動態獲取,實時變化,但是現在我們模擬一下數據,利用時間戳。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>歡迎訪問河軟商城</div>
<div>{time_now}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>
html
import socket
import time
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

def html(conn):
    time_now = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime())
    with open('06 高級版動態html.html', encoding='utf-8') as f1:
        data = f1.read().format(time_now=time_now)
    conn.send(data.encode('utf-8'))
    conn.close()


def css(conn):
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    conn.send(data)
    conn.close()


def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    conn.send(data)
    conn.close()

def js(conn):
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    conn.send(data)
    conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    conn.send(data)
    conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服務端與客戶端建立聯系必須要遵循一個協議,此時我們用http協議示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    # 只要有一個線程處理conn管道,accept就會接受新的請求創建另一個管道
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1], args=(conn,))
            t.start()
socket服務端

2.6 wsgiref模塊版+數據庫web框架

wsgiref模塊其實就是將整個請求信息給封裝了起來,就不需要你自己處理了,假如它將所有請求信息封裝成了一個叫做request的對象,那么你直接request.path就能獲取到用戶這次請求的路徑,request.method就能獲取到本次用戶請求的請求方式(get還是post)等,那這個模塊用起來,我們再寫web框架是不是就簡單了好多啊。

  對於真實開發中的python web程序來說,一般會分為兩部分:服務器程序和應用程序。

服務器程序:

 

 

應用程序:

 

 

 

  服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各種數據進行整理。

  應用程序則負責具體的邏輯處理。為了方便應用程序的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。

  這樣,服務器程序就需要為不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規范。

  正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專注於生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應格式,所以,需要一個統一的接口協議來實現這樣的服務器軟件,讓我們專心用Python編寫Web業務。

  這時候,標准化就變得尤為重要。我們可以設立一個標准,只要服務器程序支持這個標准,框架也支持這個標准,那么他們就可以配合使用。一旦標准確定,雙方各自實現。這樣,服務器可以支持更多支持標准的框架,框架也可以使用更多支持標准的服務器。

  WSGI(Web Server Gateway Interface)就是一種規范,它定義了使用Python編寫的web應用程序與web服務器程序之間的接口格式,實現web應用程序與web服務器程序間的解耦。

  常用的WSGI服務器有uwsgi、Gunicorn。而Python標准庫提供的獨立WSGI服務器叫wsgiref,Django開發環境用的就是這個模塊來做服務器。

接下來我們先看一看wsgiref的簡單用法:

from wsgiref.simple_server import make_server
# wsgiref本身就是個web框架,提供了一些固定的功能(請求和響應信息的封裝,不需要我們自己寫原生的socket了也不需要咱們自己來完成請求信息的提取了,提取起來很方便)
#函數名字隨便起
def application(environ, start_response):
    '''
    :param environ: 是全部加工好的請求信息,加工成了一個字典,通過字典取值的方式就能拿到很多你想要拿到的信息
    :param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數
    :return:
    '''
    start_response('200 OK', [('k1','v1'),])
    print(environ)
    print(environ['PATH_INFO'])  #輸入地址127.0.0.1:8000,這個打印的是'/',輸入的是127.0.0.1:8000/index,打印結果是'/index'
    return [b'<h1>Hello, web!</h1>']

#和咱們學的socketserver那個模塊很像啊
httpd = make_server('127.0.0.1', 8080, application)

print('Serving HTTP on port 8080...')
# 開始監聽HTTP請求:
httpd.serve_forever()

這個模塊就是封裝好服務器程序的處理,方便你使用。

接下來我們引入數據庫內容,首先創建一個數據庫,然后在插入一些數據:

現將mysql改成自動賬號密碼登錄:

找到mysql軟件的min.ini 配置文件,

 

 

 

 

先要將cmd設置成以管理員身份啟動:c:\windws\System32 尋找cmd

 

 

 

 

配置數據庫:

利用pymysql先創建一個表:

創建成功:

插入一些數據:

 

 

這樣你成功的在數據庫創建並插入了一些數據,然后就是三個文件了:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>歡迎訪問河軟商城</div>
<div>{data}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>
html
from wsgiref.simple_server import make_server
import getdata

def html():
    from_mysql_data = getdata.get_data()
    with open('07 wsgiref版+數據庫html.html', encoding='utf-8') as f1:
        data = f1.read().format(data=from_mysql_data)
    return data.encode('utf-8')


def css():
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    return data


def jpeg():
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    return data


def js():
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    return data


def jd():
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    return data

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]



def application(environ, start_response):

    start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
    print(environ)
    request_path = environ['PATH_INFO']
    for i in request_list:
        if request_path == i[0]:
            ret = i[1]()
            return [ret]
    else:

        return [b'<h1>404.....</h1>']




httpd = make_server('127.0.0.1', 8080, application)
print('Serving HTTP on port 8080...')
# 開始監聽HTTP請求:
httpd.serve_forever()
socket服務端
import pymysql

conn = pymysql.connect(
    host='127.0.0.1',
    port=3306,
    user='root',
    password='123',
    database='webbase',
    charset='utf8'
)


cursor = conn.cursor(pymysql.cursors.DictCursor)
# 創建userinfo數據表
# cursor.execute('create table userinfo(id int primary key auto_increment, name varchar(16) not null unique, age int not null);')

# 給userinfo插入數據
# cursor.execute("""
#     insert into userinfo(name,age) values
#     ('小明',28),
#     ('小紅',18),
#     ('太白',18);
#     """
# )

conn.commit()

cursor.close()

conn.close()
創建數據
import pymysql


def get_data():

    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='webbase',
        charset='utf8'
    )

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

    # 獲取所有數據
    cursor.execute("select * from userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data
getdata

2.7 wsgiref模塊版+數據庫+jinja2 web框架

上面的代碼實現了一個簡單的動態頁面(字符串替換),我完全可以從數據庫中查詢數據,然后去替換我html中的對應內容(專業名詞叫做模板渲染,你先渲染一下,再給瀏覽器進行渲染),然后再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這里用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2,DJango有自帶的模版渲染方法,和jinja2很像,但是只能適用於Django框架,而jinja2可以適用於多種框架。

下載:

pip install jinja2

 用法直接體現在項目中:

import pymysql


def get_data():

    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='webbase',
        charset='utf8'
    )

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

    # 獲取所有數據
    cursor.execute("select * from userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data
getdata
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>歡迎訪問河軟商城</div>
<div>{{userinfo}}</div>

<ul>
    {% for k,v in userinfo.items()%}
    <li>{{k}} --  {{v}}</li>
    {%endfor%}
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>
html
import getdata
from jinja2 import Template
from wsgiref.simple_server import make_server


def html():
    from_mysql_data = getdata.get_data()
    with open('08 wsgiref版+數據庫+jinja2html.html', encoding='utf-8') as f1:
        data = f1.read()
    temp = Template(data)
    temp_data = temp.render({'userinfo': from_mysql_data})
    return temp_data.encode('utf-8')


def css():
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    return data


def jpeg():
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    return data


def js():
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    return data


def jd():
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    return data

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]



def application(environ, start_response):

    start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
    print(environ)
    request_path = environ['PATH_INFO']
    for i in request_list:
        if request_path == i[0]:
            ret = i[1]()
            return [ret]
    else:

        return [b'<h1>404.....</h1>']




httpd = make_server('127.0.0.1', 8848, application)
print('Serving HTTP on port 8080...')
# 開始監聽HTTP請求:
httpd.serve_forever()
socket服務端

2.8 起飛版web框架

上面所有的還不能稱為一個框架,真正的框架是分文件開發,也就是咱們在規范化格式目錄時學習過的,不同的文件擁有不同的功能,真正的框架就是要分文件各司其職。

static靜態文件

靜態文件存放的就是css、js、jQuery等相關的文件,因為這些文件可以分類放置並且每種都可以有很多文件。

 

urls:路由分發

我們代碼中有一個列表,根據不同的路徑執行不同的函數,這個列表就稱為路由分發,所以要單獨設立一個文件。

 

template:html文件

template文件夾就是專門放置html文件的,你們以后的web項目html文件會非常多,所以必須用單獨的一個文件夾存放。

 

views文件:專門放置應用程序的代碼,處理業務邏輯。

 

manage文件:專門放置服務器程序的代碼,處理http協議,socket等。

 

getdata文件:文件名自己定義,主要方式處理數據庫相關邏輯。

 

至此,我們自己搭建的起飛版的web框架就算完成了,這個就是所有web框架的雛形,只要你把這個框架研究明白,那么接下來你無論研究什么框架,都易如反掌了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM