Django基礎之web框架的本質

一 web框架的本質及自定義web框架

  能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端,基於請求作出響應,客戶都先請求,服務端作出對應的響應,按照http協議的請求協議發送請求,服務端按照http協議的響應協議來響應請求,這樣的網絡通訊,就能夠本身實現Web框架了。css

經過socket,知道網絡通訊,socket就是作網絡通訊用的,下面就基於socket來實現一個web框架,寫一個web服務端,讓瀏覽器來請求,並經過服務端把頁面返回給瀏覽器,瀏覽器渲染出想要的效果。html

html文件內容以下,名稱爲test.html: 前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="wechat.ico"> 
    <!--直接寫在html頁面裏面的css樣式是直接能夠在瀏覽器上顯示的-->
    <!--<style>-->
        <!--h1{-->
            <!--background-color: green;-->
            <!--color: white;-->
        <!--}-->
    <!--</style>-->
</head>
<body>

<h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>
<!--直接寫在html頁面裏面的img標籤的src屬性值若是是別人網站的地址(網絡地址)是直接能夠在瀏覽器上顯示的-->
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--若是都是網絡地址,那麼只要你的電腦有網,就能夠看到,不須要本身在後端寫對應的讀取文件,返回圖片文件信息的代碼,由於別人的網站就作了這個事情了-->
<img src="meinv.png" alt="" width="100" height="100"> <!--若是你是本地的圖片想要返回給頁面,你須要對頁面上的關於這個圖片的請求要本身作出響應,這個src就是來你本地請求這個圖片,你只要將圖片信息讀取出來,返回給頁面,頁面拿到這個圖片的數據,就可以渲染出來了-->

<!--直接寫在html頁面裏面的js操做是直接能夠在瀏覽器上顯示的-->
<!--<script>-->
    <!--alert('這是第一個網頁')-->
<!--</script>-->

<script src="test.js"></script>
</body>
</html>
test.html

css文件內容以下,名稱爲test.css:python

h1{
    background-color: green;
    color: white;
}

js文件內容以下,名稱爲test.js:mysql

alert('這是第一個網頁');

再準備一個圖片,名稱爲meinv.jpg,再準備一個ico文件,名稱爲wechat.ico,其實就是個圖片文件,微信官網打開以後,在瀏覽器最上面可以看到,把它保存下來web

上面的文件都準備好以後,用pycharm新建一個項目,把文件都放到一個文件夾裏面去,留着備用,像下面這個樣子:sql

    

而後開始寫web框架,分這麼幾步來寫:shell

1、簡單的web框架數據庫

建立一個python文件,內容以下,名稱爲test.py:django

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()
conn,addr = sk.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
#socket是應用層和傳輸層之間的抽象層,每次都有協議,協議就是消息格式,那麼傳輸層的消息格式咱們不用管,由於socket幫咱們搞定了,可是應用層的協議仍是須要我們本身遵照的,因此再給瀏覽器發送消息的時候,若是沒有按照應用層的消息格式來寫,那麼你返回給瀏覽器的信息,瀏覽器是無法識別的。而應用層的協議就是咱們的HTTP協議,因此咱們按照HTTP協議規定的消息格式來給瀏覽器返回消息就沒有問題了,關於HTTP咱們會細說,首先看一下直接寫conn.send(b'hello')的效果,而後運行代碼,經過瀏覽器來訪問一下,而後再看這一句conn.send(b'HTTP/1.1 200 ok \r\n\r\nhello')的效果
#下面這句就是按照http協議來寫的
# conn.send(b'HTTP/1.1 200 ok \r\n\r\nhello')
#上面這句還能夠分紅下面兩句來寫
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
conn.send(b'hello')
test.py

來瀏覽器上看一下瀏覽器發送的請求:

      

目前尚未寫如何返回一個html文件給瀏覽器,因此這裏暫時不用管它,那麼點開這個127.0.0.1看看:

      

在python文件中打印一下瀏覽器發送過來的請求信息是啥:

       

重啓代碼,而後在網址中輸入這個:

      

再重啓代碼,而後在網址中輸入這個:

      

  

瀏覽器發過來一堆的消息,給瀏覽器回覆(響應)信息的時候,也要按照一個消息格式來寫,這些都是http協議規定的,那麼就來學習一下http協議,而後繼續完善web框架:

HTTP協議簡介

超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)是一種用於分佈式、協做式和超媒體信息系統的應用層協議。HTTP是萬維網的數據通訊的基礎。

HTTP的發展是由蒂姆·伯納斯-李於1989年在歐洲核子研究組織(CERN)所發起。HTTP的標準制定由萬維網協會(World Wide Web Consortium,W3C)和互聯網工程任務組(Internet Engineering Task Force,IETF)進行協調,最終發佈了一系列的RFC,其中最著名的是1999年6月公佈的 RFC 2616,定義了HTTP協議中現今普遍使用的一個版本——HTTP 1.1。

2014年12月,互聯網工程任務組(IETF)的Hypertext Transfer Protocol Bis(httpbis)工做小組將HTTP/2標準提議遞交至IESG進行討論,於2015年2月17日被批准。 HTTP/2標準於2015年5月以RFC 7540正式發表,取代HTTP 1.1成爲HTTP的實現標準。

HTTP協議概述

HTTP是一個客戶端終端(用戶)和服務器端(網站)請求和應答的標準(TCP)。經過使用網頁瀏覽器、網絡爬蟲或者其它的工具,客戶端發起一個HTTP請求到服務器上指定端口(默認端口爲80)。咱們稱這個客戶端爲用戶代理程序(user agent)。應答的服務器上存儲着一些資源,好比HTML文件和圖像。咱們稱這個應答服務器爲源服務器(origin server)。在用戶代理和源服務器中間可能存在多個「中間層」,好比代理服務器、網關或者隧道(tunnel)。

儘管TCP/IP協議是互聯網上最流行的應用,HTTP協議中,並無規定必須使用它或它支持的層。事實上,HTTP能夠在任何互聯網協議上,或其餘網絡上實現。HTTP假定其下層協議提供可靠的傳輸。所以,任何可以提供這種保證的協議均可以被其使用。所以也就是其在TCP/IP協議族使用TCP做爲其傳輸層。

一般,由HTTP客戶端發起一個請求,建立一個到服務器指定端口(默認是80端口)的TCP鏈接。HTTP服務器則在那個端口監聽客戶端的請求。一旦收到請求,服務器會向客戶端返回一個狀態,好比"HTTP/1.1 200 OK",以及返回的內容,如請求的文件、錯誤消息、或者其它信息。

HTTP工做原理

HTTP協議定義Web客戶端如何從Web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶端。HTTP協議採用了請求/響應模型。客戶端向服務器發送一個請求報文,請求報文包含請求的方法、URL、協議版本、請求頭部和請求數據。服務器以一個狀態行做爲響應,響應的內容包括協議的版本、成功或者錯誤代碼、服務器信息、響應頭部和響應數據。

如下是 HTTP 請求/響應的步驟:

\1. 客戶端鏈接到Web服務器
一個HTTP客戶端,一般是瀏覽器,與Web服務器的HTTP端口(默認爲80)創建一個TCP套接字鏈接。

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

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

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

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

例如:在瀏覽器地址欄鍵入URL,按下回車以後會經歷如下流程:

1. 瀏覽器向 DNS 服務器請求解析該 URL 中的域名所對應的 IP 地址;
2. 解析出 IP 地址後,根據該 IP 地址和默認端口 80,和服務器創建TCP鏈接;
3. 瀏覽器發出讀取文件(URL 中域名後面部分對應的文件)的HTTP 請求,該請求報文做爲 TCP 三次握手的第三個報文的數據發送給服務器;
4. 服務器對瀏覽器請求做出響應,並把對應的 html 文本發送給瀏覽器;
5. 釋放 TCP鏈接;
6. 瀏覽器將該 html 文本並顯示內容;
http協議是基於TCP/IP協議之上的應用層協議。

基於  請求-響應 的模式

HTTP協議規定,請求從客戶端發出,最後服務器端響應該請求並 返回。換句話說,確定是先從客戶端開始創建通訊的,服務器端在沒有 接收到請求以前不會發送響應
無狀態保存
HTTP是一種不保存狀態,即無狀態(stateless)協議。HTTP協議 自身不對請求和響應之間的通訊狀態進行保存。也就是說在HTTP這個 級別,協議對於發送過的請求或響應都不作持久化處理。
使用HTTP協議,每當有新的請求發送時,就會有對應的新響應產 生。協議自己並不保留以前一切的請求或響應報文的信息。這是爲了更快地處理大量事務,確保協議的可伸縮性,而特地把HTTP協議設計成 如此簡單的。但是,隨着Web的不斷髮展,因無狀態而致使業務處理變得棘手 的狀況增多了。好比,用戶登陸到一家購物網站,即便他跳轉到該站的 其餘頁面後,也須要能繼續保持登陸狀態。針對這個實例,網站爲了能 夠掌握是誰送出的請求,須要保存用戶的狀態。HTTP/1.1雖然是無狀態協議,但爲了實現指望的保持狀態功能, 因而引入了Cookie技術。有了Cookie再用HTTP協議通訊,就能夠管理狀態了。
無鏈接
無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間,而且能夠提升併發性能,不能和每一個用戶創建長久的鏈接,請求一次相應一次,服務端和客戶端就中斷了。可是無鏈接有兩種方式,早期的http協議是一個請求一個響應以後,直接就斷開了,可是如今的http協議1.1版本不是直接就斷開了,而是等幾秒鐘,這幾秒鐘是等什麼呢,等着用戶有後續的操做,若是用戶在這幾秒鐘以內有新的請求,那麼仍是經過以前的鏈接通道來收發消息,若是過了這幾秒鐘用戶沒有發送新的請求,那麼就會斷開鏈接,這樣能夠提升效率,減小短期內創建鏈接的次數,由於創建鏈接也是耗時的,默認的好像是3秒中如今,可是這個時間是能夠經過我們後端的代碼來調整的,本身網站根據本身網站用戶的行爲來分析統計出一個最優的等待時間。
HTTP請求方法
HTTP/1.1協議中共定義了八種方法(也叫「動做」)來以不一樣方式操做指定的資源:
GET
向指定的資源發出「顯示」請求。使用GET方法應該只用在讀取數據,而不該當被用於產生「反作用」的操做中,例如在Web Application中。其中一個緣由是GET可能會被網絡蜘蛛等隨意訪問。
HEAD
與GET方法同樣,都是向服務器發出指定資源的請求。只不過服務器將不傳回資源的本文部分。它的好處在於,使用這個方法能夠在沒必要傳輸所有內容的狀況下,就能夠獲取其中「關於該資源的信息」(元信息或稱元數據)。
POST
向指定資源提交數據,請求服務器進行處理(例如提交表單或者上傳文件)。數據被包含在請求本文中。這個請求可能會建立新的資源或修改現有資源,或兩者皆有。
PUT
向指定資源位置上傳其最新內容。
DELETE
請求服務器刪除Request-URI所標識的資源。
TRACE
回顯服務器收到的請求,主要用於測試或診斷。
OPTIONS
這個方法可以使服務器傳回該資源所支持的全部HTTP請求方法。用'*'來代替資源名稱,向Web服務器發送OPTIONS請求,能夠測試服務器功能是否正常運做。
CONNECT
HTTP/1.1協議中預留給可以將鏈接改成管道方式的代理服務器。一般用於SSL加密服務器的連接(經由非加密的HTTP代理服務器)。
注意事項:
1. 方法名稱是區分大小寫的。當某個請求所針對的資源不支持對應的請求方法的時候,服務器應當返回狀態碼405(Method Not Allowed),當服務器不認識或者不支持對應的請求方法的時候,應當返回狀態碼501(Not Implemented)。
2. HTTP服務器至少應該實現GET和HEAD方法,其餘方法都是可選的。固然,全部的方法支持的實現都應當匹配下述的方法各自的語義定義。此外,除了上述方法,特定的HTTP服務器還可以擴展自定義的方法。例如PATCH(由 RFC 5789 指定的方法)用於將局部修改應用到資源。
請求方式: get與post請求
- GET提交的數據會放在URL以後,也就是請求行裏面,以?分割URL和傳輸數據,參數之間以&相連,如EditBook?name=test1&id=123456.(請求頭裏面那個content-type作的這種參數形式,後面講) POST方法是把提交的數據放在HTTP包的請求體中.
- GET提交的數據大小有限制(由於瀏覽器對URL的長度有限制),而POST方法提交的數據沒有限制.
- GET與POST請求在服務端獲取請求數據方式不一樣,就是在服務端取請求數據的時候的方式不一樣了。
HTTP狀態碼
全部HTTP響應的第一行都是狀態行,依次是當前HTTP版本號,3位數字組成的狀態代碼,以及描述狀態的短語,彼此由空格分隔。
狀態代碼的第一個數字表明當前響應的類型:
- 1xx消息——請求已被服務器接收,繼續處理
- 2xx成功——請求已成功被服務器接收、理解、並接受
- 3xx重定向——須要後續操做才能完成這一請求
- 4xx請求錯誤——請求含有詞法錯誤或者沒法被執行
- 5xx服務器錯誤——服務器在處理某個正確請求時發生錯誤
雖然 RFC 2616 中已經推薦了描述狀態的短語,例如"200 OK""404 Not Found",可是WEB開發者仍然可以自行決定採用何種短語,用以顯示本地化的狀態描述或者自定義信息。
URL

超文本傳輸協議(HTTP)的統一資源定位符將從因特網獲取信息的五個基本元素包括在一個簡單的地址中:

- 傳送協議。
- 層級URL標記符號(爲[//],固定不變)
- 訪問資源須要的憑證信息(可省略)
- 服務器。(一般爲域名,有時爲IP地址)
- 端口號。(以數字方式表示,若爲HTTP的默認值「:80」可省略)
- 路徑。(以「/」字符區別路徑中的每個目錄名稱)
- 查詢。(GET模式的窗體參數,以「?」字符爲起點,每一個參數以「&」隔開,再以「=」分開參數名稱與數據,一般以UTF8的URL編碼,避開字符衝突的問題)
- 片斷。以「#」字符爲起點

以http://www.luffycity.com:80/news/index.html?id=250&page=1 爲例, 其中:

http,是協議;
www.luffycity.com,是服務器;
80,是服務器上的默認網絡端口號,默認不顯示;
/news/index.html,是路徑(URI:直接定位到對應的資源);
?id=250&page=1,是查詢。
大多數網頁瀏覽器不要求用戶輸入網頁中「http://」的部分,由於絕大多數網頁內容是超文本傳輸協議文件。一樣,「80」是超文本傳輸協議文件的經常使用端口號,所以通常也沒必要寫明。通常來講用戶只要鍵入統一資源定位符的一部分(www.luffycity.com:80/news/index.html?id=250&page=1)就能夠了。

因爲超文本傳輸協議容許服務器將瀏覽器重定向到另外一個網頁地址,所以許多服務器容許用戶省略網頁地址中的部分,好比 www。從技術上來講這樣省略後的網頁地址其實是一個不一樣的網頁地址,瀏覽器自己沒法決定這個新地址是否通,服務器必須完成重定向的任務。
View Code

HTTP請求格式(請求協議)

    

     URL包含:/index/index2?a=1&b=2;路徑和參數都在這裏。

    

請求頭裏面的內容舉個例子:這個length表示請求體裏面的數據長度,其餘的請求頭裏面的這些鍵值對,其中有一個user-agent,就是告訴你的服務端,我是用什麼給你發送的請求。

      

      

以京東爲例,看一下user-agent

      

 

     

看一個爬蟲的例子,爬京東的時候沒問題,可是爬抽屜的時候必須帶着user-agent,由於抽屜對user-agent作了判斷,來判斷你是否是一個正常的請求,算是反扒機制的一種。

      

打開咱們保存的demo.html文件,而後經過瀏覽器打開看看就能看到頁面效果。

寫上面這些內容的意思是知道有這麼個請求頭的存在,有些是有意義的,請求頭還能夠本身定義,就在requests模塊裏面那個headers={},這個字典裏面加就行。  

HTTP響應格式(響應協議)

  

  

 

2、返回HTML文件的web框架

首先寫一個html文件,內容以下,名稱爲test.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title><link rel="stylesheet" href="test.css">

    <!--直接寫在html頁面裏面的css樣式是直接能夠在瀏覽器上顯示的-->
    <style>
        h1{
            background-color: green;
            color: white;
        }
    </style>
</head>
<body>

<h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>
<!--直接寫在html頁面裏面的img標籤的src屬性值若是是別人網站的地址(網絡地址)是直接能夠在瀏覽器上顯示的-->
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt=""> 
<!--若是都是網絡地址,那麼只要你的電腦有網,就能夠看到,不須要本身在後端寫對應的讀取文件,返回圖片文件信息的代碼,由於別人的網站就作了這個事情了-->

<!--直接寫在html頁面裏面的js操做是直接能夠在瀏覽器上顯示的-->
<script>
    alert('這是第一個網頁')
</script>

</body>
</html>
test.html

準備python代碼,服務端程序,文件內容以下,文件名稱爲test.py:

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()
conn,addr = sk.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
print('瀏覽器請求信息:',str_msg)

# conn.send(b'HTTP/1.1 200 ok \r\ncontent-type:text/html;charset=utf-8;\r\n') 
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')

with open('test1.html','rb') as f:
    f_data = f.read()
conn.send(f_data)
text.py

頁面上輸入網址看效果,css和js代碼的效果也有,very good:

      

    

可是css和js基本都是寫在本地的文件裏面,並且圖片基本也是本身本地的,將上面提早準備好的js和css還有那個.ico結尾的圖片文件都準備好,在來一個升級版的web框架,其實css、js、圖片等文件都叫作網站的靜態文件。

首先看一個效果,若是直接將寫好的css和js還有.ico和圖片文件插入到html頁面裏面,就是下面這個html文件

名稱爲test.html,內容以下: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="test.css">
    <!--加上下面這句,那麼看瀏覽器調試窗口中的那個network裏面就沒有那個favicon.ico的請求了,其實這就是頁面title標籤文字左邊的那個頁面圖標,可是這個文件是本身本地的,因此須要在後端代碼裏面將這個文件數據讀取出來返回給前端-->
    <link rel="icon" href="wechat.ico">
    <!--直接寫在html頁面裏面的css樣式是直接能夠在瀏覽器上顯示的-->
    <!--<style>-->
        <!--h1{-->
            <!--background-color: green;-->
            <!--color: white;-->
        <!--}-->
    <!--</style>-->
</head>
<body>

<h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>
<!--直接寫在html頁面裏面的img標籤的src屬性值若是是別人網站的地址(網絡地址)是直接能夠在瀏覽器上顯示的-->
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--若是都是網絡地址,那麼只要你的電腦有網,就能夠看到,不須要本身在後端寫對應的讀取文件,返回圖片文件信息的代碼,由於別人的網站就作了這個事情了-->
<img src="meinv.png" alt="" width="100" height="100"> <!--若是你是本地的圖片想要返回給頁面,你須要對頁面上的關於這個圖片的請求要本身作出響應,這個src就是來你本地請求這個圖片,你只要將圖片信息讀取出來,返回給頁面,頁面拿到這個圖片的數據,就可以渲染出來了,是否是很簡單-->

<!--直接寫在html頁面裏面的js操做是直接能夠在瀏覽器上顯示的-->
<!--<script>-->
    <!--alert('這是第一個網頁')-->
<!--</script>-->

<script src="test.js"></script>
</body>
</html>
test.html

一樣使用以前的python程序,來看效果:

      

發現js和css的效果都沒有出來,而且看一下瀏覽器調試窗口的那個network

      

在network裏面點擊那個test.css文件,看看請求是什麼:

     

還有就是直接在瀏覽器上保存某個頁面的時候,隨便一個頁面,到頁面上點擊右鍵另存爲,而後存到本地的一個目錄下,會發現這個頁面的html、css、js、圖片等文件都跟着保存下來了,保存了一下博客園首頁的頁面,看,是一個文件夾和一個html文件:

      

 

點開博客園那個文件夾看看裏面都有什麼:

      

發現js、css還有圖片什麼的都被保存了下來,說明這些文件自己就存在瀏覽器上了,原來就是將html頁面須要的css、js、圖片等文件也發送給瀏覽器就能夠了,而且這些靜態文件都是瀏覽器單獨過來請求的,其實和標籤的屬性有有關係,css文件是link標籤的href屬性:<link rel="stylesheet" href="test.css">,js文件是script標籤的src屬性:<script src="test.js"></script>,圖片文件是img標籤的src屬性: ,那個.ico文件是link標籤的屬性:<link rel="icon" href="wechat.ico">,其實這些屬性都會在頁面加載的時候,單獨到本身對應的屬性值裏面取請求對應的文件數據,若是在值裏面寫的都是本身本地的路徑,那麼都會來本身的本地路徑來找,若是寫的是相對路徑,就會到本身的網址+文件名稱,這個路徑來找它須要的文件,因此只須要將這些請求作一些響應,將對應的文件數據相應給瀏覽器就能夠了!而且經過前面的查看,可以發現,瀏覽器url的請求路徑知道是什麼,靜態文件也這樣請求的,針對不一樣的路徑給它返回不一樣的文件,img

3、返回靜態文件的高級web框架

仍是用第二個web框架裏面的那個html文件,只須要寫一些服務端程序就能夠了,一樣是test.py文件,內容以下:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()

#首先瀏覽器至關於發送了多個請求,一個是請求html文件,而html文件裏面的引入文件的標籤又給這個網站發送了請求靜態文件的請求,因此要將創建鏈接的過程循環起來,才能接受多個請求。
while 1:
    conn,addr = sk.accept()
    # while 1:
    from_b_msg = conn.recv(1024)
    str_msg = from_b_msg.decode('utf-8')
    #經過http協議知道,瀏覽器請求的時候,有一個請求內容的路徑,經過對請求信息的分析,這個路徑在請求的全部請求信息中能夠提煉出來,下面的path就是提煉出來的路徑
    path = str_msg.split('\r\n')[0].split(' ')[1]
    print('path>>>',path)
    conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    #因爲整個頁面須要html、css、js、圖片等一系列的文件,因此都須要給人家瀏覽器發送過去,瀏覽器纔能有這些文件,才能很好的渲染頁面
    #根據不一樣的路徑來返回響應的內容
    if path == '/': #返回html文件
        print(from_b_msg)
        with open('test.html','rb') as f:
        # with open('Python開發.html','rb') as f:
            data = f.read()
        conn.send(data)
        conn.close()
    elif path == '/meinv.png': #返回圖片
        with open('meinv.png','rb') as f:
            pic_data = f.read()
        # conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
        conn.send(pic_data)
        conn.close()
    elif path == '/test.css': #返回css文件
        with open('test.css','rb') as f:
            css_data = f.read()
        conn.send(css_data)
        conn.close()

    elif path == '/wechat.ico':#返回頁面的ico圖標
        with open('wechat.ico','rb') as f:
            ico_data = f.read()
        conn.send(ico_data)
        conn.close()

    elif path == '/test.js': #返回js文件
        with open('test.js','rb') as f:
            js_data = f.read()
        conn.send(js_data)
        conn.close()

    #注意:上面每個請求處理完以後,都有一個conn.close()是由於,HTTP協議是短連接的,一次請求對應一次響應,這個請求就結束了,因此須要寫上close,否則瀏覽器本身斷了,本身寫的服務端沒有斷,就會出問題。
test.py

運行起來py文件,而後在瀏覽器訪問一下服務端,看效果:

    

徹底搞定了,本身經過socket已經徹底搞定了web項目

四:函數版高級web框架

html文件和其餘的靜態文件仍是上面使用的。

python代碼以下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time    : 2019/2/17 14:06
# @Author  : wo
# @Site    :
# @File    : test.py
# @Software: PyCharm
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()

#處理頁面請求的函數
def func1(conn):
    with open('test.html', 'rb') as f:
        # with open('Python開發.html','rb') as f:
        data = f.read()
    conn.send(data)
    conn.close()

#處理頁面img標籤src屬性值是本地路徑的時候的請求
def func2(conn):
    with open('meinv.png', 'rb') as f:
        pic_data = f.read()
    # conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    conn.send(pic_data)
    conn.close()
#處理頁面link( <link rel="stylesheet" href="test.css">)標籤href屬性值是本地路徑的時候的請求
def func3(conn):
    with open('test.css', 'rb') as f:
        css_data = f.read()
    conn.send(css_data)
    conn.close()

#處理頁面link(<link rel="icon" href="wechat.ico">)標籤href屬性值是本地路徑的時候的請求
def func4(conn):
    with open('wechat.ico', 'rb') as f:
        ico_data = f.read()
    conn.send(ico_data)
    conn.close()

#處理頁面script(<script src="test.js"></script>)標籤src屬性值是本地路徑的時候的請求
def func5(conn):
    with open('test.js', 'rb') as f:
        js_data = f.read()
    conn.send(js_data)
    conn.close()

while 1:
    conn,addr = sk.accept()
    # while 1:
    from_b_msg = conn.recv(1024)
    str_msg = from_b_msg.decode('utf-8')
    path = str_msg.split('\r\n')[0].split(' ')[1]
    print('path>>>',path)
    conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    print(from_b_msg)
    if path == '/':
        func1(conn)
    elif path == '/meinv.png':
        func2(conn)
    elif path == '/test.css':
        func3(conn)
    elif path == '/wechat.ico':
        func4(conn)

    elif path == '/test.js':
        func5(conn)   
View Code

五 更高級版(多線程版)web框架

應用上併發編程的內容,反正html文件和靜態文件都直接給瀏覽器,那就一塊併發處理,html文件和靜態文件仍是上面的。

python代碼以下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time    : 2019/2/17 14:06
# @Author  : wo
# @Site    : 
# @File    : test.py
# @Software: PyCharm
import socket
from threading import Thread
#注意一點,不開多線程徹底是能夠搞定的。

sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()

def func1(conn):
    with open('test.html', 'rb') as f:
        # with open('Python開發.html','rb') as f:
        data = f.read()
    conn.send(data)
    conn.close()

def func2(conn):
    with open('meinv.png', 'rb') as f:
        pic_data = f.read()
    # conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    conn.send(pic_data)
    conn.close()

def func3(conn):
    with open('test.css', 'rb') as f:
        css_data = f.read()
    conn.send(css_data)
    conn.close()

def func4(conn):
    with open('wechat.ico', 'rb') as f:
        ico_data = f.read()
    conn.send(ico_data)
    conn.close()

def func5(conn):
    with open('test.js', 'rb') as f:
        js_data = f.read()
    conn.send(js_data)
    conn.close()

while 1:
    conn,addr = sk.accept()
    # while 1:
    from_b_msg = conn.recv(1024)
    str_msg = from_b_msg.decode('utf-8')
    path = str_msg.split('\r\n')[0].split(' ')[1]
    print('path>>>',path)
    conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    print(from_b_msg)
    if path == '/':
        # func1(conn)
        t = Thread(target=func1,args=(conn,))
        t.start()
    elif path == '/meinv.png':
        # func2(conn)
        t = Thread(target=func2, args=(conn,))
        t.start()
    elif path == '/test.css':
        # func3(conn)
        t = Thread(target=func3, args=(conn,))
        t.start()
    elif path == '/wechat.ico':
        # func4(conn)
        t = Thread(target=func4, args=(conn,))
        t.start()
    elif path == '/test.js':
        # func5(conn)
        t = Thread(target=func5, args=(conn,))
        t.start()
View Code

六 更更高級版web框架

if判斷太多了,開線程的方式也比較噁心,有多少個if判斷,就寫多少次建立線程,簡化一下:

import socket
from threading import Thread

sk = socket.socket()
sk.bind(('127.0.0.1',8001))
sk.listen()

def func1(conn):

    conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n')
    with open('test.html', 'rb') as f:
        # with open('Python開發.html','rb') as f:
        data = f.read()
    conn.send(data)
    conn.close()

def func2(conn):
    conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
    with open('meinv.png', 'rb') as f:
        pic_data = f.read()
    # conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
    conn.send(pic_data)
    conn.close()

def func3(conn):
    conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
    with open('test.css', 'rb') as f:
        css_data = f.read()
    conn.send(css_data)
    conn.close()

def func4(conn):
    conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
    with open('wechat.ico', 'rb') as f:
        ico_data = f.read()
    conn.send(ico_data)
    conn.close()

def func5(conn):
    conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
    with open('test.js', 'rb') as f:
        js_data = f.read()
    conn.send(js_data)
    conn.close()

#定義一個路徑和執行函數的對應關係,再也不寫一堆的if判斷了
l1 = [
    ('/',func1),
    ('/meinv.png',func2),
    ('/test.css',func3),
    ('/wechat.ico',func4),
    ('/test.js',func5),
]

#遍歷路徑和函數的對應關係列表,並開多線程高效的去執行路徑對應的函數,
def fun(path,conn):
    for i in l1:
        if i[0] == path:
            t = Thread(target=i[1],args=(conn,))
            t.start()
    # else:
    #     conn.send(b'sorry')

while 1:
    conn,addr = sk.accept()
    #看完這裏面的代碼以後,就能夠思考一個問題,不少人要同時訪問你的網站,你在請求這裏是否是能夠開起併發編程的思想了,多進程+多線程+協程,妥妥的支持高併發,再配合服務器集羣,這個網頁就支持大量的高併發了,可是寫的太low了,並且功能不好,容錯能力也不好,若是有能力,如今徹底能夠本身寫web框架了,寫一個nb的,若是如今沒有這個能力,那麼就來學別人寫好的框架,首先第一個就是django框架,其實就是將這些功能封裝起來,而且容錯能力強,抗壓能力強。
    # while 1:
    from_b_msg = conn.recv(1024)
    str_msg = from_b_msg.decode('utf-8')
    path = str_msg.split('\r\n')[0].split(' ')[1]
    print('path>>>',path)
    # 注意:由於開啓的線程很快,可能致使文件尚未發送過去,其餘文件的請求已經來了,致使你文件信息沒有被瀏覽器正確的認識,因此須要將發送請求行和請求頭的部分寫道前面的每個函數裏面去,而且防止出現瀏覽器可能不能識別html文件的狀況,須要在發送html文件的那個函數裏面的發送請求行和請求頭的部分加上兩個請求頭content-type:text/html\r\ncharset:utf-8\r\n
    # conn.send(b'HTTP/1.1 200 ok\r\n\r\n')  不這樣寫了
    # conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n')  不這樣寫了
    print(from_b_msg)
    #執行這個fun函數並將路徑和conn管道都做爲參數傳給他
    fun(path,conn)
View Code

七 根據不一樣路徑返回不一樣頁面的web框架

既然知道了能夠根據不一樣的請求路徑來返回不一樣的內容,那麼可不能夠根據用戶訪問的不一樣路徑,返回不一樣的頁面,應該是能夠的

本身建立兩個html文件,寫幾個標籤在裏面,名爲index.html和home.html,而後根據不一樣的路徑返回不一樣的頁面,就寫上python代碼:

"""
根據URL中不一樣的路徑返回不一樣的內容
返回獨立的HTML頁面
"""

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


# 將返回不一樣的內容部分封裝成函數
def index(url):
    # 讀取index.html頁面的內容
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
    # 返回字節數據
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# 定義一個url和實際要執行的函數的對應關係
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # 等待鏈接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客戶端發來的消息
    # 從data中取到路徑
    data = str(data, encoding="utf8")  # 把收到的字節類型的數據轉換成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行
    # 根據不一樣的路徑返回不一樣內容
    func = None  # 定義一個保存將要執行的函數名的變量
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # 返回具體的響應消息
    conn.send(response)
    conn.close()
View Code

8、返回動態頁面的web框架

這網頁可以顯示出來了,可是都是靜態的。頁面的內容都不會變化的,想要的是動態網站,動態網站的意思是裏面有動態變化的數據,而不是頁面裏面有動態效果。

選擇使用字符串替換來實現這個需求。(這裏使用時間戳來模擬動態的數據)

"""
根據URL中不一樣的路徑返回不一樣的內容
返回HTML頁面
讓網頁動態起來
"""

import socket
import time

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


# 將返回不一樣的內容部分封裝成函數
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = str(time.time())
        s = s.replace("@@oo@@", now)  # 在網頁中定義好特殊符號,用動態的數據去替換提早定義好的特殊符號
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# 定義一個url和實際要執行的函數的對應關係
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # 等待鏈接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客戶端發來的消息
    # 從data中取到路徑
    data = str(data, encoding="utf8")  # 把收到的字節類型的數據轉換成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行
    # 根據不一樣的路徑返回不一樣內容
    func = None  # 定義一個保存將要執行的函數名的變量
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # 返回具體的響應消息
    conn.send(response)
    conn.close()
View Code

可是注意一個問題,在裏面獲取路徑的時候,是按照\r\n來分割而後再經過空格來分割獲取到的路徑,可是若是不是http協議的話,要注意消息格式了。

接下來看一個寫好的模塊來搞的web框架,這個模塊叫作wsgiref

9、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開發環境用的就是這個模塊來作服務器。

先看看wsfiref怎麼使用

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', [('Content-Type', 'text/html'),('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() 
View Code

來一個完整的web項目,用戶登陸認證的項目,須要鏈接數據庫了,因此先到mysql數據庫裏面準備一些表和數據

mysql> create database db1;
Query OK, 1 row affected (0.00 sec)

mysql> use db1;
Database changed
mysql> create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null);
Query OK, 0 rows affected (0.23 sec)

mysql> insert into userinfo(username,password) values('chao','666'),('sb1','222');
Query OK, 2 rows affected (0.03 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> select * from userinfo;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | chao     | 666      |
|  2 | sb1      | 222      |
+----+----------+----------+
2 rows in set (0.00 sec) 
View Code

而後再建立這麼幾個文件:

python文件名稱webmodel.py,內容以下:

#建立表,插入數據
def createtable():
    import pymysql
    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='666',
        database='db1',
        charset='utf8'
    )
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    sql = '''
        -- 建立表
        create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null);
        -- 插入數據
        insert into userinfo(username,password) values('chao','666'),('sb1','222');
    '''
    cursor.execute(sql)
    conn.commit()
    cursor.close()
    conn.close()
View Code

python的名爲webauth文件,內容以下:

#對用戶名和密碼進行驗證
def auth(username,password):
    import pymysql
    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='db1',
        charset='utf8'
    )
    print('userinfo',username,password)
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    sql = 'select * from userinfo where username=%s and password=%s;'
    res = cursor.execute(sql, [username, password])
    if res:
        return True
    else:
        return False
View Code

用戶輸入用戶名和密碼的文件,名爲web.html,內容以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--若是form表單裏面的action什麼值也沒給,默認是往當前頁面的url上提交的數據,因此能夠本身指定數據的提交路徑-->
<!--<form action="http://127.0.0.1:8080/auth/" method="post">-->
<form action="http://127.0.0.1:8080/auth/" method="get">
    用戶名<input type="text" name="username">
    密碼 <input type="password" name="password">
    <input type="submit">
</form>

<script>

</script>
</body>
</html>
View Code

用戶驗證成功後跳轉的頁面,顯示成功,名爲websuccess.html,內容以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        h1{
            color:red;
        }
    </style>
</head>
<body>
<h1>寶貝兒,恭喜你登錄成功啦</h1>


</body>
</html>
View Code

python服務端代碼(主邏輯代碼),名爲web_python.py:

from urllib.parse import parse_qs
from wsgiref.simple_server import make_server
import webauth
def application(environ, start_response):

    # start_response('200 OK', [('Content-Type', 'text/html'),('k1','v1')])
    # start_response('200 OK', [('Content-Type', 'text/html'),('charset','utf-8')])
    start_response('200 OK', [('Content-Type', 'text/html')])
    print(environ)
    print(environ['PATH_INFO'])
    path = environ['PATH_INFO']
    #用戶獲取login頁面的請求路徑
    if path == '/login':
        with open('web.html','rb') as f:
            data = f.read()
    #針對form表單提交的auth路徑,進行對應的邏輯處理
    elif path == '/auth/':
        #登錄認證
        #1.獲取用戶輸入的用戶名和密碼

        #2.去數據庫作數據的校驗,查看用戶提交的是否合法
        # user_information = environ['']
        if environ.get("REQUEST_METHOD") == "POST":
            #獲取請求體數據的長度,由於提交過來的數據須要用它來提取,注意POST請求和GET請求的獲取數據的方式不一樣
            try:
                request_body_size = int(environ.get('CONTENT_LENGTH', 0))
            except (ValueError):
                request_body_size = 0
            #POST請求獲取數據的方式
            request_data = environ['wsgi.input'].read(request_body_size)
            print('>>>>>',request_data) # >>>>> b'username=chao&password=123',是個bytes類型數據
            print('?????',environ['QUERY_STRING']) #????? 空的,由於post請求只能按照上面這種方式取數據
            #parse_qs能夠幫咱們解析數據
            re_data = parse_qs(request_data)
            print('拆解後的數據',re_data) #拆解後的數據 {b'password': [b'123'], b'username': [b'chao']}            #post請求的返回數據我就不寫啦        pass
        if environ.get("REQUEST_METHOD") == "GET":
            #GET請求獲取數據的方式,只能按照這種方式取
            print('?????',environ['QUERY_STRING']) #????? username=chao&password=123,是個字符串類型數據
            request_data = environ['QUERY_STRING']

            # parse_qs能夠幫咱們解析數據
            re_data = parse_qs(request_data)
            print('拆解後的數據', re_data) #拆解後的數據 {'password': ['123'], 'username': ['chao']}
            username = re_data['username'][0]
            password = re_data['password'][0]
            print(username,password)
            #進行驗證:
            status = webauth.auth(username,password)
            if status:
            # 3.將相應內容返回
                with open('websuccess.html','rb') as f:
                    data = f.read()
            else:
                data = b'auth error'
        # 可是無論是post仍是get請求都不能直接拿到數據,拿到的數據還須要咱們來進行分解提取,因此引入urllib模塊來分解
        #注意,若是直接返回中文,沒有給瀏覽器指定編碼格式,默認是gbk,因此須要gbk來編碼一下,瀏覽器才能識別
        # data='登錄成功!'.encode('gbk')
    else:
        data = b'sorry 404!,not found the page'
    return [data]

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

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

把代碼拷走,建立文件,放到同一個目錄下,運行一下we_python.py文件的代碼就能看到效果,注意先輸入的網址是127.0.0.1:8080/login ,還要注意mysql數據庫沒有問題。

     

10、起飛版web框架

    上一個web框架把全部的代碼都寫在了一個py文件中,拆到其餘文件裏面去,而且針對不用的路徑來進行分發請求的時候都用的if判斷,不少值得優化的地方,結合前面幾個版本的優點來優化一下,分幾個文件和文件夾https://pan.baidu.com/s/1Ns5QHFpZGusGHuHzrCto3A

   MVC框架是什麼呢:

    M:model.py 就是和數據庫打交道用的,建立表等操做

    V:View 視圖(視圖函數,html文件)

    C:controller 控制器(urls文件裏面的內容,url(路徑)分發與視圖函數的邏輯處理)

  Django叫作MTV框架

    M:model.py 就是和數據庫打交道用的,建立表等操做

    T:templates 存放HTML文件的

    V:View 視圖函數(邏輯處理)

    會發現MTV比MVC少一個url分發的部分

    因此django還有一個叫作url控制器(路徑分發)的東西,MTV+url控制器就是django要學的內容。     

二 模板渲染JinJa2

上面的代碼實現了一個簡單的動態頁面(字符串替換),徹底能夠從數據庫中查詢數據,而後去替換html中的對應內容(專業名詞叫作模板渲染,先渲染一下,再給瀏覽器進行渲染),而後再發送給瀏覽器完成渲染。 這個過程就至關於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展現的數據。 這裏用的特殊符號是定義的,其實模板渲染有個現成的工具: jinja2  

下載:

pip install jinja2

來一個html文件,index2,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>Title</title>
</head>
<body>
    <h1>姓名:{{name}}</h1>
    <h1>愛好:</h1>
    <ul>
        {% for hobby in hobby_list %}
        <li>{{hobby}}</li>
        {% endfor %}
    </ul>
</body>
</html>
View Code

使用jinja2渲染index2.html文件,建立一個python文件,代碼以下:

from wsgiref.simple_server import make_server
from jinja2 import Template


def index():
    with open("index2.html", "r",encoding='utf-8') as f:
        data = f.read()
    template = Template(data)  # 生成模板文件
    ret = template.render({"name": "于謙", "hobby_list": ["燙頭", "泡吧"]})  # 把數據填充到模板裏面
    return [bytes(ret, encoding="utf8"), ]


# 定義一個url和函數的對應關係
URL_LIST = [
    ("/index/", index),
]

def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 設置HTTP響應的狀態碼和頭信息
    url = environ['PATH_INFO']  # 取到用戶輸入的url
    func = None  # 將要執行的函數
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去以前定義好的url列表裏找url應該執行的函數
            break
    if func:  # 若是能找到要執行的函數
        return func()  # 返回函數的執行結果
    else:
        return [bytes("404沒有該頁面", encoding="utf8"), ]


if __name__ == '__main__':
    httpd = make_server('', 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
View Code

如今的數據是本身手寫的,那可不能夠從數據庫中查詢數據,來填充頁面呢?使用pymysql鏈接數據庫:

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select name, age, department_id from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()
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;
View Code

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

三 MVC和MTV框架

MVC

Web服務器開發領域裏著名的MVC模式,所謂MVC就是把Web應用分爲模型(M),控制器(C)和視圖(V)三層,他們之間以一種插件式的、鬆耦合的方式鏈接在一塊兒,模型負責業務對象與數據庫的映射(ORM),視圖負責與用戶的交互(頁面),控制器接受用戶的輸入調用模型和視圖完成用戶的請求,其示意圖以下所示:

MTV

Django的MTV模式本質上和MVC是同樣的,也是爲了各組件間保持鬆耦合關係,只是定義上有些許不一樣,Django的MTV分別是值:

  • M 表明模型(Model): 負責業務對象和數據庫的關係映射(ORM)。

  • T 表明模板 (Template):負責如何把頁面展現給用戶(html)。

  • V 表明視圖(View): 負責業務邏輯,並在適當時候調用Model和Template。

  除了以上三層以外,還須要一個URL分發器,它的做用是將一個個URL的頁面請求分發給不一樣的View處理,View再調用相應的Model和Template,MTV的響應模式以下所示:

  

  通常是用戶經過瀏覽器向咱們的服務器發起一個請求(request),這個請求回去訪問視圖函數,(若是不涉及到數據調用,那麼這個時候視圖函數返回一個模板也就是一個網頁給用戶),視圖函數調用模型,模型去數據庫查找數據,而後逐級返回,視圖函數把返回的數據填充到模板中空格中,最後返回網頁給用戶。

四 Django下載安裝

Django官網下載頁面

一、下載Django:

pip3 installdjango==1.11.9

二、建立一個django project

django``-``admin startproject mysite建立了一個名爲"mysite"的Django 項目:

  

當前目錄下會生成mysite的工程,目錄結構以下:(注意,pip下載下來的django就理解成一個模塊,而不是django項目,這個模塊能夠建立django項目)

  

  • manage.py ----- Django項目裏面的工具,經過它能夠調用django shell和數據庫,啓動關閉項目與項目交互等,無論將框架分了幾個文件,必然有一個啓動文件,其實他們自己就是一個文件。

  • settings.py ---- 包含了項目的默認設置,包括數據庫信息,調試標誌以及其餘一些工做的變量。

  • urls.py ----- 負責把URL模式映射到應用程序。

  • wsgi.py ---- runserver命令就使用wsgiref模塊作簡單的web server,全部與socket相關的內容都在這個文件裏面了。

python manage.py runserver 127.0.0.1:8080  #此時已經能夠啓動django項目了,只不過什麼邏輯也沒有呢

上面沒有什麼view視圖函數的文件,這裏說一個應用與項目的關係,上面只是建立了一個項目,並無建立應用,以微信來舉例,微信是否是一個大的項目,可是微信裏面是否是有不少個應用,支付應用、聊天應用、朋友圈、小程序等這些在必定程度上都是相互獨立的應用,也就是說一個大的項目裏面能夠有多個應用,也就是說項目是包含應用的,它沒有將view放到這個項目目錄裏面是由於它以爲,一個項目裏面能夠有多個應用,而每一個應用都有本身這個應用的邏輯內容,因此他以爲這個view應該放到應用裏面,好比微信,剛纔說了幾個應用,這幾個應用的邏輯能放到一塊兒是就亂套啦,也很差管理和維護,因此這些應用的邏輯都分開來放,它就幫咱們提煉出來了,提煉出來一個叫作應用的東西,因此咱們須要來建立這個應用。    

三、在mysite目錄下建立應用

python manage.py startapp blog #經過執行manage.py文件來建立應用,執行這句話必定要注意,應該在這個manage.py的文件所在目錄下執行這句話,由於其餘目錄裏面沒有這個文件python manage.py startapp blog2 #每一個應用都有本身的目錄,每一個應用的目錄下都有本身的views.py視圖函數和models.py數據庫操做相關的文件

  

如今只須要看其中兩個文件

models.py :以前寫的那個名爲model的文件就是建立表用的,這個文件就是存放與該app(應用)相關的表結構的

views.py :存放與該app相關的視圖函數的

四、啓動django項目

python manage.py runserver ``8080 #python manage.py runserver 127.0.0.1:8080,本機就不用寫ip地址了 若是連端口都沒寫,默認是本機的8000端口

這樣django就啓動起來了!當訪問:http://127.0.0.1:8080/時就能夠看到:

 

學習Django,就學上面的這些文件,怎麼在MTV+url分發的功能下來使用。

其實未來建立django項目,不多用命令行,就用pycharm來建立,看圖:

  

 

  

看項目目錄:

    

 

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

如今實現一個用戶輸入一個timer路徑,返回一個含有當前時間的頁面,用戶輸入網址-->路徑-->函數-->返回數據(文件)

url控制器(第一步就找它)

django 1.11.9版本的url寫法:from django.conf.urls import urlfrom django.contrib import adminfrom crm import viewsurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^index/', views.index),]
下面是django2.x版本的url寫法,不太同樣了,可是兼容1.x的,不過如今仍是主要說1.xx版本的,因此寫url的時候按照上的方式寫。from django.contrib import admin
from django.urls import path

#找對應的函數,是哪一個app裏面的函數
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls), 
    path('index/',views.index),
]
View Code

視圖

from django.shortcuts import render,HttpResponse

# Create your views here.
#邏輯和返回數據
def index(request):

    import datetime
    now=datetime.datetime.now()
    ctime=now.strftime("%Y-%m-%d %X")
   #return HttpResponse('哈哈,好玩嗎?')
    return render(request,"index.html",{"ctime":ctime}) #render,渲染html頁面文件並返回給瀏覽器
View Code

模板

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

<h4>當前時間:{{ ctime }}</h4>

</body>
</html>
View Code

經過pycharm來運行項目:

    

看控制檯:

    

 

執行效果以下:

    

 

想本身配置啓動的端口:

  

    

     

在settings配置文件裏面有關於templates(放html文件的配置):

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')] #有些版本的django沒有寫這個,本身寫一下,就是配置一個django找html文件的路徑,render方法就來這裏找html文件
        ,
        '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',
            ],
        },
    },
]
View Code

以前用wsgiref,如今都不須要寫了。

還有一點:post請求的時候會發現一個 Forbidden的錯誤:

    

 

如今只須要作一步,在settings配置文件裏面將這一行註釋掉,這是django加的一個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',
]
View Code

還記得django寫視圖函數的時候,有一個參數是必需要給的,叫作request,若是是post請求,那麼就用request.POST,就能拿到post請求提交過來的全部數據(一個字典,而後再經過字典取值request.POST.get('username'),取出來的就是個字符串,在那個字典裏面看到的是{'username':['chao']},雖然看着是列表,可是request.POST.get('username')取出來的就是個字符串),經過request.GET就能拿到提交過來的全部數據,並且記着,每個視圖函數都要給人家返回一些內容,用render或者HttpResponse等,其實render裏面也是經過HttpResponse來返回內容,否則會報錯,錯誤是告訴你沒有返回任何內容:

    

相關文章
相關標籤/搜索