Web请求原理
目錄
1.web請求/響應(yīng)解析
2.url靜態(tài)響應(yīng)
3.url動態(tài)響應(yīng)
4.連接數(shù)據(jù)庫
5.使用jinja2優(yōu)化
1.web請求/響應(yīng)解析
web的原理,服務(wù)端處理監(jiān)聽狀態(tài),監(jiān)聽到該ip端口有請求了,根據(jù)請求信息返回對應(yīng)的響應(yīng),比如在百度中搜索python教程,百度會無端監(jiān)聽到之后,進(jìn)行響應(yīng)。常用的socket編程就是這樣的過程
web瀏覽器(socket客戶端):
- 創(chuàng)建socket對象
- 連接
- 發(fā)送請求
- 接收請求
- 斷開連接
百度服務(wù)器(socket服務(wù)端):
- 監(jiān)聽斷開和ip
- while Trure:
? ? ? ? 等待用戶連接
? ? ? ? 收到用戶請求
? ? ? ?響應(yīng)
? ? ? ?斷開連接
當(dāng)然實際的過程要比這個復(fù)雜很多,但是基本的過程原理是這樣,因此先模擬以上的過程
#encoding:utf-8import socketsock = socket.socket() sock.bind(('127.0.0.1',8080)) sock.listen(5)while True:conn, addr = sock.accept();# 獲取用戶發(fā)送的數(shù)據(jù)data = conn.recv(8096)print(data)conn.send(b'response')conn.close()運行之后打開瀏覽器,輸入127.0.0.1:8080展示結(jié)果如下:
因為是http請求,知道請求包括三個部分:請求行,請求頭,body三個部分,那按照http請求那樣發(fā)送/響應(yīng)按照http個要求來處理消息的請求和響應(yīng)。在上面的代碼中打印出來了客戶的請求消息如下:
GET / HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate DNT: 1 Connection: keep-alive Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0抓包看一下實際的請求和響應(yīng)的格式如下:
響應(yīng)這里:響應(yīng)行,響應(yīng)頭,響應(yīng)體。響應(yīng)體這里是一個html的字符串,因為瀏覽器認(rèn)識這個html字符串并把它變成對普通人更友好的界面進(jìn)行了展示
注意:請求頭和請求體,響應(yīng)頭和響應(yīng)體之間均有一個空白行作為分隔
因為在上述的腳本中只響應(yīng)了response字符串,沒有按照人家的這個規(guī)則進(jìn)行回復(fù),所以寫的這個東西不規(guī)范。因此模擬上述的格式進(jìn)行請求和響應(yīng)
#encoding:utf-8import socketsock = socket.socket() sock.bind(('127.0.0.1',8080)) sock.listen(5)while True:conn, addr = sock.accept();# 獲取用戶發(fā)送的數(shù)據(jù)data = conn.recv(8096)print(data)# 返回一個響應(yīng)頭conn.send(b'HTTP/1.1 200 OK\r\n\r\n')conn.send(b'response')conn.close()那現(xiàn)在在響應(yīng)的格式上已經(jīng)逐步靠近實際的web請求
2.url靜態(tài)響應(yīng)
url的全稱是統(tǒng)一資源定位符,就是通俗的理解像是在自己的PC上的找文件的路徑,得到路徑之后在搜索欄位輸入就會定位到對應(yīng)的資源,那web也是一樣,訪問不同的url得到不同的響應(yīng)
響應(yīng)的報文格式如下:
?
那這里的請求的路徑就是/,所以可以先獲取到請求的路徑,再根據(jù)路徑去響應(yīng),因為不同的請求路徑意味著客戶端請求的內(nèi)容不同,因此如何獲取url是現(xiàn)在的工作重點
注意:請求行使用空格進(jìn)行分割,請求頭中使用:進(jìn)行分割,請求頭和請求體使用空白行進(jìn)行分割
#encoding:utf-8import socketsock = socket.socket() sock.bind(('127.0.0.1',8080)) sock.listen(5)while True:conn, addr = sock.accept();# 獲取用戶發(fā)送的數(shù)據(jù)data = conn.recv(8096)data = str(data)headers, bodys = data.split('\r\n\r\n')temp_list = headers.split("\r\n")method, url, protocal = temp_list[0].split(' ')# 返回一個響應(yīng)頭conn.send(b'HTTP/1.1 200 OK\r\n\r\n')if url == "/xxx":conn.send(b'response')else:conn.send(b'404 not found')conn.close()運行之后,在瀏覽器中重寫請求
如果是/xxx會判斷等于url,則返回響應(yīng)response,如果是其他請求,則返回404。因為url有無數(shù)個對應(yīng)著有無數(shù)個響應(yīng),因此將url提出來。因此做如下改動:
運行查看結(jié)果
?
現(xiàn)在是不是有點像平時訪問的web請求的了,不同的請求給出不同的響應(yīng)。但是這個界面太丑了,能不能像平時一樣展示的稍微好看點,在之前提到說普通的響應(yīng)返回的是html字符串,那這里也模仿返回一個html字符串,那最常見的就是登陸界面,對應(yīng)的還是在f1和f2給不同的html響應(yīng)
index.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><h1>用戶登錄</h1><form><p><input type="text" placeholder="用戶名" /></p><p><input type="password" placeholder="密碼" /></p></form> </body> </html>table.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><table border="1"><thead><tr><th>ID</th><th>用戶名</th><th>郵箱</th></tr></thead><tbody><tr><th>admin</th><th>root</th><th>admin@qq.com</th></tr></tbody></table> </body> </html>對應(yīng)的將之前的f1和f2中返回的結(jié)果用這個文件替代,因為瀏覽器認(rèn)識html,看看瀏覽器給最終展示的結(jié)果如何
#encoding:utf-8 import socketdef f1(request):"""處理用戶的請求并返回相應(yīng)的內(nèi)容:request:用戶請求的所有信息:return:"""f = open('index.html','rb')data = f.read()f.close()return data# return "f1" def f2(request):"""處理用戶的請求并返回相應(yīng)的內(nèi)容:request:return:"""f = open('table.html','rb')data = f.read()f.close()return data# return b"f2"routers = [('/xxx',f1),('/ooo',f2) ]def run():# 創(chuàng)建socket對象sock = socket.socket();sock.bind(('127.0.0.1',8080))sock.listen(5)while True:conn,addr = sock.accept();# 獲取用戶發(fā)送的數(shù)據(jù)data= conn.recv(8096)data = str(data)headers,bodys = data.split('\r\n\r\n')temp_list = headers.split("\r\n")method,url,protocal = temp_list[0].split(' ')conn.send(b'HTTP/1.1 200 OK\r\n\r\n')func_Name = Nonefor item in routers:if item[0] == url:func_Name = item[1]breakif func_Name:response = func_Name(data)else:response = b'404'conn.send(response)conn.close()if __name__ == '__main__':run()重新運行代碼,并在瀏覽器上進(jìn)行范圍
這個網(wǎng)站只有兩個地址,/xxx和/ooo,所以這個網(wǎng)站就寫完了,就這樣像普通的其他的網(wǎng)站一樣訪問。那因為從界面還是底層代碼中不難發(fā)現(xiàn),這個界面展示一直是不變的,即所謂的“靜態(tài)網(wǎng)站”,但實際上我們常常訪問的是動態(tài)的網(wǎng)站,那數(shù)據(jù)動態(tài)數(shù)據(jù)當(dāng)然是從數(shù)據(jù)庫中獲取的。在上面的index和table的讀取過程中,定義的是html類型,因為是讀取自己定義的文件,所以當(dāng)然也可以是txt格式,或者任意其他格式了。
3.url動態(tài)響應(yīng)
比如在上述的table中用戶名root定義為位當(dāng)前時間,這樣的話時間一直持續(xù)變化的,不斷的刷新就會返回不同的響應(yīng)的結(jié)果,這樣就簡單的實現(xiàn)了動態(tài)響應(yīng)
def f2(request):"""處理用戶的請求并返回相應(yīng)的內(nèi)容:request:return:"""f = open('table.html','r')data = f.read()f.close()import timectime = time.time()data = data.replace("root",str(ctime))data = bytes(data)return data# return b"f2"輸出結(jié)果如下:
這是不是就是使用table這個模板展示了不同的“用戶”信息。所以這個html的角色:模板
那如果這里的用戶名不使用這個時間戳,而是使用數(shù)據(jù)庫中的數(shù)據(jù)這樣就是真正的實際使用,那假設(shè)現(xiàn)在數(shù)據(jù)庫有10個數(shù)據(jù),需要從數(shù)據(jù)庫中讀取出來這10個數(shù)據(jù)進(jìn)行展示。關(guān)于本地mysql的安裝可以在官方網(wǎng)站下載。
4.連接數(shù)據(jù)庫
本地安裝pymsyql,并創(chuàng)建數(shù)據(jù)庫/表,本地如下所示
現(xiàn)在接著第3節(jié)中的顯示的信息,這里使用mysql數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行展示
同樣創(chuàng)建一個userlist.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><table border="1"><thead><tr><th>ID</th><th>username</th><th>mail</th></tr></thead><tbody>@@content@@</tbody></table> </body> </html>要做的就是把tbody中的@@content@@替換為數(shù)據(jù)庫中的數(shù)據(jù)
def f3(request):import pymysql# 創(chuàng)建連接conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='pgtuser')# 創(chuàng)建游標(biāo)# 游標(biāo)設(shè)置為字典類型cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)# 執(zhí)行SQL,并返回收影響行數(shù)effect_row = cursor.execute("select id,username,password from userinfo")userlist = cursor.fetchall()# 關(guān)閉游標(biāo)cursor.close()# 關(guān)閉連接conn.close()# print(userlist)contentlist = []for row in userlist:tp ="<tr><th>%s</th><th>%s</th><th>%s</th></tr>" %(row['id'],row['username'],row['password'])contentlist.append(tp)content = "".join(contentlist)f = open('userlist.html','r',encoding='utf-8')template = f.read()f.close()data = template.replace('@@content@@',content)return bytes(data,encoding='utf-8')routers = [('/xxx',f1),('/ooo',f2),('/userlist.htm',f3), ]展示的結(jié)果如下
那這里的語句,將數(shù)據(jù)庫中的數(shù)據(jù)在userlist.html中替換的過程就是渲染,即數(shù)據(jù)+模板,所謂的模板就是這里的html
data = template.replace('@@content@@',content)5.使用jinja2優(yōu)化
在第4章中使用字符串替換非常麻煩,字符串寫一堆處理,其實這個還有一個別人寫好的東西可以直接操作就是jina2
在jinja2中,存在三種語法:
{%?for?user in users %}
<li>{{ user.username|title }}</li>
{% endfor %}
這里新增一個host.html,如下
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><table border="1"><thead><tr><th>ID</th><th>username</th><th>mail</th></tr></thead><tbody>{% for row in user_list %}<tr><td>{row.id}</td><td>{row.username}</td><td>{row.password}</td></tr>{% endfo %}</tbody></table> </body> </html>新增一個f4的函數(shù)
def f4(request):import pymysql# 創(chuàng)建連接conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='pgtuser')# 創(chuàng)建游標(biāo)# 游標(biāo)設(shè)置為字典類型cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)# 執(zhí)行SQL,并返回收影響行數(shù)effect_row = cursor.execute("select id,username,password from userinfo")userlist = cursor.fetchall()# 關(guān)閉游標(biāo)cursor.close()# 關(guān)閉連接conn.close()f = open('hostlist.html','r',encoding='utf-8')data = f.read()f.close()from jinja2 import Templatetemplate = Template(data)data = template.render(user_list=userlist)print(data)return data.encode('utf-8')routers = [('/xxx',f1),('/ooo',f2),('/userlist.htm',f3),('/host.html',f4) ]那這里jinjia2所做的事情就是使用第三方工具做的渲染
結(jié)合剛在我們開發(fā)的這個“網(wǎng)站”總結(jié)歸納下:
針對以上的流程,常見的web框架種類有:
?
總結(jié)
- 上一篇: JMeter初探五-配置元件与参数化
- 下一篇: centos桥接模式网络配置