咱們能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端,基於請求作出響應,客戶都先請求,服務端作出對應的響應,按照http協議的請求協議發送請求,服務端按照http協議的響應協議來響應請求,這樣的網絡通訊,咱們就能夠本身實現Web框架了。css
經過對socket的學習,咱們知道網絡通訊,咱們徹底能夠本身寫了,由於socket就是作網絡通訊用的,下面咱們就基於socket來本身實現一個web框架,寫一個web服務端,讓瀏覽器來請求,並經過本身的服務端把頁面返回給瀏覽器,瀏覽器渲染出咱們想要的效果。html
html文件內容以下,名稱爲test.html:python
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</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> <!--再準備一個圖片,名稱爲meinv.jpg,再準備一個ico文件,名稱爲wechat.ico,其實就是個圖片文件,微信官網打開以後,在瀏覽器最上面可以看到-->
而後開始寫咱們的web框架,咱們分這麼幾步來寫:mysql
1、簡單的web框架web
建立一個python文件,內容以下,名稱爲test.py:sql
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') with open('test.html','rb') as f: f_data = f.read() conn.send(f_data)
頁面上輸入網址看效果,css和js代碼的效果也有,very good數據庫
可是咱們知道,咱們的css和js基本都是寫在本地的文件裏面的啊,並且咱們的圖片基本也是咱們本身本地的啊,怎麼辦,咱們將上面咱們提早準備好的js和css還有那個.ico結尾的圖片文件都準備好,來咱們在來一個升級版的web框架,其實css、js、圖片等文件都叫作網站的靜態文件。django
首先咱們先看一個效果,若是咱們直接將咱們寫好的css和js還有.ico和圖片文件插入到咱們的html頁面裏面,就是下面這個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"> </head> <body> <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1> <img src="meinv.png" alt="" width="100" height="100"> <script src="test.js"></script> </body> </html> <!--css文件內容以下,名稱爲test.css: --> h1{ background-color: green; color: white; } <!-- js文件內容以下,名稱爲test.js:--> alert('這是咱們第一個網頁');
2、返回靜態文件的高級web框架瀏覽器
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,否則瀏覽器本身斷了,你本身寫的服務端沒有斷,就會出問題。
運行起來咱們的py文件,而後在瀏覽器訪問一下咱們的服務端,看效果
徹底搞定了,本身經過socket已經徹底搞定了web項目,激動不,哈哈,咱們再來完善一下
3、更高級版(函數+多線程版+動態獲取時間戳)web框架
# -*- coding: utf-8 -*- # @Time : 2019/7/12 17:11 # @Author : AnWen import time import socket from threading import Thread server = socket.socket() server.bind(('127.0.0.1', 9000)) server.listen() def html(conn): time_msg=str(time.time()) with open('test.html', 'r',encoding='utf-8') as f: date = f.read() # 在網頁中定義好特殊符號,用動態的數據去替換提早定義好的特殊符號 date=date.replace('%這是被替換字符串%',time_msg) date=date.encode('utf-8') conn.send(date) conn.close() def css(conn): with open('test.css', 'rb') as f: date = f.read() conn.send(date) conn.close() def js(conn): with open('test.js', 'rb') as f: date = f.read() conn.send(date) conn.close() def ico(conn): with open('wechat.ico', 'rb') as f: date = f.read() conn.send(date) conn.close() def jpg(conn): with open('window.jpg', 'rb') as f: date = f.read() conn.send(date) conn.close() #定義一個路徑和執行函數的對應關係,再也不寫一堆的if判斷了 urlpatterns = [ ('/', html), ('/test.css', css), ('/test.js', js), ('/wechat.ico', ico), ('/window.jpg', jpg) ] while True: conn, addr = server.accept() request_str = conn.recv(1024).decode('utf-8') path = request_str.split('\r\n')[0].split(' ')[1] print(path) # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 conn.send(b'HTTP/1.1 200 ok\r\n\r\n') #遍歷路徑和函數的對應關係列表,並開多線程高效的去執行路徑對應的函數, for item in urlpatterns: if path==item[0]: t=Thread(target=item[1],args=(conn,)) t.start()
4、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:8080,這個打印的是'/',輸入的是127.0.0.1:8080/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()
來一個完整的web項目,咱們須要鏈接數據庫了,因此先到mysql數據庫裏面準備一些表和數據
# -*- coding: utf-8 -*- # @Time : 2019/7/12 18:33 # @Author : AnWen import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123456', database='day53', charset='utf8', ) cursor = conn.cursor(pymysql.cursors.DictCursor) #建立表 sql = "create table userinfo(id int primary key auto_increment,name char(12),age int not null);" cursor.execute(sql) conn.commit() cursor.close() conn.close()
import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123456', database='day53', charset='utf8', ) cursor = conn.cursor(pymysql.cursors.DictCursor) #插入數據 sql = "insert into userinfo(name,age) values ('anwen',20);" cursor.execute(sql) conn.commit() cursor.close() conn.close()
# -*- coding: utf-8 -*- # @Time : 2019/7/12 18:55 # @Author : AnWen import pymysql def showdata(): conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123456', database='day53', charset='utf8', ) cursor = conn.cursor(pymysql.cursors.DictCursor) #查詢數據 sql = 'select * from userinfo' cursor.execute(sql) data=cursor.fetchone() conn.close() conn.cursor() return data # showdata()
wsgiref模塊版web框架
# -*- coding: utf-8 -*- # @Time : 2019/7/12 12:17 # @Author : AnWen import time from wsgiref.simple_server import make_server from showdata import showdata def html(): #獲取數據庫數據 userinfo_data=showdata() # {'id': 1, 'name': 'anwen', 'age': 20} with open('test.html', 'r', encoding='utf-8') as f: date = f.read() date = date.replace('%這是被替換字符串%', userinfo_data['name']) date = date.encode('utf-8') return date def css(): with open('test.css', 'rb') as f: date = f.read() return date def js(): with open('test.js', 'rb') as f: date = f.read() return date def ico(): with open('wechat.ico', 'rb') as f: date = f.read() return date def jpg(): with open('window.jpg', 'rb') as f: date = f.read() return date urlpatterns = [ ('/', html), ('/test.css', css), ('/test.js', js), ('/wechat.ico', ico), ('/window.jpg', jpg) ] def application(environ, start_response): ''' :param environ: 是所有加工好的請求信息,加工成了一個字典,經過字典取值的方式就能拿到不少你想要拿到的信息 :param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數 :return: ''' # print(environ) start_response('200 ok', [('k1', 'v1')]) path = environ['PATH_INFO'] for item in urlpatterns: if path == item[0]: ret = item[1]() break else: ret = '404 not found!' return [ret] httpd = make_server('127.0.0.1', 9000, application) print('Serving HTTP on port 9000...') # 開始監聽HTTP請求: httpd.serve_forever() # wsgiref自己就是個web框架,提供了一些固定的功能(請求和響應信息的封裝,不須要咱們本身寫原生的socket了也不須要我們本身來完成請求信息的提取了,提取起來很方便)
上面的代碼實現了一個簡單的動態頁面(字符串替換),我徹底能夠從數據庫中查詢數據,而後去替換我html中的對應內容(專業名詞叫作模板渲染,你先渲染一下,再給瀏覽器進行渲染),而後再發送給瀏覽器完成渲染。 這個過程就至關於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展現的數據。 我這裏用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2
下載:pip install jinja2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>第一個Web框架</title> <link rel="stylesheet" href="test.css"> <link rel="icon" href="wechat.ico"> </head> <body> <h1>%這是被替換字符串%</h1> <h1>{{userinfo}}</h1> <!--<ul>--> <!-- {% for k,v in userinfo.items() %}--> <!-- <li>{{k}}--{{v}}</li>--> <!-- {% endfor %}--> <!--</ul>--> <ul> {% for k,v in userinfo.items() %} <li>{{v}}</li> {% endfor %} </ul> <h1>嘻嘻~~</h1> <img src="window.jpg" alt="" width="100px" height="100px"> <script src="test.js"></script> </body> </html>
# -*- coding: utf-8 -*- # @Time : 2019/7/12 12:17 # @Author : AnWen import time from wsgiref.simple_server import make_server from showdata import showdata from jinja2 import Template def html(): userinfo_data=showdata() with open('6jinja2和wsgiref動態框架.html', 'r', encoding='utf-8') as f: date = f.read() # print(date) tem=Template(date) ## 生成模板文件 print(userinfo_data) #{'id': 1, 'name': 'anwen', 'age': 20} date=tem.render({'userinfo':userinfo_data}) #模板的原理就是字符串替換,咱們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。 date = date.encode('utf-8') return date def css(): with open('test.css', 'rb') as f: date = f.read() return date def js(): with open('test.js', 'rb') as f: date = f.read() return date def ico(): with open('wechat.ico', 'rb') as f: date = f.read() return date def jpg(): with open('window.jpg', 'rb') as f: date = f.read() return date urlpatterns = [ ('/', html), ('/test.css', css), ('/test.js', js), ('/wechat.ico', ico), ('/window.jpg', jpg) ] def application(environ, start_response): ''' :param environ: 是所有加工好的請求信息,加工成了一個字典,經過字典取值的方式就能拿到不少你想要拿到的信息 :param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數 :return: ''' # print(environ) ## 設置HTTP響應的狀態碼和頭信息 start_response('200 ok', [('k1', 'v1')]) path = environ['PATH_INFO'] for item in urlpatterns: if path == item[0]: ret = item[1]() break else: ret = '404 not found!' return [ret] httpd = make_server('127.0.0.1', 9000, application) print('Serving HTTP on port 9000...') httpd.serve_forever()