python之MiniWeb框架

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

本次学习MiniWeb框架的目的并不是能够完成框架的编写,而是需要学习框架编写中的思想,主要是理解路由表的定义和自动维护、视图函数的作用、前后端分离的开发思想,以及WSGI协议中的application函数的实现与调用的思想。

以往,我们多完成的是静态web服务器,主要处理的都是一些已经‘写死’的数据,那么今天,我们来学习一下动态数据的处理。

说到动态数据,我们就需要了解一个东西,那就是web框架。

所谓web框架简单地说就是用来处理数据或模板的一个py程序。

那么接下,我就简单的给大家简述一下一个浏览器访问动态数据的整体流程。

WSGI服务器网管接口(Web Server Gateway Interface)是为python语言定义的一种服务器与框架进行通信的简单接口,其接口原理就是实现application函数。
application函数格式如下:

defapplication(environ, func): start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])return 'Hello MiniWeb'

函数的第一个参数environ类型是一个字典,用来接收封装到字典内的请求资源路径。

函数的第二个参数func类型是一个函数,用来接收调用时传递的函数引用。

application函数在是在框架中实现,框架就是处理数据的py文件;在服务器端调用。

实现application函数并且完成数据处理的py文件我们就可以称它是WSGI接口。

首先,我们先看一张图片

 

这张图片完美的阐述了从浏览器到服务器到web框架的应用。

接下来我将给大家进行简单的阐述:

首先,大家需要明确一点本次开发中以.html后缀的文件为动态资源,其余均为静态资源

1.浏览器发送HTTP请求报文给服务器,

2.服务器根据HTTP请求报文的请求路径进行判断,

如果请求的是静态资源,那么服务器直接将静态资源的数据读出来然后拼接成HTTP响应报文返回给浏览器;

如果请求的是动态资源,那么服务器会将动态资源请求发送给web框架。

3.web框架接收到服务器转发的请求后,先对请求进行判断,

如果是请求模版那么web框架将会去模版中查找请求的模板,如果查询到请求的模板那就把模板数据返回给web服务器,再有服务器返回给浏览器,

如果请求的是数据那么web框架将会去数据库中进行操作,将查询等操作的数据或返回值,返回给web服务器,再有服务器返回给浏览器。

至此一个整体流程就阐述完毕了。

1.web服务器代码实现:

#1.导入socket模块
importsocketimportthreadingimportFreamWork#创建服务器类
classHttpServerSocket(object):#给服务器类的对象设置属性
def __init__(self):#2.创建Socket对象
self.server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)#3.设置端口复用
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)#4.绑定端口
self.server_socket.bind(('', 8000))#5.设置监听
self.server_socket.listen(128)defstart(self):whileTrue:#6.等待客户端连接
client_socket, ip_port =self.server_socket.accept()#gevent.spawn(self.task, client_socket, ip_port)
print("上线了", ip_port) threading.Thread(target=self.task, args=(client_socket, ip_port), daemon=True).start()deftask(self, client_socket, ip_port):#7.接收数据
recv_data = client_socket.recv(1024).decode('utf-8')print(recv_data)if notrecv_data:print("客户端:%s下线了,端口号为:%s" %ip_port)return

#8.发送数据
#判断请求资源是否包含参数
#请求行格式:GET /index.html HTTP/1.1
recv_path = recv_data.split()[1]#print("第一次分割",recv_path)
#如果有参数则以?分割
if '?' inrecv_path: real_recv_path= recv_path.split('?')[0]#print("?分割",real_recv_path)
else:#如果没有参数,则保持请求路径不变
real_recv_path =recv_path#print("无?分割",real_recv_path)

#设置没指定资源路径,默认返回index.html
if real_recv_path == '/': real_recv_path= '/index.html'

#判断请求资源是静态资源还是动态资源
if real_recv_path.endswith('.html'): env = {'PATH_INFO': real_recv_path} # 调用框架中的application函数 response_body = FreamWork.application(env, self.start_response) response_line = 'HTTP/1.1 %s\r\n' % self.status response_header = 'Server: PWS/1.0\r\n' # self.response_header 接收的是列表中保存的元组需要进行解包处理 response_header += '%s :%s\r\n' % self.response_header[0] send_data = (response_line + response_header + '\r\n' + response_body).encode('utf8') client_socket.send(send_data) client_socket.close()else:#判断请求的资源路径是否存在
try: with open(f"static{real_recv_path}", "rb") as file: response_body=file.read()exceptException as e:#如果不存在则返回404
response_line = 'HTTP/1.1 404 NOT FOUND\r\n'response_header= 'Server: PWS/1.0\r\n'response_body= 'sorry nor found page!\r\n'.capitalize() send_data= (response_line + response_header + '\r\n' + response_body).encode('utf-8') client_socket.send(send_data)else:#如果存在则换回请求的页面信息
response_line = 'HTTP/1.1 200 OK\r\n'response_header= 'Server: PWS/1.0\r\n'send_data= (response_line + response_header + '\r\n').encode('utf-8') +response_body client_socket.send(send_data)finally:#断开与客户端连接
client_socket.close()defstart_response(self, status, response_header): self.status=status self.response_header=response_headerdef __del__(self):#当服务端程序结束时停止服务器服务
self.server_socket.close()defmain(): http_socket=HttpServerSocket() http_socket.start()if __name__ == '__main__': main()

核心代码(代码段中颜色为红色并加粗的代码)解读:

1.通过分解后的请求资源路径的后缀判断请求的是否是html页面,如果是则认为请求的是动态资源;

2.将动态资源路径封装到一个字典中,并将字典和函数的引用传递给application函数,

3.web框架(application函数)根据传递的资源路径去模板中查找是否含有请求的模板,如果有则读取模版数据并返回;

4.接收到web框架(application函数)返回的数据,并拼接HTTP响应报文,然后通过浏览器套接字将HTTP响应报文发送给浏览器,最后关闭与浏览器连接。

2.MiniWeb框架代码实现:

importdbimportjson#定义一个路由表
router_table ={}#编写接口函数#environ参数用来接收请求资源路径#start_response参数用来接收传递过来的函数
defapplication(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')]) func=errorif environ['PATH_INFO'] inrouter_table: func= router_table[environ['PATH_INFO']] body=func()returnbody#定义一个装饰器,用来实现路由表的自动维护
defrouter(url):defdecorator(func):def inner(*args, **kwargs): body= func(*args, **kwargs)returnbody router_table[url]=funcreturninnerreturndecorator#前后端不分离技术
@router('/index.html')defindex(): db_data=db.db_select() with open('templates/index.html', 'r') as file: body=file.read() row_str= """<tr> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td> <input type="button" value="添加" name="toAdd" systemidvaule="%s"> </td> </tr>"""body_data= ''
for data indb_data: body_data+= row_str % (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[1]) body= body.replace('{%content%}', body_data)returnbody#前后端分离技术
@router('/center.html')defcenter(): with open('templates/center.html', 'r', encoding='utf-8') as file: body=file.read()returnbody#编写接口函数
defcenter_data(): result=db.db_select_interface()#编写JSON数据
#JSON类型数据格式:[{"key":"value"},{"key":"value"},{"key":"value"}]
data =[]for i inresult: dd={} dd['code'] =i[0] dd['short'] = i[1] dd['chg'] = i[2] dd['turnover'] = i[3] dd['price'] = i[4] dd['highs'] = i[5] dd['info'] = i[6] data.append(dd)#将数据装换成JSON类型数据
#data:要转换的字符串
#ensure_ascii设置是否用ASCII码解析,False为否,True为是
josn_data = json.dumps(data,ensure_ascii=False)returnjosn_data @router('/update.html')defupdate(): with open('templates/update.html', 'r', encoding='utf-8') as file: body=file.read()returnbodydeferror():return '<h1>你的页面迷路了。。。</h1>'

JSON的数据格式:

[{"key":"value"},{"key":"value"},{"key":"value"}]

在JSON数据转换时需要注意,json.dumps()会默认采用ASCII码进项解码转换,想要不使用ASCII进行转码就需要设置ensure_ascii参数,将它设置成False即可解决问题。

这里给大家简单阐述一下路由表的概念:

路由表就是一个用请求地址作为Key,装饰器内层函数的引用作为Value而形成的键值对组成的字典。

其格式为:

{'/index.html':inner,'/error.html':inner }

对于路由表的自动维护,我们可以通过编写带参数的装饰器来实现:

router_table ={}defrouter(url):defdecorator(func):def inner(*args, **kwargs): func(*args, **kwargs) router_table[url]=innerreturninnerreturn decorator

通过@router('/index.html')语法糖来给路由表中添加数据。

其主要流程:

1.先执行router('/index.html'),然后将装饰器decorator函数的引用返回;

2.在通过@decorator的引用,将视图函数传递给func,将inner的引用及url组成键值对保存到路由表中;

这里再给大家扩展一个概念,视图函数是MVT开发模式中的V也就是View,其主要功能是处理数据。

3.数据库操作代码实现:

importpymysql#定义一个数据库连接函数
defdb_connect(): db= pymysql.connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')#返回数据库连接对象
returndb#定义一个index页面查询数据的函数
defdb_select(): db=db_connect() cur=db.cursor() sql= '''select * from info;'''cur.execute(sql) result=cur.fetchall() cur.close() db.close()returnresult#定义一个接口查询数据的函数
defdb_select_interface(): db=db_connect() cur=db.cursor() sql= '''select i.code, i.short, i.chg, i.turnover,i.price, i.highs, f.note_info from info i inner join focus f on i.id = f.info_id'''cur.execute(sql) result=cur.fetchall() cur.close() db.close()return result

最后,总结一下MiniWeb开发的大概流程:

1.在原有的TCP服务端的基础上加上一个请求资源的判断,即判断是否是动态资源;

2.如果是动态资源,就将动态资源的请求路径发送给框架,

3.框架在进行判断,如果动态资源是模版那就读出模版的数据并返回给服务器,最后再由服务器拼接响应报文给客户端,

4.如果动态资源请求的是数据,那么框架就回到数据库中将数据查询出来,并且拼接成JSON数据格式的字符串,再将JSON数据格式的字符串转换成JSON数据,最后在返回给服务器,最后再由服务器拼接响应报文并发送给客户端。

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看